@kolisachint/hoocode-agent 0.4.158 → 0.4.159
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 +2 -0
- package/dist/core/subagent-pool.d.ts.map +1 -1
- package/dist/core/subagent-pool.js +5 -1
- package/dist/core/subagent-pool.js.map +1 -1
- package/dist/core/warm-subagent-pool.d.ts.map +1 -1
- package/dist/core/warm-subagent-pool.js +5 -1
- package/dist/core/warm-subagent-pool.js.map +1 -1
- 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/CHANGELOG.md
CHANGED
|
@@ -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, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport { getDispatchTaskDir } from \"../config.js\";\nimport { attachJsonlLineReader } from \"../modes/rpc/jsonl.js\";\nimport { writeFileAtomicSync } from \"../utils/atomic-file.js\";\nimport { waitForChildProcess } from \"../utils/child-process.js\";\nimport { killProcessTree } from \"../utils/shell.js\";\nimport { MODEL_INHERIT } from \"./agent-frontmatter.js\";\nimport { type AgentRegistry, loadAgentRegistry } from \"./agent-registry.js\";\nimport { DispatchEvaluator } from \"./dispatch-evaluator.js\";\nimport { SubagentLifeguard } from \"./lifeguard.js\";\nimport { resolveModelReference } from \"./model-categories.js\";\nimport { OutputVerifier } from \"./output-verifier.js\";\nimport type { Settings } from \"./settings-manager.js\";\nimport {\n\tcurrentSubagentDepth,\n\tDEFER_MCP_SCHEMAS_ENV,\n\tresolveMaxSubagentDepth,\n\tSUBAGENT_DEPTH_ENV,\n\tSUBAGENT_SKIP_MCP_ENV,\n\ttoolAllowlistNeedsMcp,\n} from \"./subagent-depth.js\";\nimport { SUBAGENT_PROGRESS_EVENTS } from \"./subagent-events.js\";\nimport { TokenBudget } from \"./token-budget.js\";\n\n/**\n * Provider/model failure signatures where inheriting the parent model can\n * recover. Compiled once at module load instead of on every error check.\n */\nconst INHERITED_MODEL_FALLBACK_ERROR =\n\t/usage[_\\s-]?limit|subscription|quota|rate.?limit|too many requests|429|insufficient|out of credit|credit balance|billing|payment required|402|model[^\\n]*(not found|unavailable|not available|not supported|does not exist|invalid|unsupported)|no api key|no auth configured|authentication|unauthorized|forbidden|permission/i;\n\nexport interface SubagentPoolTask {\n\ttask_id: string;\n\tagent_type: string;\n\ttask: string;\n\tcontext?: string;\n\ttoken_budget?: number;\n\tcwd?: string;\n\tmodel?: string;\n\tprovider?: string;\n\t/**\n\t * Explicit session file for the child to persist/continue. When omitted the\n\t * child uses its own dispatch dir (`<dispatch>/<task_id>/session.jsonl`).\n\t * Resume reuses the original task's session file to continue the transcript.\n\t */\n\tsessionFile?: string;\n\t/** Internal: retry using the caller's model when a built-in agent's preferred model fails. */\n\tuseInheritedModelFallback?: boolean;\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\" | \"cancelled\";\n\t/** Parsed result.json content when available (e.g. on partial completion). */\n\tresult_data?: Record<string, unknown>;\n\t/** True when this run used the inherited-model fallback (preferred model failed first). */\n\tusedInheritedModelFallback?: boolean;\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 DispatchOptions {\n\t/** Skip evaluation and force this agent type (user/explicit override).\n\t * Accepts any registry-defined agent name, not just the built-in modes. */\n\tforceAgent?: string;\n\t/** Context distilled from the calling agent, passed to the subagent. */\n\tcontext?: string;\n\t/** Model id for the subagent (defaults to the child's configured default). */\n\tmodel?: string;\n\t/** Provider for the subagent. */\n\tprovider?: string;\n\t/** Explicit session file to persist/continue (used by resume). */\n\tsessionFile?: string;\n\t/** Caller-supplied task id. Defaults to a generated `dispatch-…` id. Lets a\n\t * caller register liveness/inbox state under the id before dispatch resolves. */\n\ttaskId?: string;\n}\n\nexport interface SubagentPoolOptions {\n\t/** Path to the hoocode executable (or the runtime, e.g. node, when prefixArgs is set). */\n\texecutable: string;\n\t/** Args inserted before task args (e.g. the CLI entry script for node/tsx). */\n\tprefixArgs?: 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\t/**\n\t * Non-default skill paths to forward to every spawned subagent via --skill.\n\t * Subagents auto-discover skills from standard locations; only paths that\n\t * won't be found by default discovery need to be forwarded here.\n\t */\n\tskillPaths?: string[];\n\t/** Settings for model category resolution. */\n\tsettings?: Settings;\n\t/**\n\t * Available/configured models used to derive default model-category mappings\n\t * when a tier is not explicitly set in `settings.modelCategories`. Snapshotted\n\t * at pool creation, mirroring how `settings` is captured.\n\t */\n\tavailableModels?: readonly Model<Api>[];\n}\n\n/**\n * Default hard cap on assistant turns for a spawned subagent when its definition\n * does not set `maxTurns`. The token budget is advisory (it warns but never\n * kills), so this turn cap is the guaranteed hard stop for every subagent.\n */\nexport const DEFAULT_SUBAGENT_MAX_TURNS = 50;\n\n/**\n * AgentSession event `type`s forwarded from a subagent's json event stream as\n * `task_progress` events. Deliberately coarse; the child now also filters its\n * stdout to this set plus `message_end` at the source (see\n * SUBAGENT_STDOUT_EVENT_TYPES), so the per-delta firehose no longer crosses the\n * pipe. Re-exported from the shared module so the child emitter and this\n * consumer stay in lockstep.\n */\nexport const FORWARDED_SUBAGENT_EVENTS = SUBAGENT_PROGRESS_EVENTS;\n\n/**\n * Cap on the captured stdout/stderr text kept per task (tail-truncated). The\n * capture exists for diagnostics (failure reasons, output.json), so keeping the\n * most recent tail is enough; without a cap a chatty child could grow the\n * parent's memory without bound across a long swarm.\n */\nexport const MAX_CAPTURED_STREAM_CHARS = 256 * 1024;\n\n/**\n * Cap on a single un-terminated JSONL line buffered from a child's stdout. The\n * forwarded events are small; anything approaching this is a runaway writer,\n * and the reader drops the line rather than buffering toward OOM.\n */\nexport const MAX_SUBAGENT_EVENT_LINE_CHARS = 8 * 1024 * 1024;\n\n/** Append a chunk to a capped capture buffer, keeping the most recent tail. */\nfunction appendTail(current: string, chunk: string, cap: number = MAX_CAPTURED_STREAM_CHARS): string {\n\tconst next = current + chunk;\n\treturn next.length > cap ? next.slice(next.length - cap) : next;\n}\n\n/** The action the pool should take for one JSONL line from a subagent's stdout. */\nexport type SubagentStdoutLine =\n\t| { kind: \"heartbeat\" }\n\t| { kind: \"progress\"; event: Record<string, unknown> }\n\t| { kind: \"ignore\" };\n\n/**\n * Classify one JSONL line from a subagent's stdout into the action to take.\n * Pure (no side effects) so the ping/forward/drop policy is unit-testable without\n * spawning a child. Line framing — UTF-8-safe reassembly of chunks split mid-line\n * — is handled upstream by attachJsonlLineReader; this only sees complete lines.\n */\nexport function classifySubagentLine(line: string): SubagentStdoutLine {\n\tconst trimmed = line.trim();\n\tif (!trimmed.startsWith(\"{\")) return { kind: \"ignore\" };\n\tlet parsed: Record<string, unknown>;\n\ttry {\n\t\tparsed = JSON.parse(trimmed) as Record<string, unknown>;\n\t} catch {\n\t\treturn { kind: \"ignore\" };\n\t}\n\tif (parsed.ping === true) return { kind: \"heartbeat\" };\n\tif (typeof parsed.type === \"string\" && FORWARDED_SUBAGENT_EVENTS.has(parsed.type)) {\n\t\treturn { kind: \"progress\", event: parsed };\n\t}\n\treturn { kind: \"ignore\" };\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 past the load-scaled threshold (60s base,\n * widened under concurrency/event-loop lag), process SIGKILLed\n * - \"task_timeout\" – hard timeout exceeded, process was SIGKILLed\n * - \"task_cancelled\" – user-initiated cancel (see cancel()); process tree killed\n * - \"budget_warning\" – token usage crossed 80% threshold (advisory)\n * - \"budget_exceeded\" – token usage crossed 100% threshold (advisory; never kills)\n * - \"task_progress\" – coarse lifecycle event (turn_end, tool start/end) parsed\n * from the child's json event stream, for live UI updates\n */\nexport class SubagentPool extends EventEmitter {\n\tprivate readonly maxConcurrency: number;\n\tprivate readonly executable: string;\n\tprivate readonly prefixArgs: string[];\n\tprivate readonly cwd: string;\n\tprivate readonly env: NodeJS.ProcessEnv;\n\tprivate readonly defaultTokenBudget: number;\n\t/** Non-default skill paths forwarded to every spawned subagent via --skill. */\n\tprivate skillPaths: string[];\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/** Lazily-loaded agent registry (frontmatter definitions) for this pool's cwd. */\n\tprivate registry?: AgentRegistry;\n\t/** Tracks why a task was killed (stalled / timeout / user cancel) before exit handler fires. */\n\tprivate killReasons = new Map<string, \"stalled\" | \"timeout\" | \"cancelled\">();\n\t/** Persistent terminal status map, survives wait_for consumption. */\n\tprivate taskStatus = new Map<string, \"done\" | \"failed\" | \"stalled\" | \"timeout\" | \"cancelled\">();\n\t/** Settings for model category resolution. */\n\tprivate readonly settings?: Settings;\n\t/** Available models used to derive default model-category mappings (snapshot). */\n\tprivate readonly availableModels: readonly Model<Api>[];\n\n\tconstructor(options: SubagentPoolOptions) {\n\t\tsuper();\n\t\tthis.maxConcurrency = options.maxConcurrency ?? 5;\n\t\tthis.executable = options.executable;\n\t\tthis.prefixArgs = options.prefixArgs ?? [];\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.skillPaths = options.skillPaths ? [...options.skillPaths] : [];\n\t\tthis.settings = options.settings;\n\t\tthis.availableModels = options.availableModels ?? [];\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/** Update the non-default skill paths forwarded to new subagents. */\n\tupdateSkillPaths(paths: string[]): void {\n\t\tthis.skillPaths = [...paths];\n\t}\n\n\t/**\n\t * Report external in-process load (e.g. the number of background MCP tools\n\t * currently executing in the parent) to the lifeguard. This widens its\n\t * heartbeat/timeout tolerance so monitored subagents aren't false-positive\n\t * reaped when the parent's event loop is busy with concurrent background work.\n\t */\n\tsetExternalLoad(count: number): void {\n\t\tthis.lifeguard.setExternalLoad(count);\n\t}\n\n\t/** Lazily load the agent registry for this pool's cwd. */\n\tprivate getRegistry(): AgentRegistry {\n\t\tif (!this.registry) {\n\t\t\tthis.registry = loadAgentRegistry({ cwd: this.cwd });\n\t\t}\n\t\treturn this.registry;\n\t}\n\n\t/** Priority value: higher numbers run first. */\n\tprivate priorityOf(agent_type: string): number {\n\t\t// Read-only investigation (explore/plan) often unblocks downstream work, so\n\t\t// it runs ahead of other agents.\n\t\treturn agent_type === \"explore\" || agent_type === \"plan\" ? 2 : 1;\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(\n\t\ttask_id: string,\n\t): \"running\" | \"queued\" | \"done\" | \"failed\" | \"stalled\" | \"timeout\" | \"cancelled\" | \"unknown\" {\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.status === \"cancelled\") return \"cancelled\";\n\t\t\tif (result.ok) return \"done\";\n\t\t\treturn \"failed\";\n\t\t}\n\t\treturn \"unknown\";\n\t}\n\n\t/**\n\t * Cancel a task on the user's behalf (Esc/abort mid-turn). A queued task is\n\t * removed and settles immediately; a running task's whole process tree is\n\t * killed and settles with status \"cancelled\" when its exit is observed\n\t * (unless it already wrote a valid result.json — completed work is honored).\n\t * Returns false for unknown/settled task ids.\n\t */\n\tcancel(task_id: string): boolean {\n\t\tconst queued = this.queue.findIndex((t) => t.task_id === task_id);\n\t\tif (queued !== -1) {\n\t\t\tthis.queue.splice(queued, 1);\n\t\t\tconst result: SubagentResult = {\n\t\t\t\ttask_id,\n\t\t\t\tok: false,\n\t\t\t\tstdout: \"\",\n\t\t\t\tstderr: \"\",\n\t\t\t\texit_code: null,\n\t\t\t\terror: \"cancelled before start\",\n\t\t\t\tstatus: \"cancelled\",\n\t\t\t};\n\t\t\tthis.emit(\"task_cancelled\", { task_id });\n\t\t\tthis.resolveWaiter(task_id, result);\n\t\t\treturn true;\n\t\t}\n\t\tconst slot = this.slots.get(task_id);\n\t\tif (!slot) return false;\n\t\tthis.killReasons.set(task_id, \"cancelled\");\n\t\tif (slot.pid > 0) {\n\t\t\tkillProcessTree(slot.pid);\n\t\t} else if (!slot.process.killed) {\n\t\t\tslot.process.kill(\"SIGKILL\");\n\t\t}\n\t\treturn true;\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 `options.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, options: DispatchOptions = {}): Promise<TaskResult> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(new Error(\"SubagentPool has been disposed\"));\n\t\t}\n\t\tconst begin = this.beginDispatch(task, options);\n\t\tif (begin.handled_inline) {\n\t\t\treturn { handled_inline: true, reason: begin.reason };\n\t\t}\n\t\tconst result = await this.wait_for(begin.task_id);\n\t\treturn {\n\t\t\thandled_inline: false,\n\t\t\ttask_id: begin.task_id,\n\t\t\tagent_type: begin.agent_type,\n\t\t\treason: begin.reason,\n\t\t\tresult,\n\t\t\tduration: Date.now() - begin.startTime,\n\t\t};\n\t}\n\n\t/**\n\t * Fire-and-forget dispatch for background agents. Spawns the subagent and\n\t * returns its handle immediately; the caller polls get_status()/collect().\n\t */\n\tdispatchDetached(\n\t\ttask: string,\n\t\toptions: DispatchOptions = {},\n\t): { handled_inline: boolean; task_id?: string; agent_type?: string; reason?: string } {\n\t\tif (this.disposed) {\n\t\t\tthrow new Error(\"SubagentPool has been disposed\");\n\t\t}\n\t\tconst begin = this.beginDispatch(task, options);\n\t\tif (begin.handled_inline) {\n\t\t\treturn { handled_inline: true, reason: begin.reason };\n\t\t}\n\t\treturn { handled_inline: false, task_id: begin.task_id, agent_type: begin.agent_type, reason: begin.reason };\n\t}\n\n\t/**\n\t * Evaluate, log, and spawn a task without waiting. Shared by dispatch()\n\t * (blocking) and dispatchDetached() (background).\n\t */\n\tprivate beginDispatch(\n\t\ttask: string,\n\t\toptions: DispatchOptions,\n\t):\n\t\t| { handled_inline: true; reason?: string }\n\t\t| { handled_inline: false; task_id: string; agent_type: string; reason?: string; startTime: number } {\n\t\tconst { forceAgent, context, model, provider, sessionFile } = options;\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 = forceAgent ?? \"general-purpose\";\n\t\tconst task_id = options.taskId ?? `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\t\t// Depth of the child about to be spawned (this process's depth + 1). Surfaced\n\t\t// so a delegation tree's nesting is visible in logs without extra tooling.\n\t\tconst childDepth = currentSubagentDepth(this.env) + 1;\n\n\t\t// Pre-dispatch logging. Use stderr: stdout is reserved for the JSON event\n\t\t// stream / TUI render and must not be polluted.\n\t\tconsole.error(\n\t\t\t`[DISPATCH] agent=${agent_type} depth=${childDepth} reason=${reason} complexity=${complexity} task_id=${task_id}`,\n\t\t);\n\t\tthis.writeDispatchLog(task_id, agent_type, reason, complexity, task, childDepth);\n\n\t\tconst poolTask: SubagentPoolTask = {\n\t\t\ttask_id,\n\t\t\tagent_type,\n\t\t\ttask,\n\t\t\tcontext,\n\t\t\tmodel,\n\t\t\tprovider,\n\t\t\tsessionFile,\n\t\t\tcwd: this.cwd,\n\t\t};\n\t\tconst startTime = Date.now();\n\t\tthis.spawn(poolTask);\n\t\treturn { handled_inline: false, task_id, agent_type, reason, startTime };\n\t}\n\n\t/**\n\t * Non-destructively read a completed task's result (for background polling).\n\t * Returns undefined while the task is still running/queued, or if its result\n\t * was already consumed via wait_for().\n\t */\n\tcollect(task_id: string): SubagentResult | undefined {\n\t\treturn this.completed.get(task_id);\n\t}\n\n\t/** Absolute path of the persisted session file for a task. */\n\tgetSessionFile(task_id: string, cwd: string = this.cwd): string {\n\t\treturn join(getDispatchTaskDir(cwd, task_id), \"session.jsonl\");\n\t}\n\n\t/**\n\t * Resume a previously dispatched subagent, continuing its persisted session\n\t * with a follow-up prompt. Recovers the original agent type from its dispatch\n\t * log. Rejects if no resumable session exists for the task.\n\t */\n\tasync resume(\n\t\ttask_id: string,\n\t\tprompt: string,\n\t\toptions: Omit<DispatchOptions, \"forceAgent\" | \"sessionFile\"> = {},\n\t): Promise<TaskResult> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(new Error(\"SubagentPool has been disposed\"));\n\t\t}\n\t\tconst sessionFile = this.getSessionFile(task_id);\n\t\tif (!existsSync(sessionFile)) {\n\t\t\treturn Promise.reject(new Error(`No resumable session for task \"${task_id}\" (expected ${sessionFile}).`));\n\t\t}\n\t\tconst agent_type = this.readDispatchAgentType(task_id) ?? \"general-purpose\";\n\t\treturn this.dispatch(prompt, { ...options, forceAgent: agent_type, sessionFile });\n\t}\n\n\t/** Recover the agent type a task was dispatched with, from its dispatch log. */\n\tprivate readDispatchAgentType(task_id: string): string | undefined {\n\t\tconst path = join(getDispatchTaskDir(this.cwd, task_id), \"dispatch-log.json\");\n\t\tif (!existsSync(path)) return undefined;\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\")) as { agent_type?: string };\n\t\t\treturn typeof parsed.agent_type === \"string\" ? parsed.agent_type : undefined;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\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\tdepth: number,\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\tdepth,\n\t\t\treason,\n\t\t\tcomplexity,\n\t\t\ttask,\n\t\t};\n\t\tconst path = join(getDispatchTaskDir(this.cwd, 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(getDispatchTaskDir(this.cwd, task_id), \"output.json\");\n\t\ttry {\n\t\t\t// Atomic like result.json: background pollers may read output.json while\n\t\t\t// it is being (re)written.\n\t\t\twriteFileAtomicSync(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/**\n\t * Remove a task's dispatch dir after a clean, verified success. Best-effort:\n\t * a cleanup failure must never fail an otherwise successful task.\n\t */\n\tprivate cleanupDispatchDir(task_id: string, cwd: string): void {\n\t\ttry {\n\t\t\trmSync(getDispatchTaskDir(cwd, task_id), { recursive: true, force: true });\n\t\t} catch {\n\t\t\t// Best-effort cleanup\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\t// Kill the whole process group/tree, not just the direct child: a\n\t\t\t// subagent's own children (bash commands, nested subagents) must not\n\t\t\t// outlive the pool.\n\t\t\tif (slot.pid > 0) {\n\t\t\t\tkillProcessTree(slot.pid);\n\t\t\t} else if (!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\t// Persist the child's session so a finished/interrupted subagent can be\n\t\t// resumed later (see resume()). SessionManager.open() creates the file on\n\t\t// first run and continues it on subsequent runs.\n\t\tconst sessionFile = task.sessionFile ?? this.getSessionFile(task.task_id, task.cwd ?? this.cwd);\n\t\tconst args: string[] = [\n\t\t\t...this.prefixArgs,\n\t\t\t\"--mode\",\n\t\t\t\"json\",\n\t\t\t\"--session\",\n\t\t\tsessionFile,\n\t\t\t\"--task-id\",\n\t\t\ttask.task_id,\n\t\t];\n\n\t\t// Prefer the data-driven agent definition from the registry; fall back to the\n\t\t// built-in mode prompt/allowlist for legacy modes not present in the registry.\n\t\tconst def = task.agent_type ? this.getRegistry().get(task.agent_type) : undefined;\n\n\t\tif (task.agent_type) {\n\t\t\tconst systemPrompt = def?.prompt;\n\t\t\tif (systemPrompt) {\n\t\t\t\targs.push(\"--system-prompt\", systemPrompt);\n\t\t\t}\n\n\t\t\t// A `delegate: true` agent may itself dispatch via the Task tool, but only\n\t\t\t// while the child it becomes can still nest (childDepth < cap) — so the\n\t\t\t// deepest permitted level cannot delegate further. Gating here keeps the\n\t\t\t// authorization explicit and bounded by the same cap as the depth guard.\n\t\t\tconst childDepth = currentSubagentDepth(this.env) + 1;\n\t\t\tconst canChildDelegate = def?.delegate === true && childDepth < resolveMaxSubagentDepth(undefined, this.env);\n\n\t\t\t// Tool allowlist comes from the agent definition's frontmatter `tools`\n\t\t\t// field (read-only built-ins declare their own sandbox). When omitted, no\n\t\t\t// --tools is passed and the subagent inherits all parent tools (so the Task\n\t\t\t// tool already survives). A delegating agent with an explicit allowlist must\n\t\t\t// have Task/TaskOutput added, or the child would filter them out.\n\t\t\tconst tools = def?.tools ? [...def.tools] : undefined;\n\t\t\tif (canChildDelegate && tools) {\n\t\t\t\tfor (const t of [\"Task\", \"TaskOutput\"]) {\n\t\t\t\t\tif (!tools.includes(t)) tools.push(t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (tools && tools.length > 0) {\n\t\t\t\targs.push(\"--tools\", tools.join(\",\"));\n\t\t\t}\n\t\t\tif (def?.disallowedTools && def.disallowedTools.length > 0) {\n\t\t\t\targs.push(\"--disallowed-tools\", def.disallowedTools.join(\",\"));\n\t\t\t}\n\n\t\t\t// Propagate subagent enablement so the child registers the Task tool; without\n\t\t\t// this the flag-based enablement would not reach a spawned child.\n\t\t\tif (canChildDelegate) {\n\t\t\t\targs.push(\"--enable-subagents\");\n\t\t\t\t// Scoped delegation: restrict which agent types this child may spawn.\n\t\t\t\tif (def?.delegateTo && def.delegateTo.length > 0) {\n\t\t\t\t\targs.push(\"--delegate-allow\", def.delegateTo.join(\",\"));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Model precedence: a definition's explicit model wins (unless it is the\n\t\t// `inherit` sentinel), otherwise use the caller-provided model. Built-in\n\t\t// agents can retry with the inherited model when their preferred model is\n\t\t// unavailable or quota-limited.\n\t\tconst explicitModel =\n\t\t\t!task.useInheritedModelFallback && def?.model && def.model !== MODEL_INHERIT ? def.model : undefined;\n\t\t// Resolve a model-category reference (fast/standard/capable) to its\n\t\t// configured or derived model id. An unresolvable category yields undefined,\n\t\t// so no `--model` is passed and the child keeps its default model.\n\t\tconst rawModel = explicitModel ?? task.model;\n\t\tconst modelToUse = rawModel ? resolveModelReference(rawModel, this.settings, this.availableModels) : undefined;\n\t\tif (modelToUse) {\n\t\t\targs.push(\"--model\", modelToUse);\n\t\t}\n\t\tif (task.provider) {\n\t\t\targs.push(\"--provider\", task.provider);\n\t\t}\n\n\t\t// Always give subagents a hard turn cap. With the token budget now advisory\n\t\t// (warn-only), this is the guaranteed hard stop for a runaway subagent.\n\t\tconst maxTurns = def?.maxTurns && def.maxTurns > 0 ? def.maxTurns : DEFAULT_SUBAGENT_MAX_TURNS;\n\t\targs.push(\"--max-turns\", String(maxTurns));\n\n\t\t// Forward non-default skill paths so the subagent has access to all parent skills.\n\t\t// Standard discovery locations (~/.hoocode/, .hoocode/, .claude/) are found automatically.\n\t\tfor (const skillPath of this.skillPaths) {\n\t\t\targs.push(\"--skill\", skillPath);\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/**\n\t * Environment for a spawned child.\n\t *\n\t * Stamps the child's depth (parent depth + 1) so its own guard knows where it\n\t * sits in the tree; the tree-wide cap (HOOCODE_SUBAGENT_MAX_DEPTH) is inherited\n\t * via the spread, so at the default cap of 1 the child lands at depth 1 and\n\t * cannot spawn further subagents. Also flags the child to skip MCP server\n\t * connection when its tool allowlist is explicit and MCP-free — connecting\n\t * external servers it can never call is pure boot latency.\n\t */\n\tprivate childSpawnEnv(task: SubagentPoolTask): NodeJS.ProcessEnv {\n\t\tconst env: NodeJS.ProcessEnv = {\n\t\t\t...this.env,\n\t\t\t[SUBAGENT_DEPTH_ENV]: String(currentSubagentDepth(this.env) + 1),\n\t\t};\n\t\t// Use the agent's own frontmatter allowlist (the same source buildArgs passes\n\t\t// via --tools). A delegating child also gets Task/TaskOutput appended there,\n\t\t// but neither is an MCP tool, so the decision is unchanged by that.\n\t\tconst def = task.agent_type ? this.getRegistry().get(task.agent_type) : undefined;\n\t\tif (!toolAllowlistNeedsMcp(def?.tools)) {\n\t\t\tenv[SUBAGENT_SKIP_MCP_ENV] = \"1\";\n\t\t}\n\t\t// A child never defers MCP schemas: if it needs MCP it eager-registers its\n\t\t// allowlisted tools at dispatch so they are immediately callable (spec §2).\n\t\tdelete env[DEFER_MCP_SCHEMAS_ENV];\n\t\treturn env;\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\t// The token budget is advisory: surface telemetry but never kill. The\n\t\t\t// guaranteed hard stop is the per-subagent turn cap (--max-turns); see\n\t\t\t// DEFAULT_SUBAGENT_MAX_TURNS.\n\t\t\tbudget.on(\"budget_exceeded\", (data: { task_id: string; used: number; limit: number }) => {\n\t\t\t\tthis.emit(\"budget_exceeded\", data);\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.childSpawnEnv(task),\n\t\t\t\tshell: false,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t// POSIX: the child leads its own process group so a kill can reach its\n\t\t\t\t// whole tree (see lifeguard/dispose). A single-PID SIGKILL orphans any\n\t\t\t\t// grandchildren the subagent spawned (its own bash commands, nested\n\t\t\t\t// subagents), which kept burning CPU after their parent was reaped.\n\t\t\t\tdetached: process.platform !== \"win32\",\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 detachStdoutReader: (() => void) | undefined;\n\n\t\tproc.stdout?.on(\"data\", (data: Buffer) => {\n\t\t\t// Capture (tail-capped) for diagnostics, and treat any output as a\n\t\t\t// heartbeat: a child busily streaming events is alive even when the\n\t\t\t// parent's starved event loop hasn't parsed its {\"ping\":true} line yet.\n\t\t\t// All *parsing* happens in the single JSONL reader below.\n\t\t\tstdout = appendTail(stdout, data.toString());\n\t\t\tthis.lifeguard.recordHeartbeat(task.task_id);\n\t\t});\n\n\t\t// The one parser on the child's stdout: UTF-8-safe, LF-only framing —\n\t\t// multi-byte characters and large events split across pipe chunks are\n\t\t// reassembled before parsing. Each complete line feeds the token budget\n\t\t// (usage telemetry) and the ping/forward/drop classifier; previously the\n\t\t// budget ran its own naive chunk-splitting parser on the same stream, which\n\t\t// corrupted lines split mid-character. The line buffer is capped so a\n\t\t// runaway writer cannot OOM the parent. Detached when the child exits (see\n\t\t// cleanup below); the reader also self-detaches on stream error/close.\n\t\tif (proc.stdout) {\n\t\t\tdetachStdoutReader = attachJsonlLineReader(\n\t\t\t\tproc.stdout,\n\t\t\t\t(line) => {\n\t\t\t\t\tbudget.processLine(line);\n\t\t\t\t\tconst action = classifySubagentLine(line);\n\t\t\t\t\tif (action.kind === \"heartbeat\") {\n\t\t\t\t\t\tthis.lifeguard.recordHeartbeat(task.task_id);\n\t\t\t\t\t} else if (action.kind === \"progress\") {\n\t\t\t\t\t\tthis.emit(\"task_progress\", {\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\tevent: action.event,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{ maxBuffer: MAX_SUBAGENT_EVENT_LINE_CHARS },\n\t\t\t);\n\t\t}\n\t\tproc.stderr?.on(\"data\", (data: Buffer) => {\n\t\t\tstderr = appendTail(stderr, data.toString());\n\t\t});\n\n\t\t// True when this attempt scheduled another run of the same task id (spawn\n\t\t// retry or inherited-model fallback). The retry reuses the task's TokenBudget\n\t\t// so usage accumulates across attempts; the .finally below must then leave\n\t\t// the budget (and its listeners) alone instead of tearing it down under the\n\t\t// retry's feet.\n\t\tlet retryScheduled = false;\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// A subagent's success is defined by a valid, verified result.json, not by\n\t\t\t\t// its exit code. A child that finished its work and wrote a valid result can\n\t\t\t\t// still be SIGKILLed by the lifeguard before it exits on its own (lingering\n\t\t\t\t// open handles delay a natural exit past the heartbeat threshold), which forces\n\t\t\t\t// exit_code === null. Keying completion off the verified result, not code === 0,\n\t\t\t\t// honors that genuine success instead of discarding it as a false stall.\n\t\t\t\tconst verification = this.verifier.verify(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\t// A well-formed result.json counts as clean completion unless its own\n\t\t\t\t// status field declares failure (e.g. \"failed\" from a provider quota\n\t\t\t\t// error). Without this check the pool would treat a child that wrote a\n\t\t\t\t// valid-but-failed result.json and exited non-zero as a success.\n\t\t\t\tlet cleanlyCompleted = code === 0 || verification.valid;\n\t\t\t\tif (cleanlyCompleted && verification.valid) {\n\t\t\t\t\tconst rd = this.tryReadResultJson(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\t\tif (rd && (rd as Record<string, unknown>).status === \"failed\") {\n\t\t\t\t\t\tcleanlyCompleted = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// If killed (lifeguard reap or user cancel) before producing a valid\n\t\t\t\t// result, honor the kill; a child that already wrote a verified\n\t\t\t\t// result.json completed its work and settles as a success below.\n\t\t\t\tif (killReason !== undefined && !verification.valid) {\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: cleanlyCompleted,\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\t// Advisory telemetry only: exceeding the budget never fails the task.\n\t\t\t\t\tbudget_exceeded: budgetExceeded,\n\t\t\t\t\tstatus: cleanlyCompleted ? \"complete\" : \"failed\",\n\t\t\t\t\tusedInheritedModelFallback: task.useInheritedModelFallback === true,\n\t\t\t\t};\n\n\t\t\t\tif (result.ok) {\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\t// Attach the verified result.json so callers can read the summary\n\t\t\t\t\t// without parsing the raw event stream.\n\t\t\t\t\tresult.result_data = this.tryReadResultJson(task.task_id, task.cwd ?? this.cwd);\n\n\t\t\t\t\t// Clean success: discard the per-task dispatch dir entirely\n\t\t\t\t\t// (session.jsonl, result.json, dispatch-log.json, budget.json). The\n\t\t\t\t\t// in-memory result already carries result_data, so callers lose\n\t\t\t\t\t// nothing. Trade-off: resume() only works for non-successful tasks.\n\t\t\t\t\tthis.cleanupDispatchDir(task.task_id, task.cwd ?? this.cwd);\n\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\tthis.resolveWaiter(task.task_id, result);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Failure path: keep the dispatch dir for debugging and persist output.\n\t\t\t\t// Attach the child's result.json (if any) and derive a concrete failure\n\t\t\t\t// reason so callers see the real cause (e.g. a provider usage/quota\n\t\t\t\t// error) instead of a generic \"subagent failed\".\n\t\t\t\tresult.result_data = this.tryReadResultJson(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\tif (!result.error) {\n\t\t\t\t\tresult.error = this.deriveFailureReason(result);\n\t\t\t\t}\n\t\t\t\tif (this.shouldRetryWithInheritedModel(task, result)) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`[DISPATCH] agent=${task.agent_type} task_id=${task.task_id} preferred model failed; retrying with inherited model`,\n\t\t\t\t\t);\n\t\t\t\t\tthis.cleanupRetryArtifacts(task);\n\t\t\t\t\tretryScheduled = true;\n\t\t\t\t\tthis.queue.unshift({ ...task, useInheritedModelFallback: true });\n\t\t\t\t\treturn;\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: result.error ?? `Exited with code ${code}`,\n\t\t\t\t});\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\t// The retry (started synchronously here) looks the budget up from\n\t\t\t\t\t// this.budgets and reuses it; flag it so the .finally below doesn't\n\t\t\t\t\t// strip the budget's listeners or delete the entry the retry now owns.\n\t\t\t\t\tretryScheduled = true;\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\tusedInheritedModelFallback: task.useInheritedModelFallback === true,\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\t// Runs on success AND failure: the stdout reader used to be detached only\n\t\t\t\t// on the success path, leaking listeners (and their buffers) whenever a\n\t\t\t\t// child failed or the wait rejected.\n\t\t\t\tdetachStdoutReader?.();\n\t\t\t\tproc.stdout?.removeAllListeners(\"data\");\n\t\t\t\tproc.stderr?.removeAllListeners(\"data\");\n\t\t\t\tproc.stdout?.destroy();\n\t\t\t\tproc.stderr?.destroy();\n\t\t\t\tif (!retryScheduled) {\n\t\t\t\t\tbudget.removeAllListeners();\n\t\t\t\t\tthis.budgets.delete(task.task_id);\n\t\t\t\t}\n\t\t\t\tthis.pull();\n\t\t\t});\n\t}\n\n\t/** Whether a failed built-in subagent should be retried with `model: inherit`. */\n\tprivate shouldRetryWithInheritedModel(task: SubagentPoolTask, result: SubagentResult): boolean {\n\t\tif (task.useInheritedModelFallback) return false;\n\t\tif (task.sessionFile) return false;\n\t\t// Only the parent model is required: the provider may be unset when the\n\t\t// harness routes through a gateway. The retry inherits the parent model and\n\t\t// lets the child resolve the provider from its own default when none was threaded through.\n\t\tif (!task.model) return false;\n\n\t\tconst def = task.agent_type ? this.getRegistry().get(task.agent_type) : undefined;\n\t\t// Built-in agents always inherit; project agents may pin an explicit model in\n\t\t// frontmatter, so let them fall back too when that model is rejected.\n\t\tif (def?.source !== \"builtin\" && def?.source !== \"project\") return false;\n\t\tif (!def.model || def.model === MODEL_INHERIT) return false;\n\n\t\treturn this.isInheritedModelFallbackError(result);\n\t}\n\n\t/** Detect provider/model failures where inheriting the parent model can recover. */\n\tprivate isInheritedModelFallbackError(result: SubagentResult): boolean {\n\t\tconst text = [result.error, result.stderr, JSON.stringify(result.result_data ?? {})]\n\t\t\t.filter((part): part is string => typeof part === \"string\" && part.length > 0)\n\t\t\t.join(\"\\n\");\n\n\t\treturn INHERITED_MODEL_FALLBACK_ERROR.test(text);\n\t}\n\n\t/** Remove failed attempt artifacts before rerunning the same task id. */\n\tprivate cleanupRetryArtifacts(task: SubagentPoolTask): void {\n\t\tconst cwd = task.cwd ?? this.cwd;\n\t\tconst taskDir = getDispatchTaskDir(cwd, task.task_id);\n\t\tconst sessionFile = task.sessionFile ?? this.getSessionFile(task.task_id, cwd);\n\t\ttry {\n\t\t\trmSync(sessionFile, { force: true });\n\t\t\trmSync(join(taskDir, \"result.json\"), { force: true });\n\t\t\trmSync(join(taskDir, \"output.json\"), { force: true });\n\t\t} catch {\n\t\t\t// Best-effort cleanup; retry can still proceed with existing artifacts.\n\t\t}\n\t}\n\n\t/**\n\t * Best-effort concrete failure reason for a non-zero-exit subagent. Prefers\n\t * the child's result.json summary (which carries the provider/model error\n\t * message on failure), then the tail of stderr, then the exit code.\n\t */\n\tprivate deriveFailureReason(result: SubagentResult): string {\n\t\tconst summary = (result.result_data as { summary?: string } | undefined)?.summary?.trim();\n\t\tif (summary) {\n\t\t\treturn summary;\n\t\t}\n\t\tconst stderrTail = result.stderr\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => line.trim())\n\t\t\t.filter((line) => line.length > 0)\n\t\t\t.slice(-5)\n\t\t\t.join(\"\\n\");\n\t\tif (stderrTail) {\n\t\t\treturn stderrTail;\n\t\t}\n\t\treturn `Exited with code ${result.exit_code}`;\n\t}\n\n\tprivate tryReadResultJson(task_id: string, cwd: string): Record<string, unknown> | undefined {\n\t\tconst path = join(getDispatchTaskDir(cwd, 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.status === \"cancelled\") this.taskStatus.set(task_id, \"cancelled\");\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, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport { getDispatchTaskDir } from \"../config.js\";\nimport { attachJsonlLineReader } from \"../modes/rpc/jsonl.js\";\nimport { writeFileAtomicSync } from \"../utils/atomic-file.js\";\nimport { waitForChildProcess } from \"../utils/child-process.js\";\nimport { killProcessTree } from \"../utils/shell.js\";\nimport { MODEL_INHERIT } from \"./agent-frontmatter.js\";\nimport { type AgentRegistry, loadAgentRegistry } from \"./agent-registry.js\";\nimport { DispatchEvaluator } from \"./dispatch-evaluator.js\";\nimport { SubagentLifeguard } from \"./lifeguard.js\";\nimport { resolveModelReference } from \"./model-categories.js\";\nimport { OutputVerifier } from \"./output-verifier.js\";\nimport type { Settings } from \"./settings-manager.js\";\nimport {\n\tcurrentSubagentDepth,\n\tDEFER_MCP_SCHEMAS_ENV,\n\tresolveMaxSubagentDepth,\n\tSUBAGENT_DEPTH_ENV,\n\tSUBAGENT_SKIP_MCP_ENV,\n\ttoolAllowlistNeedsMcp,\n} from \"./subagent-depth.js\";\nimport { SUBAGENT_PROGRESS_EVENTS } from \"./subagent-events.js\";\nimport { TokenBudget } from \"./token-budget.js\";\n\n/**\n * Provider/model failure signatures where inheriting the parent model can\n * recover. Compiled once at module load instead of on every error check.\n */\nconst INHERITED_MODEL_FALLBACK_ERROR =\n\t/usage[_\\s-]?limit|subscription|quota|rate.?limit|too many requests|429|insufficient|out of credit|credit balance|billing|payment required|402|model[^\\n]*(not found|unavailable|not available|not supported|does not exist|invalid|unsupported)|no api key|no auth configured|authentication|unauthorized|forbidden|permission/i;\n\nexport interface SubagentPoolTask {\n\ttask_id: string;\n\tagent_type: string;\n\ttask: string;\n\tcontext?: string;\n\ttoken_budget?: number;\n\tcwd?: string;\n\tmodel?: string;\n\tprovider?: string;\n\t/**\n\t * Explicit session file for the child to persist/continue. When omitted the\n\t * child uses its own dispatch dir (`<dispatch>/<task_id>/session.jsonl`).\n\t * Resume reuses the original task's session file to continue the transcript.\n\t */\n\tsessionFile?: string;\n\t/** Internal: retry using the caller's model when a built-in agent's preferred model fails. */\n\tuseInheritedModelFallback?: boolean;\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\" | \"cancelled\";\n\t/** Parsed result.json content when available (e.g. on partial completion). */\n\tresult_data?: Record<string, unknown>;\n\t/** True when this run used the inherited-model fallback (preferred model failed first). */\n\tusedInheritedModelFallback?: boolean;\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 DispatchOptions {\n\t/** Skip evaluation and force this agent type (user/explicit override).\n\t * Accepts any registry-defined agent name, not just the built-in modes. */\n\tforceAgent?: string;\n\t/** Context distilled from the calling agent, passed to the subagent. */\n\tcontext?: string;\n\t/** Model id for the subagent (defaults to the child's configured default). */\n\tmodel?: string;\n\t/** Provider for the subagent. */\n\tprovider?: string;\n\t/** Explicit session file to persist/continue (used by resume). */\n\tsessionFile?: string;\n\t/** Caller-supplied task id. Defaults to a generated `dispatch-…` id. Lets a\n\t * caller register liveness/inbox state under the id before dispatch resolves. */\n\ttaskId?: string;\n}\n\nexport interface SubagentPoolOptions {\n\t/** Path to the hoocode executable (or the runtime, e.g. node, when prefixArgs is set). */\n\texecutable: string;\n\t/** Args inserted before task args (e.g. the CLI entry script for node/tsx). */\n\tprefixArgs?: 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\t/**\n\t * Non-default skill paths to forward to every spawned subagent via --skill.\n\t * Subagents auto-discover skills from standard locations; only paths that\n\t * won't be found by default discovery need to be forwarded here.\n\t */\n\tskillPaths?: string[];\n\t/** Settings for model category resolution. */\n\tsettings?: Settings;\n\t/**\n\t * Available/configured models used to derive default model-category mappings\n\t * when a tier is not explicitly set in `settings.modelCategories`. Snapshotted\n\t * at pool creation, mirroring how `settings` is captured.\n\t */\n\tavailableModels?: readonly Model<Api>[];\n}\n\n/**\n * Default hard cap on assistant turns for a spawned subagent when its definition\n * does not set `maxTurns`. The token budget is advisory (it warns but never\n * kills), so this turn cap is the guaranteed hard stop for every subagent.\n */\nexport const DEFAULT_SUBAGENT_MAX_TURNS = 50;\n\n/**\n * AgentSession event `type`s forwarded from a subagent's json event stream as\n * `task_progress` events. Deliberately coarse; the child now also filters its\n * stdout to this set plus `message_end` at the source (see\n * SUBAGENT_STDOUT_EVENT_TYPES), so the per-delta firehose no longer crosses the\n * pipe. Re-exported from the shared module so the child emitter and this\n * consumer stay in lockstep.\n */\nexport const FORWARDED_SUBAGENT_EVENTS = SUBAGENT_PROGRESS_EVENTS;\n\n/**\n * Cap on the captured stdout/stderr text kept per task (tail-truncated). The\n * capture exists for diagnostics (failure reasons, output.json), so keeping the\n * most recent tail is enough; without a cap a chatty child could grow the\n * parent's memory without bound across a long swarm.\n */\nexport const MAX_CAPTURED_STREAM_CHARS = 256 * 1024;\n\n/**\n * Cap on a single un-terminated JSONL line buffered from a child's stdout. The\n * forwarded events are small; anything approaching this is a runaway writer,\n * and the reader drops the line rather than buffering toward OOM.\n */\nexport const MAX_SUBAGENT_EVENT_LINE_CHARS = 8 * 1024 * 1024;\n\n/** Append a chunk to a capped capture buffer, keeping the most recent tail. */\nfunction appendTail(current: string, chunk: string, cap: number = MAX_CAPTURED_STREAM_CHARS): string {\n\tconst next = current + chunk;\n\treturn next.length > cap ? next.slice(next.length - cap) : next;\n}\n\n/** The action the pool should take for one JSONL line from a subagent's stdout. */\nexport type SubagentStdoutLine =\n\t| { kind: \"heartbeat\" }\n\t| { kind: \"progress\"; event: Record<string, unknown> }\n\t| { kind: \"ignore\" };\n\n/**\n * Classify one JSONL line from a subagent's stdout into the action to take.\n * Pure (no side effects) so the ping/forward/drop policy is unit-testable without\n * spawning a child. Line framing — UTF-8-safe reassembly of chunks split mid-line\n * — is handled upstream by attachJsonlLineReader; this only sees complete lines.\n */\nexport function classifySubagentLine(line: string): SubagentStdoutLine {\n\tconst trimmed = line.trim();\n\tif (!trimmed.startsWith(\"{\")) return { kind: \"ignore\" };\n\tlet parsed: Record<string, unknown>;\n\ttry {\n\t\tparsed = JSON.parse(trimmed) as Record<string, unknown>;\n\t} catch {\n\t\treturn { kind: \"ignore\" };\n\t}\n\tif (parsed.ping === true) return { kind: \"heartbeat\" };\n\tif (typeof parsed.type === \"string\" && FORWARDED_SUBAGENT_EVENTS.has(parsed.type)) {\n\t\treturn { kind: \"progress\", event: parsed };\n\t}\n\treturn { kind: \"ignore\" };\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 past the load-scaled threshold (60s base,\n * widened under concurrency/event-loop lag), process SIGKILLed\n * - \"task_timeout\" – hard timeout exceeded, process was SIGKILLed\n * - \"task_cancelled\" – user-initiated cancel (see cancel()); process tree killed\n * - \"budget_warning\" – token usage crossed 80% threshold (advisory)\n * - \"budget_exceeded\" – token usage crossed 100% threshold (advisory; never kills)\n * - \"task_progress\" – coarse lifecycle event (turn_end, tool start/end) parsed\n * from the child's json event stream, for live UI updates\n */\nexport class SubagentPool extends EventEmitter {\n\tprivate readonly maxConcurrency: number;\n\tprivate readonly executable: string;\n\tprivate readonly prefixArgs: string[];\n\tprivate readonly cwd: string;\n\tprivate readonly env: NodeJS.ProcessEnv;\n\tprivate readonly defaultTokenBudget: number;\n\t/** Non-default skill paths forwarded to every spawned subagent via --skill. */\n\tprivate skillPaths: string[];\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/** Lazily-loaded agent registry (frontmatter definitions) for this pool's cwd. */\n\tprivate registry?: AgentRegistry;\n\t/** Tracks why a task was killed (stalled / timeout / user cancel) before exit handler fires. */\n\tprivate killReasons = new Map<string, \"stalled\" | \"timeout\" | \"cancelled\">();\n\t/** Persistent terminal status map, survives wait_for consumption. */\n\tprivate taskStatus = new Map<string, \"done\" | \"failed\" | \"stalled\" | \"timeout\" | \"cancelled\">();\n\t/** Settings for model category resolution. */\n\tprivate readonly settings?: Settings;\n\t/** Available models used to derive default model-category mappings (snapshot). */\n\tprivate readonly availableModels: readonly Model<Api>[];\n\n\tconstructor(options: SubagentPoolOptions) {\n\t\tsuper();\n\t\tthis.maxConcurrency = options.maxConcurrency ?? 5;\n\t\tthis.executable = options.executable;\n\t\tthis.prefixArgs = options.prefixArgs ?? [];\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.skillPaths = options.skillPaths ? [...options.skillPaths] : [];\n\t\tthis.settings = options.settings;\n\t\tthis.availableModels = options.availableModels ?? [];\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/** Update the non-default skill paths forwarded to new subagents. */\n\tupdateSkillPaths(paths: string[]): void {\n\t\tthis.skillPaths = [...paths];\n\t}\n\n\t/**\n\t * Report external in-process load (e.g. the number of background MCP tools\n\t * currently executing in the parent) to the lifeguard. This widens its\n\t * heartbeat/timeout tolerance so monitored subagents aren't false-positive\n\t * reaped when the parent's event loop is busy with concurrent background work.\n\t */\n\tsetExternalLoad(count: number): void {\n\t\tthis.lifeguard.setExternalLoad(count);\n\t}\n\n\t/** Lazily load the agent registry for this pool's cwd. */\n\tprivate getRegistry(): AgentRegistry {\n\t\tif (!this.registry) {\n\t\t\tthis.registry = loadAgentRegistry({ cwd: this.cwd });\n\t\t}\n\t\treturn this.registry;\n\t}\n\n\t/** Priority value: higher numbers run first. */\n\tprivate priorityOf(agent_type: string): number {\n\t\t// Read-only investigation (explore/plan) often unblocks downstream work, so\n\t\t// it runs ahead of other agents.\n\t\treturn agent_type === \"explore\" || agent_type === \"plan\" ? 2 : 1;\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(\n\t\ttask_id: string,\n\t): \"running\" | \"queued\" | \"done\" | \"failed\" | \"stalled\" | \"timeout\" | \"cancelled\" | \"unknown\" {\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.status === \"cancelled\") return \"cancelled\";\n\t\t\tif (result.ok) return \"done\";\n\t\t\treturn \"failed\";\n\t\t}\n\t\treturn \"unknown\";\n\t}\n\n\t/**\n\t * Cancel a task on the user's behalf (Esc/abort mid-turn). A queued task is\n\t * removed and settles immediately; a running task's whole process tree is\n\t * killed and settles with status \"cancelled\" when its exit is observed\n\t * (unless it already wrote a valid result.json — completed work is honored).\n\t * Returns false for unknown/settled task ids.\n\t */\n\tcancel(task_id: string): boolean {\n\t\tconst queued = this.queue.findIndex((t) => t.task_id === task_id);\n\t\tif (queued !== -1) {\n\t\t\tthis.queue.splice(queued, 1);\n\t\t\tconst result: SubagentResult = {\n\t\t\t\ttask_id,\n\t\t\t\tok: false,\n\t\t\t\tstdout: \"\",\n\t\t\t\tstderr: \"\",\n\t\t\t\texit_code: null,\n\t\t\t\terror: \"cancelled before start\",\n\t\t\t\tstatus: \"cancelled\",\n\t\t\t};\n\t\t\tthis.emit(\"task_cancelled\", { task_id });\n\t\t\tthis.resolveWaiter(task_id, result);\n\t\t\treturn true;\n\t\t}\n\t\tconst slot = this.slots.get(task_id);\n\t\tif (!slot) return false;\n\t\tthis.killReasons.set(task_id, \"cancelled\");\n\t\tif (slot.pid > 0) {\n\t\t\tkillProcessTree(slot.pid);\n\t\t} else if (!slot.process.killed) {\n\t\t\tslot.process.kill(\"SIGKILL\");\n\t\t}\n\t\treturn true;\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 `options.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, options: DispatchOptions = {}): Promise<TaskResult> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(new Error(\"SubagentPool has been disposed\"));\n\t\t}\n\t\tconst begin = this.beginDispatch(task, options);\n\t\tif (begin.handled_inline) {\n\t\t\treturn { handled_inline: true, reason: begin.reason };\n\t\t}\n\t\tconst result = await this.wait_for(begin.task_id);\n\t\treturn {\n\t\t\thandled_inline: false,\n\t\t\ttask_id: begin.task_id,\n\t\t\tagent_type: begin.agent_type,\n\t\t\treason: begin.reason,\n\t\t\tresult,\n\t\t\tduration: Date.now() - begin.startTime,\n\t\t};\n\t}\n\n\t/**\n\t * Fire-and-forget dispatch for background agents. Spawns the subagent and\n\t * returns its handle immediately; the caller polls get_status()/collect().\n\t */\n\tdispatchDetached(\n\t\ttask: string,\n\t\toptions: DispatchOptions = {},\n\t): { handled_inline: boolean; task_id?: string; agent_type?: string; reason?: string } {\n\t\tif (this.disposed) {\n\t\t\tthrow new Error(\"SubagentPool has been disposed\");\n\t\t}\n\t\tconst begin = this.beginDispatch(task, options);\n\t\tif (begin.handled_inline) {\n\t\t\treturn { handled_inline: true, reason: begin.reason };\n\t\t}\n\t\treturn { handled_inline: false, task_id: begin.task_id, agent_type: begin.agent_type, reason: begin.reason };\n\t}\n\n\t/**\n\t * Evaluate, log, and spawn a task without waiting. Shared by dispatch()\n\t * (blocking) and dispatchDetached() (background).\n\t */\n\tprivate beginDispatch(\n\t\ttask: string,\n\t\toptions: DispatchOptions,\n\t):\n\t\t| { handled_inline: true; reason?: string }\n\t\t| { handled_inline: false; task_id: string; agent_type: string; reason?: string; startTime: number } {\n\t\tconst { forceAgent, context, model, provider, sessionFile } = options;\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 = forceAgent ?? \"general-purpose\";\n\t\tconst task_id = options.taskId ?? `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\t\t// Depth of the child about to be spawned (this process's depth + 1). Surfaced\n\t\t// so a delegation tree's nesting is visible in logs without extra tooling.\n\t\tconst childDepth = currentSubagentDepth(this.env) + 1;\n\n\t\t// Pre-dispatch logging. Use stderr: stdout is reserved for the JSON event\n\t\t// stream / TUI render and must not be polluted.\n\t\tconsole.error(\n\t\t\t`[DISPATCH] agent=${agent_type} depth=${childDepth} reason=${reason} complexity=${complexity} task_id=${task_id}`,\n\t\t);\n\t\tthis.writeDispatchLog(task_id, agent_type, reason, complexity, task, childDepth);\n\n\t\tconst poolTask: SubagentPoolTask = {\n\t\t\ttask_id,\n\t\t\tagent_type,\n\t\t\ttask,\n\t\t\tcontext,\n\t\t\tmodel,\n\t\t\tprovider,\n\t\t\tsessionFile,\n\t\t\tcwd: this.cwd,\n\t\t};\n\t\tconst startTime = Date.now();\n\t\tthis.spawn(poolTask);\n\t\treturn { handled_inline: false, task_id, agent_type, reason, startTime };\n\t}\n\n\t/**\n\t * Non-destructively read a completed task's result (for background polling).\n\t * Returns undefined while the task is still running/queued, or if its result\n\t * was already consumed via wait_for().\n\t */\n\tcollect(task_id: string): SubagentResult | undefined {\n\t\treturn this.completed.get(task_id);\n\t}\n\n\t/** Absolute path of the persisted session file for a task. */\n\tgetSessionFile(task_id: string, cwd: string = this.cwd): string {\n\t\treturn join(getDispatchTaskDir(cwd, task_id), \"session.jsonl\");\n\t}\n\n\t/**\n\t * Resume a previously dispatched subagent, continuing its persisted session\n\t * with a follow-up prompt. Recovers the original agent type from its dispatch\n\t * log. Rejects if no resumable session exists for the task.\n\t */\n\tasync resume(\n\t\ttask_id: string,\n\t\tprompt: string,\n\t\toptions: Omit<DispatchOptions, \"forceAgent\" | \"sessionFile\"> = {},\n\t): Promise<TaskResult> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(new Error(\"SubagentPool has been disposed\"));\n\t\t}\n\t\tconst sessionFile = this.getSessionFile(task_id);\n\t\tif (!existsSync(sessionFile)) {\n\t\t\treturn Promise.reject(new Error(`No resumable session for task \"${task_id}\" (expected ${sessionFile}).`));\n\t\t}\n\t\tconst agent_type = this.readDispatchAgentType(task_id) ?? \"general-purpose\";\n\t\treturn this.dispatch(prompt, { ...options, forceAgent: agent_type, sessionFile });\n\t}\n\n\t/** Recover the agent type a task was dispatched with, from its dispatch log. */\n\tprivate readDispatchAgentType(task_id: string): string | undefined {\n\t\tconst path = join(getDispatchTaskDir(this.cwd, task_id), \"dispatch-log.json\");\n\t\tif (!existsSync(path)) return undefined;\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\")) as { agent_type?: string };\n\t\t\treturn typeof parsed.agent_type === \"string\" ? parsed.agent_type : undefined;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\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\tdepth: number,\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\tdepth,\n\t\t\treason,\n\t\t\tcomplexity,\n\t\t\ttask,\n\t\t};\n\t\tconst path = join(getDispatchTaskDir(this.cwd, 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(getDispatchTaskDir(this.cwd, task_id), \"output.json\");\n\t\ttry {\n\t\t\t// Atomic like result.json: background pollers may read output.json while\n\t\t\t// it is being (re)written.\n\t\t\twriteFileAtomicSync(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/**\n\t * Remove a task's dispatch dir after a clean, verified success. Best-effort:\n\t * a cleanup failure must never fail an otherwise successful task.\n\t */\n\tprivate cleanupDispatchDir(task_id: string, cwd: string): void {\n\t\ttry {\n\t\t\trmSync(getDispatchTaskDir(cwd, task_id), { recursive: true, force: true });\n\t\t} catch {\n\t\t\t// Best-effort cleanup\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\t// Kill the whole process group/tree, not just the direct child: a\n\t\t\t// subagent's own children (bash commands, nested subagents) must not\n\t\t\t// outlive the pool.\n\t\t\tif (slot.pid > 0) {\n\t\t\t\tkillProcessTree(slot.pid);\n\t\t\t} else if (!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\t// Persist the child's session so a finished/interrupted subagent can be\n\t\t// resumed later (see resume()). SessionManager.open() creates the file on\n\t\t// first run and continues it on subsequent runs.\n\t\tconst sessionFile = task.sessionFile ?? this.getSessionFile(task.task_id, task.cwd ?? this.cwd);\n\t\tconst args: string[] = [\n\t\t\t...this.prefixArgs,\n\t\t\t\"--mode\",\n\t\t\t\"json\",\n\t\t\t\"--session\",\n\t\t\tsessionFile,\n\t\t\t\"--task-id\",\n\t\t\ttask.task_id,\n\t\t];\n\n\t\t// Prefer the data-driven agent definition from the registry; fall back to the\n\t\t// built-in mode prompt/allowlist for legacy modes not present in the registry.\n\t\tconst def = task.agent_type ? this.getRegistry().get(task.agent_type) : undefined;\n\n\t\tif (task.agent_type) {\n\t\t\tconst systemPrompt = def?.prompt;\n\t\t\tif (systemPrompt) {\n\t\t\t\targs.push(\"--system-prompt\", systemPrompt);\n\t\t\t}\n\n\t\t\t// A `delegate: true` agent may itself dispatch via the Task tool, but only\n\t\t\t// while the child it becomes can still nest (childDepth < cap) — so the\n\t\t\t// deepest permitted level cannot delegate further. Gating here keeps the\n\t\t\t// authorization explicit and bounded by the same cap as the depth guard.\n\t\t\tconst childDepth = currentSubagentDepth(this.env) + 1;\n\t\t\tconst canChildDelegate = def?.delegate === true && childDepth < resolveMaxSubagentDepth(undefined, this.env);\n\n\t\t\t// Tool allowlist comes from the agent definition's frontmatter `tools`\n\t\t\t// field (read-only built-ins declare their own sandbox). When omitted, no\n\t\t\t// --tools is passed and the subagent inherits all parent tools (so the Task\n\t\t\t// tool already survives). A delegating agent with an explicit allowlist must\n\t\t\t// have Task/TaskOutput added, or the child would filter them out.\n\t\t\tconst tools = def?.tools ? [...def.tools] : undefined;\n\t\t\tif (canChildDelegate && tools) {\n\t\t\t\tfor (const t of [\"Task\", \"TaskOutput\"]) {\n\t\t\t\t\tif (!tools.includes(t)) tools.push(t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (tools && tools.length > 0) {\n\t\t\t\targs.push(\"--tools\", tools.join(\",\"));\n\t\t\t}\n\t\t\tif (def?.disallowedTools && def.disallowedTools.length > 0) {\n\t\t\t\targs.push(\"--disallowed-tools\", def.disallowedTools.join(\",\"));\n\t\t\t}\n\n\t\t\t// Propagate subagent enablement so the child registers the Task tool; without\n\t\t\t// this the flag-based enablement would not reach a spawned child.\n\t\t\tif (canChildDelegate) {\n\t\t\t\targs.push(\"--enable-subagents\");\n\t\t\t\t// Scoped delegation: restrict which agent types this child may spawn.\n\t\t\t\tif (def?.delegateTo && def.delegateTo.length > 0) {\n\t\t\t\t\targs.push(\"--delegate-allow\", def.delegateTo.join(\",\"));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Model precedence: a definition's explicit model wins (unless it is the\n\t\t// `inherit` sentinel), otherwise use the caller-provided model. Built-in\n\t\t// agents can retry with the inherited model when their preferred model is\n\t\t// unavailable or quota-limited.\n\t\tconst explicitModel =\n\t\t\t!task.useInheritedModelFallback && def?.model && def.model !== MODEL_INHERIT ? def.model : undefined;\n\t\t// Resolve a model-category reference (fast/standard/capable) to its\n\t\t// configured or derived model id. An unresolvable category yields undefined,\n\t\t// so no `--model` is passed and the child keeps its default model.\n\t\tconst rawModel = explicitModel ?? task.model;\n\t\tconst modelToUse = rawModel ? resolveModelReference(rawModel, this.settings, this.availableModels) : undefined;\n\t\tif (modelToUse) {\n\t\t\targs.push(\"--model\", modelToUse);\n\t\t}\n\t\t// Only pass --provider when the model doesn't already encode the provider\n\t\t// (e.g. \"anthropic/claude-sonnet-4-5\"). When both --model and --provider are\n\t\t// present, --provider wins and filters candidates to that provider, which\n\t\t// breaks models whose IDs contain a provider prefix from a different provider.\n\t\tif (task.provider && (!modelToUse || !modelToUse.includes(\"/\"))) {\n\t\t\targs.push(\"--provider\", task.provider);\n\t\t}\n\n\t\t// Always give subagents a hard turn cap. With the token budget now advisory\n\t\t// (warn-only), this is the guaranteed hard stop for a runaway subagent.\n\t\tconst maxTurns = def?.maxTurns && def.maxTurns > 0 ? def.maxTurns : DEFAULT_SUBAGENT_MAX_TURNS;\n\t\targs.push(\"--max-turns\", String(maxTurns));\n\n\t\t// Forward non-default skill paths so the subagent has access to all parent skills.\n\t\t// Standard discovery locations (~/.hoocode/, .hoocode/, .claude/) are found automatically.\n\t\tfor (const skillPath of this.skillPaths) {\n\t\t\targs.push(\"--skill\", skillPath);\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/**\n\t * Environment for a spawned child.\n\t *\n\t * Stamps the child's depth (parent depth + 1) so its own guard knows where it\n\t * sits in the tree; the tree-wide cap (HOOCODE_SUBAGENT_MAX_DEPTH) is inherited\n\t * via the spread, so at the default cap of 1 the child lands at depth 1 and\n\t * cannot spawn further subagents. Also flags the child to skip MCP server\n\t * connection when its tool allowlist is explicit and MCP-free — connecting\n\t * external servers it can never call is pure boot latency.\n\t */\n\tprivate childSpawnEnv(task: SubagentPoolTask): NodeJS.ProcessEnv {\n\t\tconst env: NodeJS.ProcessEnv = {\n\t\t\t...this.env,\n\t\t\t[SUBAGENT_DEPTH_ENV]: String(currentSubagentDepth(this.env) + 1),\n\t\t};\n\t\t// Use the agent's own frontmatter allowlist (the same source buildArgs passes\n\t\t// via --tools). A delegating child also gets Task/TaskOutput appended there,\n\t\t// but neither is an MCP tool, so the decision is unchanged by that.\n\t\tconst def = task.agent_type ? this.getRegistry().get(task.agent_type) : undefined;\n\t\tif (!toolAllowlistNeedsMcp(def?.tools)) {\n\t\t\tenv[SUBAGENT_SKIP_MCP_ENV] = \"1\";\n\t\t}\n\t\t// A child never defers MCP schemas: if it needs MCP it eager-registers its\n\t\t// allowlisted tools at dispatch so they are immediately callable (spec §2).\n\t\tdelete env[DEFER_MCP_SCHEMAS_ENV];\n\t\treturn env;\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\t// The token budget is advisory: surface telemetry but never kill. The\n\t\t\t// guaranteed hard stop is the per-subagent turn cap (--max-turns); see\n\t\t\t// DEFAULT_SUBAGENT_MAX_TURNS.\n\t\t\tbudget.on(\"budget_exceeded\", (data: { task_id: string; used: number; limit: number }) => {\n\t\t\t\tthis.emit(\"budget_exceeded\", data);\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.childSpawnEnv(task),\n\t\t\t\tshell: false,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t// POSIX: the child leads its own process group so a kill can reach its\n\t\t\t\t// whole tree (see lifeguard/dispose). A single-PID SIGKILL orphans any\n\t\t\t\t// grandchildren the subagent spawned (its own bash commands, nested\n\t\t\t\t// subagents), which kept burning CPU after their parent was reaped.\n\t\t\t\tdetached: process.platform !== \"win32\",\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 detachStdoutReader: (() => void) | undefined;\n\n\t\tproc.stdout?.on(\"data\", (data: Buffer) => {\n\t\t\t// Capture (tail-capped) for diagnostics, and treat any output as a\n\t\t\t// heartbeat: a child busily streaming events is alive even when the\n\t\t\t// parent's starved event loop hasn't parsed its {\"ping\":true} line yet.\n\t\t\t// All *parsing* happens in the single JSONL reader below.\n\t\t\tstdout = appendTail(stdout, data.toString());\n\t\t\tthis.lifeguard.recordHeartbeat(task.task_id);\n\t\t});\n\n\t\t// The one parser on the child's stdout: UTF-8-safe, LF-only framing —\n\t\t// multi-byte characters and large events split across pipe chunks are\n\t\t// reassembled before parsing. Each complete line feeds the token budget\n\t\t// (usage telemetry) and the ping/forward/drop classifier; previously the\n\t\t// budget ran its own naive chunk-splitting parser on the same stream, which\n\t\t// corrupted lines split mid-character. The line buffer is capped so a\n\t\t// runaway writer cannot OOM the parent. Detached when the child exits (see\n\t\t// cleanup below); the reader also self-detaches on stream error/close.\n\t\tif (proc.stdout) {\n\t\t\tdetachStdoutReader = attachJsonlLineReader(\n\t\t\t\tproc.stdout,\n\t\t\t\t(line) => {\n\t\t\t\t\tbudget.processLine(line);\n\t\t\t\t\tconst action = classifySubagentLine(line);\n\t\t\t\t\tif (action.kind === \"heartbeat\") {\n\t\t\t\t\t\tthis.lifeguard.recordHeartbeat(task.task_id);\n\t\t\t\t\t} else if (action.kind === \"progress\") {\n\t\t\t\t\t\tthis.emit(\"task_progress\", {\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\tevent: action.event,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{ maxBuffer: MAX_SUBAGENT_EVENT_LINE_CHARS },\n\t\t\t);\n\t\t}\n\t\tproc.stderr?.on(\"data\", (data: Buffer) => {\n\t\t\tstderr = appendTail(stderr, data.toString());\n\t\t});\n\n\t\t// True when this attempt scheduled another run of the same task id (spawn\n\t\t// retry or inherited-model fallback). The retry reuses the task's TokenBudget\n\t\t// so usage accumulates across attempts; the .finally below must then leave\n\t\t// the budget (and its listeners) alone instead of tearing it down under the\n\t\t// retry's feet.\n\t\tlet retryScheduled = false;\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// A subagent's success is defined by a valid, verified result.json, not by\n\t\t\t\t// its exit code. A child that finished its work and wrote a valid result can\n\t\t\t\t// still be SIGKILLed by the lifeguard before it exits on its own (lingering\n\t\t\t\t// open handles delay a natural exit past the heartbeat threshold), which forces\n\t\t\t\t// exit_code === null. Keying completion off the verified result, not code === 0,\n\t\t\t\t// honors that genuine success instead of discarding it as a false stall.\n\t\t\t\tconst verification = this.verifier.verify(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\t// A well-formed result.json counts as clean completion unless its own\n\t\t\t\t// status field declares failure (e.g. \"failed\" from a provider quota\n\t\t\t\t// error). Without this check the pool would treat a child that wrote a\n\t\t\t\t// valid-but-failed result.json and exited non-zero as a success.\n\t\t\t\tlet cleanlyCompleted = code === 0 || verification.valid;\n\t\t\t\tif (cleanlyCompleted && verification.valid) {\n\t\t\t\t\tconst rd = this.tryReadResultJson(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\t\tif (rd && (rd as Record<string, unknown>).status === \"failed\") {\n\t\t\t\t\t\tcleanlyCompleted = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// If killed (lifeguard reap or user cancel) before producing a valid\n\t\t\t\t// result, honor the kill; a child that already wrote a verified\n\t\t\t\t// result.json completed its work and settles as a success below.\n\t\t\t\tif (killReason !== undefined && !verification.valid) {\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: cleanlyCompleted,\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\t// Advisory telemetry only: exceeding the budget never fails the task.\n\t\t\t\t\tbudget_exceeded: budgetExceeded,\n\t\t\t\t\tstatus: cleanlyCompleted ? \"complete\" : \"failed\",\n\t\t\t\t\tusedInheritedModelFallback: task.useInheritedModelFallback === true,\n\t\t\t\t};\n\n\t\t\t\tif (result.ok) {\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\t// Attach the verified result.json so callers can read the summary\n\t\t\t\t\t// without parsing the raw event stream.\n\t\t\t\t\tresult.result_data = this.tryReadResultJson(task.task_id, task.cwd ?? this.cwd);\n\n\t\t\t\t\t// Clean success: discard the per-task dispatch dir entirely\n\t\t\t\t\t// (session.jsonl, result.json, dispatch-log.json, budget.json). The\n\t\t\t\t\t// in-memory result already carries result_data, so callers lose\n\t\t\t\t\t// nothing. Trade-off: resume() only works for non-successful tasks.\n\t\t\t\t\tthis.cleanupDispatchDir(task.task_id, task.cwd ?? this.cwd);\n\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\tthis.resolveWaiter(task.task_id, result);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Failure path: keep the dispatch dir for debugging and persist output.\n\t\t\t\t// Attach the child's result.json (if any) and derive a concrete failure\n\t\t\t\t// reason so callers see the real cause (e.g. a provider usage/quota\n\t\t\t\t// error) instead of a generic \"subagent failed\".\n\t\t\t\tresult.result_data = this.tryReadResultJson(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\tif (!result.error) {\n\t\t\t\t\tresult.error = this.deriveFailureReason(result);\n\t\t\t\t}\n\t\t\t\tif (this.shouldRetryWithInheritedModel(task, result)) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`[DISPATCH] agent=${task.agent_type} task_id=${task.task_id} preferred model failed; retrying with inherited model`,\n\t\t\t\t\t);\n\t\t\t\t\tthis.cleanupRetryArtifacts(task);\n\t\t\t\t\tretryScheduled = true;\n\t\t\t\t\tthis.queue.unshift({ ...task, useInheritedModelFallback: true });\n\t\t\t\t\treturn;\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: result.error ?? `Exited with code ${code}`,\n\t\t\t\t});\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\t// The retry (started synchronously here) looks the budget up from\n\t\t\t\t\t// this.budgets and reuses it; flag it so the .finally below doesn't\n\t\t\t\t\t// strip the budget's listeners or delete the entry the retry now owns.\n\t\t\t\t\tretryScheduled = true;\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\tusedInheritedModelFallback: task.useInheritedModelFallback === true,\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\t// Runs on success AND failure: the stdout reader used to be detached only\n\t\t\t\t// on the success path, leaking listeners (and their buffers) whenever a\n\t\t\t\t// child failed or the wait rejected.\n\t\t\t\tdetachStdoutReader?.();\n\t\t\t\tproc.stdout?.removeAllListeners(\"data\");\n\t\t\t\tproc.stderr?.removeAllListeners(\"data\");\n\t\t\t\tproc.stdout?.destroy();\n\t\t\t\tproc.stderr?.destroy();\n\t\t\t\tif (!retryScheduled) {\n\t\t\t\t\tbudget.removeAllListeners();\n\t\t\t\t\tthis.budgets.delete(task.task_id);\n\t\t\t\t}\n\t\t\t\tthis.pull();\n\t\t\t});\n\t}\n\n\t/** Whether a failed built-in subagent should be retried with `model: inherit`. */\n\tprivate shouldRetryWithInheritedModel(task: SubagentPoolTask, result: SubagentResult): boolean {\n\t\tif (task.useInheritedModelFallback) return false;\n\t\tif (task.sessionFile) return false;\n\t\t// Only the parent model is required: the provider may be unset when the\n\t\t// harness routes through a gateway. The retry inherits the parent model and\n\t\t// lets the child resolve the provider from its own default when none was threaded through.\n\t\tif (!task.model) return false;\n\n\t\tconst def = task.agent_type ? this.getRegistry().get(task.agent_type) : undefined;\n\t\t// Built-in agents always inherit; project agents may pin an explicit model in\n\t\t// frontmatter, so let them fall back too when that model is rejected.\n\t\tif (def?.source !== \"builtin\" && def?.source !== \"project\") return false;\n\t\tif (!def.model || def.model === MODEL_INHERIT) return false;\n\n\t\treturn this.isInheritedModelFallbackError(result);\n\t}\n\n\t/** Detect provider/model failures where inheriting the parent model can recover. */\n\tprivate isInheritedModelFallbackError(result: SubagentResult): boolean {\n\t\tconst text = [result.error, result.stderr, JSON.stringify(result.result_data ?? {})]\n\t\t\t.filter((part): part is string => typeof part === \"string\" && part.length > 0)\n\t\t\t.join(\"\\n\");\n\n\t\treturn INHERITED_MODEL_FALLBACK_ERROR.test(text);\n\t}\n\n\t/** Remove failed attempt artifacts before rerunning the same task id. */\n\tprivate cleanupRetryArtifacts(task: SubagentPoolTask): void {\n\t\tconst cwd = task.cwd ?? this.cwd;\n\t\tconst taskDir = getDispatchTaskDir(cwd, task.task_id);\n\t\tconst sessionFile = task.sessionFile ?? this.getSessionFile(task.task_id, cwd);\n\t\ttry {\n\t\t\trmSync(sessionFile, { force: true });\n\t\t\trmSync(join(taskDir, \"result.json\"), { force: true });\n\t\t\trmSync(join(taskDir, \"output.json\"), { force: true });\n\t\t} catch {\n\t\t\t// Best-effort cleanup; retry can still proceed with existing artifacts.\n\t\t}\n\t}\n\n\t/**\n\t * Best-effort concrete failure reason for a non-zero-exit subagent. Prefers\n\t * the child's result.json summary (which carries the provider/model error\n\t * message on failure), then the tail of stderr, then the exit code.\n\t */\n\tprivate deriveFailureReason(result: SubagentResult): string {\n\t\tconst summary = (result.result_data as { summary?: string } | undefined)?.summary?.trim();\n\t\tif (summary) {\n\t\t\treturn summary;\n\t\t}\n\t\tconst stderrTail = result.stderr\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => line.trim())\n\t\t\t.filter((line) => line.length > 0)\n\t\t\t.slice(-5)\n\t\t\t.join(\"\\n\");\n\t\tif (stderrTail) {\n\t\t\treturn stderrTail;\n\t\t}\n\t\treturn `Exited with code ${result.exit_code}`;\n\t}\n\n\tprivate tryReadResultJson(task_id: string, cwd: string): Record<string, unknown> | undefined {\n\t\tconst path = join(getDispatchTaskDir(cwd, 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.status === \"cancelled\") this.taskStatus.set(task_id, \"cancelled\");\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"]}
|
|
@@ -550,7 +550,11 @@ export class SubagentPool extends EventEmitter {
|
|
|
550
550
|
if (modelToUse) {
|
|
551
551
|
args.push("--model", modelToUse);
|
|
552
552
|
}
|
|
553
|
-
|
|
553
|
+
// Only pass --provider when the model doesn't already encode the provider
|
|
554
|
+
// (e.g. "anthropic/claude-sonnet-4-5"). When both --model and --provider are
|
|
555
|
+
// present, --provider wins and filters candidates to that provider, which
|
|
556
|
+
// breaks models whose IDs contain a provider prefix from a different provider.
|
|
557
|
+
if (task.provider && (!modelToUse || !modelToUse.includes("/"))) {
|
|
554
558
|
args.push("--provider", task.provider);
|
|
555
559
|
}
|
|
556
560
|
// Always give subagents a hard turn cap. With the token budget now advisory
|
|
@@ -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, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport { getDispatchTaskDir } from \"../config.js\";\nimport { attachJsonlLineReader } from \"../modes/rpc/jsonl.js\";\nimport { writeFileAtomicSync } from \"../utils/atomic-file.js\";\nimport { waitForChildProcess } from \"../utils/child-process.js\";\nimport { killProcessTree } from \"../utils/shell.js\";\nimport { MODEL_INHERIT } from \"./agent-frontmatter.js\";\nimport { type AgentRegistry, loadAgentRegistry } from \"./agent-registry.js\";\nimport { DispatchEvaluator } from \"./dispatch-evaluator.js\";\nimport { SubagentLifeguard } from \"./lifeguard.js\";\nimport { resolveModelReference } from \"./model-categories.js\";\nimport { OutputVerifier } from \"./output-verifier.js\";\nimport type { Settings } from \"./settings-manager.js\";\nimport {\n\tcurrentSubagentDepth,\n\tDEFER_MCP_SCHEMAS_ENV,\n\tresolveMaxSubagentDepth,\n\tSUBAGENT_DEPTH_ENV,\n\tSUBAGENT_SKIP_MCP_ENV,\n\ttoolAllowlistNeedsMcp,\n} from \"./subagent-depth.js\";\nimport { SUBAGENT_PROGRESS_EVENTS } from \"./subagent-events.js\";\nimport { TokenBudget } from \"./token-budget.js\";\n\n/**\n * Provider/model failure signatures where inheriting the parent model can\n * recover. Compiled once at module load instead of on every error check.\n */\nconst INHERITED_MODEL_FALLBACK_ERROR =\n\t/usage[_\\s-]?limit|subscription|quota|rate.?limit|too many requests|429|insufficient|out of credit|credit balance|billing|payment required|402|model[^\\n]*(not found|unavailable|not available|not supported|does not exist|invalid|unsupported)|no api key|no auth configured|authentication|unauthorized|forbidden|permission/i;\n\nexport interface SubagentPoolTask {\n\ttask_id: string;\n\tagent_type: string;\n\ttask: string;\n\tcontext?: string;\n\ttoken_budget?: number;\n\tcwd?: string;\n\tmodel?: string;\n\tprovider?: string;\n\t/**\n\t * Explicit session file for the child to persist/continue. When omitted the\n\t * child uses its own dispatch dir (`<dispatch>/<task_id>/session.jsonl`).\n\t * Resume reuses the original task's session file to continue the transcript.\n\t */\n\tsessionFile?: string;\n\t/** Internal: retry using the caller's model when a built-in agent's preferred model fails. */\n\tuseInheritedModelFallback?: boolean;\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\" | \"cancelled\";\n\t/** Parsed result.json content when available (e.g. on partial completion). */\n\tresult_data?: Record<string, unknown>;\n\t/** True when this run used the inherited-model fallback (preferred model failed first). */\n\tusedInheritedModelFallback?: boolean;\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 DispatchOptions {\n\t/** Skip evaluation and force this agent type (user/explicit override).\n\t * Accepts any registry-defined agent name, not just the built-in modes. */\n\tforceAgent?: string;\n\t/** Context distilled from the calling agent, passed to the subagent. */\n\tcontext?: string;\n\t/** Model id for the subagent (defaults to the child's configured default). */\n\tmodel?: string;\n\t/** Provider for the subagent. */\n\tprovider?: string;\n\t/** Explicit session file to persist/continue (used by resume). */\n\tsessionFile?: string;\n\t/** Caller-supplied task id. Defaults to a generated `dispatch-…` id. Lets a\n\t * caller register liveness/inbox state under the id before dispatch resolves. */\n\ttaskId?: string;\n}\n\nexport interface SubagentPoolOptions {\n\t/** Path to the hoocode executable (or the runtime, e.g. node, when prefixArgs is set). */\n\texecutable: string;\n\t/** Args inserted before task args (e.g. the CLI entry script for node/tsx). */\n\tprefixArgs?: 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\t/**\n\t * Non-default skill paths to forward to every spawned subagent via --skill.\n\t * Subagents auto-discover skills from standard locations; only paths that\n\t * won't be found by default discovery need to be forwarded here.\n\t */\n\tskillPaths?: string[];\n\t/** Settings for model category resolution. */\n\tsettings?: Settings;\n\t/**\n\t * Available/configured models used to derive default model-category mappings\n\t * when a tier is not explicitly set in `settings.modelCategories`. Snapshotted\n\t * at pool creation, mirroring how `settings` is captured.\n\t */\n\tavailableModels?: readonly Model<Api>[];\n}\n\n/**\n * Default hard cap on assistant turns for a spawned subagent when its definition\n * does not set `maxTurns`. The token budget is advisory (it warns but never\n * kills), so this turn cap is the guaranteed hard stop for every subagent.\n */\nexport const DEFAULT_SUBAGENT_MAX_TURNS = 50;\n\n/**\n * AgentSession event `type`s forwarded from a subagent's json event stream as\n * `task_progress` events. Deliberately coarse; the child now also filters its\n * stdout to this set plus `message_end` at the source (see\n * SUBAGENT_STDOUT_EVENT_TYPES), so the per-delta firehose no longer crosses the\n * pipe. Re-exported from the shared module so the child emitter and this\n * consumer stay in lockstep.\n */\nexport const FORWARDED_SUBAGENT_EVENTS = SUBAGENT_PROGRESS_EVENTS;\n\n/**\n * Cap on the captured stdout/stderr text kept per task (tail-truncated). The\n * capture exists for diagnostics (failure reasons, output.json), so keeping the\n * most recent tail is enough; without a cap a chatty child could grow the\n * parent's memory without bound across a long swarm.\n */\nexport const MAX_CAPTURED_STREAM_CHARS = 256 * 1024;\n\n/**\n * Cap on a single un-terminated JSONL line buffered from a child's stdout. The\n * forwarded events are small; anything approaching this is a runaway writer,\n * and the reader drops the line rather than buffering toward OOM.\n */\nexport const MAX_SUBAGENT_EVENT_LINE_CHARS = 8 * 1024 * 1024;\n\n/** Append a chunk to a capped capture buffer, keeping the most recent tail. */\nfunction appendTail(current: string, chunk: string, cap: number = MAX_CAPTURED_STREAM_CHARS): string {\n\tconst next = current + chunk;\n\treturn next.length > cap ? next.slice(next.length - cap) : next;\n}\n\n/** The action the pool should take for one JSONL line from a subagent's stdout. */\nexport type SubagentStdoutLine =\n\t| { kind: \"heartbeat\" }\n\t| { kind: \"progress\"; event: Record<string, unknown> }\n\t| { kind: \"ignore\" };\n\n/**\n * Classify one JSONL line from a subagent's stdout into the action to take.\n * Pure (no side effects) so the ping/forward/drop policy is unit-testable without\n * spawning a child. Line framing — UTF-8-safe reassembly of chunks split mid-line\n * — is handled upstream by attachJsonlLineReader; this only sees complete lines.\n */\nexport function classifySubagentLine(line: string): SubagentStdoutLine {\n\tconst trimmed = line.trim();\n\tif (!trimmed.startsWith(\"{\")) return { kind: \"ignore\" };\n\tlet parsed: Record<string, unknown>;\n\ttry {\n\t\tparsed = JSON.parse(trimmed) as Record<string, unknown>;\n\t} catch {\n\t\treturn { kind: \"ignore\" };\n\t}\n\tif (parsed.ping === true) return { kind: \"heartbeat\" };\n\tif (typeof parsed.type === \"string\" && FORWARDED_SUBAGENT_EVENTS.has(parsed.type)) {\n\t\treturn { kind: \"progress\", event: parsed };\n\t}\n\treturn { kind: \"ignore\" };\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 past the load-scaled threshold (60s base,\n * widened under concurrency/event-loop lag), process SIGKILLed\n * - \"task_timeout\" – hard timeout exceeded, process was SIGKILLed\n * - \"task_cancelled\" – user-initiated cancel (see cancel()); process tree killed\n * - \"budget_warning\" – token usage crossed 80% threshold (advisory)\n * - \"budget_exceeded\" – token usage crossed 100% threshold (advisory; never kills)\n * - \"task_progress\" – coarse lifecycle event (turn_end, tool start/end) parsed\n * from the child's json event stream, for live UI updates\n */\nexport class SubagentPool extends EventEmitter {\n\tprivate readonly maxConcurrency: number;\n\tprivate readonly executable: string;\n\tprivate readonly prefixArgs: string[];\n\tprivate readonly cwd: string;\n\tprivate readonly env: NodeJS.ProcessEnv;\n\tprivate readonly defaultTokenBudget: number;\n\t/** Non-default skill paths forwarded to every spawned subagent via --skill. */\n\tprivate skillPaths: string[];\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/** Lazily-loaded agent registry (frontmatter definitions) for this pool's cwd. */\n\tprivate registry?: AgentRegistry;\n\t/** Tracks why a task was killed (stalled / timeout / user cancel) before exit handler fires. */\n\tprivate killReasons = new Map<string, \"stalled\" | \"timeout\" | \"cancelled\">();\n\t/** Persistent terminal status map, survives wait_for consumption. */\n\tprivate taskStatus = new Map<string, \"done\" | \"failed\" | \"stalled\" | \"timeout\" | \"cancelled\">();\n\t/** Settings for model category resolution. */\n\tprivate readonly settings?: Settings;\n\t/** Available models used to derive default model-category mappings (snapshot). */\n\tprivate readonly availableModels: readonly Model<Api>[];\n\n\tconstructor(options: SubagentPoolOptions) {\n\t\tsuper();\n\t\tthis.maxConcurrency = options.maxConcurrency ?? 5;\n\t\tthis.executable = options.executable;\n\t\tthis.prefixArgs = options.prefixArgs ?? [];\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.skillPaths = options.skillPaths ? [...options.skillPaths] : [];\n\t\tthis.settings = options.settings;\n\t\tthis.availableModels = options.availableModels ?? [];\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/** Update the non-default skill paths forwarded to new subagents. */\n\tupdateSkillPaths(paths: string[]): void {\n\t\tthis.skillPaths = [...paths];\n\t}\n\n\t/**\n\t * Report external in-process load (e.g. the number of background MCP tools\n\t * currently executing in the parent) to the lifeguard. This widens its\n\t * heartbeat/timeout tolerance so monitored subagents aren't false-positive\n\t * reaped when the parent's event loop is busy with concurrent background work.\n\t */\n\tsetExternalLoad(count: number): void {\n\t\tthis.lifeguard.setExternalLoad(count);\n\t}\n\n\t/** Lazily load the agent registry for this pool's cwd. */\n\tprivate getRegistry(): AgentRegistry {\n\t\tif (!this.registry) {\n\t\t\tthis.registry = loadAgentRegistry({ cwd: this.cwd });\n\t\t}\n\t\treturn this.registry;\n\t}\n\n\t/** Priority value: higher numbers run first. */\n\tprivate priorityOf(agent_type: string): number {\n\t\t// Read-only investigation (explore/plan) often unblocks downstream work, so\n\t\t// it runs ahead of other agents.\n\t\treturn agent_type === \"explore\" || agent_type === \"plan\" ? 2 : 1;\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(\n\t\ttask_id: string,\n\t): \"running\" | \"queued\" | \"done\" | \"failed\" | \"stalled\" | \"timeout\" | \"cancelled\" | \"unknown\" {\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.status === \"cancelled\") return \"cancelled\";\n\t\t\tif (result.ok) return \"done\";\n\t\t\treturn \"failed\";\n\t\t}\n\t\treturn \"unknown\";\n\t}\n\n\t/**\n\t * Cancel a task on the user's behalf (Esc/abort mid-turn). A queued task is\n\t * removed and settles immediately; a running task's whole process tree is\n\t * killed and settles with status \"cancelled\" when its exit is observed\n\t * (unless it already wrote a valid result.json — completed work is honored).\n\t * Returns false for unknown/settled task ids.\n\t */\n\tcancel(task_id: string): boolean {\n\t\tconst queued = this.queue.findIndex((t) => t.task_id === task_id);\n\t\tif (queued !== -1) {\n\t\t\tthis.queue.splice(queued, 1);\n\t\t\tconst result: SubagentResult = {\n\t\t\t\ttask_id,\n\t\t\t\tok: false,\n\t\t\t\tstdout: \"\",\n\t\t\t\tstderr: \"\",\n\t\t\t\texit_code: null,\n\t\t\t\terror: \"cancelled before start\",\n\t\t\t\tstatus: \"cancelled\",\n\t\t\t};\n\t\t\tthis.emit(\"task_cancelled\", { task_id });\n\t\t\tthis.resolveWaiter(task_id, result);\n\t\t\treturn true;\n\t\t}\n\t\tconst slot = this.slots.get(task_id);\n\t\tif (!slot) return false;\n\t\tthis.killReasons.set(task_id, \"cancelled\");\n\t\tif (slot.pid > 0) {\n\t\t\tkillProcessTree(slot.pid);\n\t\t} else if (!slot.process.killed) {\n\t\t\tslot.process.kill(\"SIGKILL\");\n\t\t}\n\t\treturn true;\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 `options.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, options: DispatchOptions = {}): Promise<TaskResult> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(new Error(\"SubagentPool has been disposed\"));\n\t\t}\n\t\tconst begin = this.beginDispatch(task, options);\n\t\tif (begin.handled_inline) {\n\t\t\treturn { handled_inline: true, reason: begin.reason };\n\t\t}\n\t\tconst result = await this.wait_for(begin.task_id);\n\t\treturn {\n\t\t\thandled_inline: false,\n\t\t\ttask_id: begin.task_id,\n\t\t\tagent_type: begin.agent_type,\n\t\t\treason: begin.reason,\n\t\t\tresult,\n\t\t\tduration: Date.now() - begin.startTime,\n\t\t};\n\t}\n\n\t/**\n\t * Fire-and-forget dispatch for background agents. Spawns the subagent and\n\t * returns its handle immediately; the caller polls get_status()/collect().\n\t */\n\tdispatchDetached(\n\t\ttask: string,\n\t\toptions: DispatchOptions = {},\n\t): { handled_inline: boolean; task_id?: string; agent_type?: string; reason?: string } {\n\t\tif (this.disposed) {\n\t\t\tthrow new Error(\"SubagentPool has been disposed\");\n\t\t}\n\t\tconst begin = this.beginDispatch(task, options);\n\t\tif (begin.handled_inline) {\n\t\t\treturn { handled_inline: true, reason: begin.reason };\n\t\t}\n\t\treturn { handled_inline: false, task_id: begin.task_id, agent_type: begin.agent_type, reason: begin.reason };\n\t}\n\n\t/**\n\t * Evaluate, log, and spawn a task without waiting. Shared by dispatch()\n\t * (blocking) and dispatchDetached() (background).\n\t */\n\tprivate beginDispatch(\n\t\ttask: string,\n\t\toptions: DispatchOptions,\n\t):\n\t\t| { handled_inline: true; reason?: string }\n\t\t| { handled_inline: false; task_id: string; agent_type: string; reason?: string; startTime: number } {\n\t\tconst { forceAgent, context, model, provider, sessionFile } = options;\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 = forceAgent ?? \"general-purpose\";\n\t\tconst task_id = options.taskId ?? `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\t\t// Depth of the child about to be spawned (this process's depth + 1). Surfaced\n\t\t// so a delegation tree's nesting is visible in logs without extra tooling.\n\t\tconst childDepth = currentSubagentDepth(this.env) + 1;\n\n\t\t// Pre-dispatch logging. Use stderr: stdout is reserved for the JSON event\n\t\t// stream / TUI render and must not be polluted.\n\t\tconsole.error(\n\t\t\t`[DISPATCH] agent=${agent_type} depth=${childDepth} reason=${reason} complexity=${complexity} task_id=${task_id}`,\n\t\t);\n\t\tthis.writeDispatchLog(task_id, agent_type, reason, complexity, task, childDepth);\n\n\t\tconst poolTask: SubagentPoolTask = {\n\t\t\ttask_id,\n\t\t\tagent_type,\n\t\t\ttask,\n\t\t\tcontext,\n\t\t\tmodel,\n\t\t\tprovider,\n\t\t\tsessionFile,\n\t\t\tcwd: this.cwd,\n\t\t};\n\t\tconst startTime = Date.now();\n\t\tthis.spawn(poolTask);\n\t\treturn { handled_inline: false, task_id, agent_type, reason, startTime };\n\t}\n\n\t/**\n\t * Non-destructively read a completed task's result (for background polling).\n\t * Returns undefined while the task is still running/queued, or if its result\n\t * was already consumed via wait_for().\n\t */\n\tcollect(task_id: string): SubagentResult | undefined {\n\t\treturn this.completed.get(task_id);\n\t}\n\n\t/** Absolute path of the persisted session file for a task. */\n\tgetSessionFile(task_id: string, cwd: string = this.cwd): string {\n\t\treturn join(getDispatchTaskDir(cwd, task_id), \"session.jsonl\");\n\t}\n\n\t/**\n\t * Resume a previously dispatched subagent, continuing its persisted session\n\t * with a follow-up prompt. Recovers the original agent type from its dispatch\n\t * log. Rejects if no resumable session exists for the task.\n\t */\n\tasync resume(\n\t\ttask_id: string,\n\t\tprompt: string,\n\t\toptions: Omit<DispatchOptions, \"forceAgent\" | \"sessionFile\"> = {},\n\t): Promise<TaskResult> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(new Error(\"SubagentPool has been disposed\"));\n\t\t}\n\t\tconst sessionFile = this.getSessionFile(task_id);\n\t\tif (!existsSync(sessionFile)) {\n\t\t\treturn Promise.reject(new Error(`No resumable session for task \"${task_id}\" (expected ${sessionFile}).`));\n\t\t}\n\t\tconst agent_type = this.readDispatchAgentType(task_id) ?? \"general-purpose\";\n\t\treturn this.dispatch(prompt, { ...options, forceAgent: agent_type, sessionFile });\n\t}\n\n\t/** Recover the agent type a task was dispatched with, from its dispatch log. */\n\tprivate readDispatchAgentType(task_id: string): string | undefined {\n\t\tconst path = join(getDispatchTaskDir(this.cwd, task_id), \"dispatch-log.json\");\n\t\tif (!existsSync(path)) return undefined;\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\")) as { agent_type?: string };\n\t\t\treturn typeof parsed.agent_type === \"string\" ? parsed.agent_type : undefined;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\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\tdepth: number,\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\tdepth,\n\t\t\treason,\n\t\t\tcomplexity,\n\t\t\ttask,\n\t\t};\n\t\tconst path = join(getDispatchTaskDir(this.cwd, 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(getDispatchTaskDir(this.cwd, task_id), \"output.json\");\n\t\ttry {\n\t\t\t// Atomic like result.json: background pollers may read output.json while\n\t\t\t// it is being (re)written.\n\t\t\twriteFileAtomicSync(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/**\n\t * Remove a task's dispatch dir after a clean, verified success. Best-effort:\n\t * a cleanup failure must never fail an otherwise successful task.\n\t */\n\tprivate cleanupDispatchDir(task_id: string, cwd: string): void {\n\t\ttry {\n\t\t\trmSync(getDispatchTaskDir(cwd, task_id), { recursive: true, force: true });\n\t\t} catch {\n\t\t\t// Best-effort cleanup\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\t// Kill the whole process group/tree, not just the direct child: a\n\t\t\t// subagent's own children (bash commands, nested subagents) must not\n\t\t\t// outlive the pool.\n\t\t\tif (slot.pid > 0) {\n\t\t\t\tkillProcessTree(slot.pid);\n\t\t\t} else if (!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\t// Persist the child's session so a finished/interrupted subagent can be\n\t\t// resumed later (see resume()). SessionManager.open() creates the file on\n\t\t// first run and continues it on subsequent runs.\n\t\tconst sessionFile = task.sessionFile ?? this.getSessionFile(task.task_id, task.cwd ?? this.cwd);\n\t\tconst args: string[] = [\n\t\t\t...this.prefixArgs,\n\t\t\t\"--mode\",\n\t\t\t\"json\",\n\t\t\t\"--session\",\n\t\t\tsessionFile,\n\t\t\t\"--task-id\",\n\t\t\ttask.task_id,\n\t\t];\n\n\t\t// Prefer the data-driven agent definition from the registry; fall back to the\n\t\t// built-in mode prompt/allowlist for legacy modes not present in the registry.\n\t\tconst def = task.agent_type ? this.getRegistry().get(task.agent_type) : undefined;\n\n\t\tif (task.agent_type) {\n\t\t\tconst systemPrompt = def?.prompt;\n\t\t\tif (systemPrompt) {\n\t\t\t\targs.push(\"--system-prompt\", systemPrompt);\n\t\t\t}\n\n\t\t\t// A `delegate: true` agent may itself dispatch via the Task tool, but only\n\t\t\t// while the child it becomes can still nest (childDepth < cap) — so the\n\t\t\t// deepest permitted level cannot delegate further. Gating here keeps the\n\t\t\t// authorization explicit and bounded by the same cap as the depth guard.\n\t\t\tconst childDepth = currentSubagentDepth(this.env) + 1;\n\t\t\tconst canChildDelegate = def?.delegate === true && childDepth < resolveMaxSubagentDepth(undefined, this.env);\n\n\t\t\t// Tool allowlist comes from the agent definition's frontmatter `tools`\n\t\t\t// field (read-only built-ins declare their own sandbox). When omitted, no\n\t\t\t// --tools is passed and the subagent inherits all parent tools (so the Task\n\t\t\t// tool already survives). A delegating agent with an explicit allowlist must\n\t\t\t// have Task/TaskOutput added, or the child would filter them out.\n\t\t\tconst tools = def?.tools ? [...def.tools] : undefined;\n\t\t\tif (canChildDelegate && tools) {\n\t\t\t\tfor (const t of [\"Task\", \"TaskOutput\"]) {\n\t\t\t\t\tif (!tools.includes(t)) tools.push(t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (tools && tools.length > 0) {\n\t\t\t\targs.push(\"--tools\", tools.join(\",\"));\n\t\t\t}\n\t\t\tif (def?.disallowedTools && def.disallowedTools.length > 0) {\n\t\t\t\targs.push(\"--disallowed-tools\", def.disallowedTools.join(\",\"));\n\t\t\t}\n\n\t\t\t// Propagate subagent enablement so the child registers the Task tool; without\n\t\t\t// this the flag-based enablement would not reach a spawned child.\n\t\t\tif (canChildDelegate) {\n\t\t\t\targs.push(\"--enable-subagents\");\n\t\t\t\t// Scoped delegation: restrict which agent types this child may spawn.\n\t\t\t\tif (def?.delegateTo && def.delegateTo.length > 0) {\n\t\t\t\t\targs.push(\"--delegate-allow\", def.delegateTo.join(\",\"));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Model precedence: a definition's explicit model wins (unless it is the\n\t\t// `inherit` sentinel), otherwise use the caller-provided model. Built-in\n\t\t// agents can retry with the inherited model when their preferred model is\n\t\t// unavailable or quota-limited.\n\t\tconst explicitModel =\n\t\t\t!task.useInheritedModelFallback && def?.model && def.model !== MODEL_INHERIT ? def.model : undefined;\n\t\t// Resolve a model-category reference (fast/standard/capable) to its\n\t\t// configured or derived model id. An unresolvable category yields undefined,\n\t\t// so no `--model` is passed and the child keeps its default model.\n\t\tconst rawModel = explicitModel ?? task.model;\n\t\tconst modelToUse = rawModel ? resolveModelReference(rawModel, this.settings, this.availableModels) : undefined;\n\t\tif (modelToUse) {\n\t\t\targs.push(\"--model\", modelToUse);\n\t\t}\n\t\tif (task.provider) {\n\t\t\targs.push(\"--provider\", task.provider);\n\t\t}\n\n\t\t// Always give subagents a hard turn cap. With the token budget now advisory\n\t\t// (warn-only), this is the guaranteed hard stop for a runaway subagent.\n\t\tconst maxTurns = def?.maxTurns && def.maxTurns > 0 ? def.maxTurns : DEFAULT_SUBAGENT_MAX_TURNS;\n\t\targs.push(\"--max-turns\", String(maxTurns));\n\n\t\t// Forward non-default skill paths so the subagent has access to all parent skills.\n\t\t// Standard discovery locations (~/.hoocode/, .hoocode/, .claude/) are found automatically.\n\t\tfor (const skillPath of this.skillPaths) {\n\t\t\targs.push(\"--skill\", skillPath);\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/**\n\t * Environment for a spawned child.\n\t *\n\t * Stamps the child's depth (parent depth + 1) so its own guard knows where it\n\t * sits in the tree; the tree-wide cap (HOOCODE_SUBAGENT_MAX_DEPTH) is inherited\n\t * via the spread, so at the default cap of 1 the child lands at depth 1 and\n\t * cannot spawn further subagents. Also flags the child to skip MCP server\n\t * connection when its tool allowlist is explicit and MCP-free — connecting\n\t * external servers it can never call is pure boot latency.\n\t */\n\tprivate childSpawnEnv(task: SubagentPoolTask): NodeJS.ProcessEnv {\n\t\tconst env: NodeJS.ProcessEnv = {\n\t\t\t...this.env,\n\t\t\t[SUBAGENT_DEPTH_ENV]: String(currentSubagentDepth(this.env) + 1),\n\t\t};\n\t\t// Use the agent's own frontmatter allowlist (the same source buildArgs passes\n\t\t// via --tools). A delegating child also gets Task/TaskOutput appended there,\n\t\t// but neither is an MCP tool, so the decision is unchanged by that.\n\t\tconst def = task.agent_type ? this.getRegistry().get(task.agent_type) : undefined;\n\t\tif (!toolAllowlistNeedsMcp(def?.tools)) {\n\t\t\tenv[SUBAGENT_SKIP_MCP_ENV] = \"1\";\n\t\t}\n\t\t// A child never defers MCP schemas: if it needs MCP it eager-registers its\n\t\t// allowlisted tools at dispatch so they are immediately callable (spec §2).\n\t\tdelete env[DEFER_MCP_SCHEMAS_ENV];\n\t\treturn env;\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\t// The token budget is advisory: surface telemetry but never kill. The\n\t\t\t// guaranteed hard stop is the per-subagent turn cap (--max-turns); see\n\t\t\t// DEFAULT_SUBAGENT_MAX_TURNS.\n\t\t\tbudget.on(\"budget_exceeded\", (data: { task_id: string; used: number; limit: number }) => {\n\t\t\t\tthis.emit(\"budget_exceeded\", data);\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.childSpawnEnv(task),\n\t\t\t\tshell: false,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t// POSIX: the child leads its own process group so a kill can reach its\n\t\t\t\t// whole tree (see lifeguard/dispose). A single-PID SIGKILL orphans any\n\t\t\t\t// grandchildren the subagent spawned (its own bash commands, nested\n\t\t\t\t// subagents), which kept burning CPU after their parent was reaped.\n\t\t\t\tdetached: process.platform !== \"win32\",\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 detachStdoutReader: (() => void) | undefined;\n\n\t\tproc.stdout?.on(\"data\", (data: Buffer) => {\n\t\t\t// Capture (tail-capped) for diagnostics, and treat any output as a\n\t\t\t// heartbeat: a child busily streaming events is alive even when the\n\t\t\t// parent's starved event loop hasn't parsed its {\"ping\":true} line yet.\n\t\t\t// All *parsing* happens in the single JSONL reader below.\n\t\t\tstdout = appendTail(stdout, data.toString());\n\t\t\tthis.lifeguard.recordHeartbeat(task.task_id);\n\t\t});\n\n\t\t// The one parser on the child's stdout: UTF-8-safe, LF-only framing —\n\t\t// multi-byte characters and large events split across pipe chunks are\n\t\t// reassembled before parsing. Each complete line feeds the token budget\n\t\t// (usage telemetry) and the ping/forward/drop classifier; previously the\n\t\t// budget ran its own naive chunk-splitting parser on the same stream, which\n\t\t// corrupted lines split mid-character. The line buffer is capped so a\n\t\t// runaway writer cannot OOM the parent. Detached when the child exits (see\n\t\t// cleanup below); the reader also self-detaches on stream error/close.\n\t\tif (proc.stdout) {\n\t\t\tdetachStdoutReader = attachJsonlLineReader(\n\t\t\t\tproc.stdout,\n\t\t\t\t(line) => {\n\t\t\t\t\tbudget.processLine(line);\n\t\t\t\t\tconst action = classifySubagentLine(line);\n\t\t\t\t\tif (action.kind === \"heartbeat\") {\n\t\t\t\t\t\tthis.lifeguard.recordHeartbeat(task.task_id);\n\t\t\t\t\t} else if (action.kind === \"progress\") {\n\t\t\t\t\t\tthis.emit(\"task_progress\", {\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\tevent: action.event,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{ maxBuffer: MAX_SUBAGENT_EVENT_LINE_CHARS },\n\t\t\t);\n\t\t}\n\t\tproc.stderr?.on(\"data\", (data: Buffer) => {\n\t\t\tstderr = appendTail(stderr, data.toString());\n\t\t});\n\n\t\t// True when this attempt scheduled another run of the same task id (spawn\n\t\t// retry or inherited-model fallback). The retry reuses the task's TokenBudget\n\t\t// so usage accumulates across attempts; the .finally below must then leave\n\t\t// the budget (and its listeners) alone instead of tearing it down under the\n\t\t// retry's feet.\n\t\tlet retryScheduled = false;\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// A subagent's success is defined by a valid, verified result.json, not by\n\t\t\t\t// its exit code. A child that finished its work and wrote a valid result can\n\t\t\t\t// still be SIGKILLed by the lifeguard before it exits on its own (lingering\n\t\t\t\t// open handles delay a natural exit past the heartbeat threshold), which forces\n\t\t\t\t// exit_code === null. Keying completion off the verified result, not code === 0,\n\t\t\t\t// honors that genuine success instead of discarding it as a false stall.\n\t\t\t\tconst verification = this.verifier.verify(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\t// A well-formed result.json counts as clean completion unless its own\n\t\t\t\t// status field declares failure (e.g. \"failed\" from a provider quota\n\t\t\t\t// error). Without this check the pool would treat a child that wrote a\n\t\t\t\t// valid-but-failed result.json and exited non-zero as a success.\n\t\t\t\tlet cleanlyCompleted = code === 0 || verification.valid;\n\t\t\t\tif (cleanlyCompleted && verification.valid) {\n\t\t\t\t\tconst rd = this.tryReadResultJson(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\t\tif (rd && (rd as Record<string, unknown>).status === \"failed\") {\n\t\t\t\t\t\tcleanlyCompleted = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// If killed (lifeguard reap or user cancel) before producing a valid\n\t\t\t\t// result, honor the kill; a child that already wrote a verified\n\t\t\t\t// result.json completed its work and settles as a success below.\n\t\t\t\tif (killReason !== undefined && !verification.valid) {\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: cleanlyCompleted,\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\t// Advisory telemetry only: exceeding the budget never fails the task.\n\t\t\t\t\tbudget_exceeded: budgetExceeded,\n\t\t\t\t\tstatus: cleanlyCompleted ? \"complete\" : \"failed\",\n\t\t\t\t\tusedInheritedModelFallback: task.useInheritedModelFallback === true,\n\t\t\t\t};\n\n\t\t\t\tif (result.ok) {\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\t// Attach the verified result.json so callers can read the summary\n\t\t\t\t\t// without parsing the raw event stream.\n\t\t\t\t\tresult.result_data = this.tryReadResultJson(task.task_id, task.cwd ?? this.cwd);\n\n\t\t\t\t\t// Clean success: discard the per-task dispatch dir entirely\n\t\t\t\t\t// (session.jsonl, result.json, dispatch-log.json, budget.json). The\n\t\t\t\t\t// in-memory result already carries result_data, so callers lose\n\t\t\t\t\t// nothing. Trade-off: resume() only works for non-successful tasks.\n\t\t\t\t\tthis.cleanupDispatchDir(task.task_id, task.cwd ?? this.cwd);\n\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\tthis.resolveWaiter(task.task_id, result);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Failure path: keep the dispatch dir for debugging and persist output.\n\t\t\t\t// Attach the child's result.json (if any) and derive a concrete failure\n\t\t\t\t// reason so callers see the real cause (e.g. a provider usage/quota\n\t\t\t\t// error) instead of a generic \"subagent failed\".\n\t\t\t\tresult.result_data = this.tryReadResultJson(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\tif (!result.error) {\n\t\t\t\t\tresult.error = this.deriveFailureReason(result);\n\t\t\t\t}\n\t\t\t\tif (this.shouldRetryWithInheritedModel(task, result)) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`[DISPATCH] agent=${task.agent_type} task_id=${task.task_id} preferred model failed; retrying with inherited model`,\n\t\t\t\t\t);\n\t\t\t\t\tthis.cleanupRetryArtifacts(task);\n\t\t\t\t\tretryScheduled = true;\n\t\t\t\t\tthis.queue.unshift({ ...task, useInheritedModelFallback: true });\n\t\t\t\t\treturn;\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: result.error ?? `Exited with code ${code}`,\n\t\t\t\t});\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\t// The retry (started synchronously here) looks the budget up from\n\t\t\t\t\t// this.budgets and reuses it; flag it so the .finally below doesn't\n\t\t\t\t\t// strip the budget's listeners or delete the entry the retry now owns.\n\t\t\t\t\tretryScheduled = true;\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\tusedInheritedModelFallback: task.useInheritedModelFallback === true,\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\t// Runs on success AND failure: the stdout reader used to be detached only\n\t\t\t\t// on the success path, leaking listeners (and their buffers) whenever a\n\t\t\t\t// child failed or the wait rejected.\n\t\t\t\tdetachStdoutReader?.();\n\t\t\t\tproc.stdout?.removeAllListeners(\"data\");\n\t\t\t\tproc.stderr?.removeAllListeners(\"data\");\n\t\t\t\tproc.stdout?.destroy();\n\t\t\t\tproc.stderr?.destroy();\n\t\t\t\tif (!retryScheduled) {\n\t\t\t\t\tbudget.removeAllListeners();\n\t\t\t\t\tthis.budgets.delete(task.task_id);\n\t\t\t\t}\n\t\t\t\tthis.pull();\n\t\t\t});\n\t}\n\n\t/** Whether a failed built-in subagent should be retried with `model: inherit`. */\n\tprivate shouldRetryWithInheritedModel(task: SubagentPoolTask, result: SubagentResult): boolean {\n\t\tif (task.useInheritedModelFallback) return false;\n\t\tif (task.sessionFile) return false;\n\t\t// Only the parent model is required: the provider may be unset when the\n\t\t// harness routes through a gateway. The retry inherits the parent model and\n\t\t// lets the child resolve the provider from its own default when none was threaded through.\n\t\tif (!task.model) return false;\n\n\t\tconst def = task.agent_type ? this.getRegistry().get(task.agent_type) : undefined;\n\t\t// Built-in agents always inherit; project agents may pin an explicit model in\n\t\t// frontmatter, so let them fall back too when that model is rejected.\n\t\tif (def?.source !== \"builtin\" && def?.source !== \"project\") return false;\n\t\tif (!def.model || def.model === MODEL_INHERIT) return false;\n\n\t\treturn this.isInheritedModelFallbackError(result);\n\t}\n\n\t/** Detect provider/model failures where inheriting the parent model can recover. */\n\tprivate isInheritedModelFallbackError(result: SubagentResult): boolean {\n\t\tconst text = [result.error, result.stderr, JSON.stringify(result.result_data ?? {})]\n\t\t\t.filter((part): part is string => typeof part === \"string\" && part.length > 0)\n\t\t\t.join(\"\\n\");\n\n\t\treturn INHERITED_MODEL_FALLBACK_ERROR.test(text);\n\t}\n\n\t/** Remove failed attempt artifacts before rerunning the same task id. */\n\tprivate cleanupRetryArtifacts(task: SubagentPoolTask): void {\n\t\tconst cwd = task.cwd ?? this.cwd;\n\t\tconst taskDir = getDispatchTaskDir(cwd, task.task_id);\n\t\tconst sessionFile = task.sessionFile ?? this.getSessionFile(task.task_id, cwd);\n\t\ttry {\n\t\t\trmSync(sessionFile, { force: true });\n\t\t\trmSync(join(taskDir, \"result.json\"), { force: true });\n\t\t\trmSync(join(taskDir, \"output.json\"), { force: true });\n\t\t} catch {\n\t\t\t// Best-effort cleanup; retry can still proceed with existing artifacts.\n\t\t}\n\t}\n\n\t/**\n\t * Best-effort concrete failure reason for a non-zero-exit subagent. Prefers\n\t * the child's result.json summary (which carries the provider/model error\n\t * message on failure), then the tail of stderr, then the exit code.\n\t */\n\tprivate deriveFailureReason(result: SubagentResult): string {\n\t\tconst summary = (result.result_data as { summary?: string } | undefined)?.summary?.trim();\n\t\tif (summary) {\n\t\t\treturn summary;\n\t\t}\n\t\tconst stderrTail = result.stderr\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => line.trim())\n\t\t\t.filter((line) => line.length > 0)\n\t\t\t.slice(-5)\n\t\t\t.join(\"\\n\");\n\t\tif (stderrTail) {\n\t\t\treturn stderrTail;\n\t\t}\n\t\treturn `Exited with code ${result.exit_code}`;\n\t}\n\n\tprivate tryReadResultJson(task_id: string, cwd: string): Record<string, unknown> | undefined {\n\t\tconst path = join(getDispatchTaskDir(cwd, 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.status === \"cancelled\") this.taskStatus.set(task_id, \"cancelled\");\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, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport { getDispatchTaskDir } from \"../config.js\";\nimport { attachJsonlLineReader } from \"../modes/rpc/jsonl.js\";\nimport { writeFileAtomicSync } from \"../utils/atomic-file.js\";\nimport { waitForChildProcess } from \"../utils/child-process.js\";\nimport { killProcessTree } from \"../utils/shell.js\";\nimport { MODEL_INHERIT } from \"./agent-frontmatter.js\";\nimport { type AgentRegistry, loadAgentRegistry } from \"./agent-registry.js\";\nimport { DispatchEvaluator } from \"./dispatch-evaluator.js\";\nimport { SubagentLifeguard } from \"./lifeguard.js\";\nimport { resolveModelReference } from \"./model-categories.js\";\nimport { OutputVerifier } from \"./output-verifier.js\";\nimport type { Settings } from \"./settings-manager.js\";\nimport {\n\tcurrentSubagentDepth,\n\tDEFER_MCP_SCHEMAS_ENV,\n\tresolveMaxSubagentDepth,\n\tSUBAGENT_DEPTH_ENV,\n\tSUBAGENT_SKIP_MCP_ENV,\n\ttoolAllowlistNeedsMcp,\n} from \"./subagent-depth.js\";\nimport { SUBAGENT_PROGRESS_EVENTS } from \"./subagent-events.js\";\nimport { TokenBudget } from \"./token-budget.js\";\n\n/**\n * Provider/model failure signatures where inheriting the parent model can\n * recover. Compiled once at module load instead of on every error check.\n */\nconst INHERITED_MODEL_FALLBACK_ERROR =\n\t/usage[_\\s-]?limit|subscription|quota|rate.?limit|too many requests|429|insufficient|out of credit|credit balance|billing|payment required|402|model[^\\n]*(not found|unavailable|not available|not supported|does not exist|invalid|unsupported)|no api key|no auth configured|authentication|unauthorized|forbidden|permission/i;\n\nexport interface SubagentPoolTask {\n\ttask_id: string;\n\tagent_type: string;\n\ttask: string;\n\tcontext?: string;\n\ttoken_budget?: number;\n\tcwd?: string;\n\tmodel?: string;\n\tprovider?: string;\n\t/**\n\t * Explicit session file for the child to persist/continue. When omitted the\n\t * child uses its own dispatch dir (`<dispatch>/<task_id>/session.jsonl`).\n\t * Resume reuses the original task's session file to continue the transcript.\n\t */\n\tsessionFile?: string;\n\t/** Internal: retry using the caller's model when a built-in agent's preferred model fails. */\n\tuseInheritedModelFallback?: boolean;\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\" | \"cancelled\";\n\t/** Parsed result.json content when available (e.g. on partial completion). */\n\tresult_data?: Record<string, unknown>;\n\t/** True when this run used the inherited-model fallback (preferred model failed first). */\n\tusedInheritedModelFallback?: boolean;\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 DispatchOptions {\n\t/** Skip evaluation and force this agent type (user/explicit override).\n\t * Accepts any registry-defined agent name, not just the built-in modes. */\n\tforceAgent?: string;\n\t/** Context distilled from the calling agent, passed to the subagent. */\n\tcontext?: string;\n\t/** Model id for the subagent (defaults to the child's configured default). */\n\tmodel?: string;\n\t/** Provider for the subagent. */\n\tprovider?: string;\n\t/** Explicit session file to persist/continue (used by resume). */\n\tsessionFile?: string;\n\t/** Caller-supplied task id. Defaults to a generated `dispatch-…` id. Lets a\n\t * caller register liveness/inbox state under the id before dispatch resolves. */\n\ttaskId?: string;\n}\n\nexport interface SubagentPoolOptions {\n\t/** Path to the hoocode executable (or the runtime, e.g. node, when prefixArgs is set). */\n\texecutable: string;\n\t/** Args inserted before task args (e.g. the CLI entry script for node/tsx). */\n\tprefixArgs?: 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\t/**\n\t * Non-default skill paths to forward to every spawned subagent via --skill.\n\t * Subagents auto-discover skills from standard locations; only paths that\n\t * won't be found by default discovery need to be forwarded here.\n\t */\n\tskillPaths?: string[];\n\t/** Settings for model category resolution. */\n\tsettings?: Settings;\n\t/**\n\t * Available/configured models used to derive default model-category mappings\n\t * when a tier is not explicitly set in `settings.modelCategories`. Snapshotted\n\t * at pool creation, mirroring how `settings` is captured.\n\t */\n\tavailableModels?: readonly Model<Api>[];\n}\n\n/**\n * Default hard cap on assistant turns for a spawned subagent when its definition\n * does not set `maxTurns`. The token budget is advisory (it warns but never\n * kills), so this turn cap is the guaranteed hard stop for every subagent.\n */\nexport const DEFAULT_SUBAGENT_MAX_TURNS = 50;\n\n/**\n * AgentSession event `type`s forwarded from a subagent's json event stream as\n * `task_progress` events. Deliberately coarse; the child now also filters its\n * stdout to this set plus `message_end` at the source (see\n * SUBAGENT_STDOUT_EVENT_TYPES), so the per-delta firehose no longer crosses the\n * pipe. Re-exported from the shared module so the child emitter and this\n * consumer stay in lockstep.\n */\nexport const FORWARDED_SUBAGENT_EVENTS = SUBAGENT_PROGRESS_EVENTS;\n\n/**\n * Cap on the captured stdout/stderr text kept per task (tail-truncated). The\n * capture exists for diagnostics (failure reasons, output.json), so keeping the\n * most recent tail is enough; without a cap a chatty child could grow the\n * parent's memory without bound across a long swarm.\n */\nexport const MAX_CAPTURED_STREAM_CHARS = 256 * 1024;\n\n/**\n * Cap on a single un-terminated JSONL line buffered from a child's stdout. The\n * forwarded events are small; anything approaching this is a runaway writer,\n * and the reader drops the line rather than buffering toward OOM.\n */\nexport const MAX_SUBAGENT_EVENT_LINE_CHARS = 8 * 1024 * 1024;\n\n/** Append a chunk to a capped capture buffer, keeping the most recent tail. */\nfunction appendTail(current: string, chunk: string, cap: number = MAX_CAPTURED_STREAM_CHARS): string {\n\tconst next = current + chunk;\n\treturn next.length > cap ? next.slice(next.length - cap) : next;\n}\n\n/** The action the pool should take for one JSONL line from a subagent's stdout. */\nexport type SubagentStdoutLine =\n\t| { kind: \"heartbeat\" }\n\t| { kind: \"progress\"; event: Record<string, unknown> }\n\t| { kind: \"ignore\" };\n\n/**\n * Classify one JSONL line from a subagent's stdout into the action to take.\n * Pure (no side effects) so the ping/forward/drop policy is unit-testable without\n * spawning a child. Line framing — UTF-8-safe reassembly of chunks split mid-line\n * — is handled upstream by attachJsonlLineReader; this only sees complete lines.\n */\nexport function classifySubagentLine(line: string): SubagentStdoutLine {\n\tconst trimmed = line.trim();\n\tif (!trimmed.startsWith(\"{\")) return { kind: \"ignore\" };\n\tlet parsed: Record<string, unknown>;\n\ttry {\n\t\tparsed = JSON.parse(trimmed) as Record<string, unknown>;\n\t} catch {\n\t\treturn { kind: \"ignore\" };\n\t}\n\tif (parsed.ping === true) return { kind: \"heartbeat\" };\n\tif (typeof parsed.type === \"string\" && FORWARDED_SUBAGENT_EVENTS.has(parsed.type)) {\n\t\treturn { kind: \"progress\", event: parsed };\n\t}\n\treturn { kind: \"ignore\" };\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 past the load-scaled threshold (60s base,\n * widened under concurrency/event-loop lag), process SIGKILLed\n * - \"task_timeout\" – hard timeout exceeded, process was SIGKILLed\n * - \"task_cancelled\" – user-initiated cancel (see cancel()); process tree killed\n * - \"budget_warning\" – token usage crossed 80% threshold (advisory)\n * - \"budget_exceeded\" – token usage crossed 100% threshold (advisory; never kills)\n * - \"task_progress\" – coarse lifecycle event (turn_end, tool start/end) parsed\n * from the child's json event stream, for live UI updates\n */\nexport class SubagentPool extends EventEmitter {\n\tprivate readonly maxConcurrency: number;\n\tprivate readonly executable: string;\n\tprivate readonly prefixArgs: string[];\n\tprivate readonly cwd: string;\n\tprivate readonly env: NodeJS.ProcessEnv;\n\tprivate readonly defaultTokenBudget: number;\n\t/** Non-default skill paths forwarded to every spawned subagent via --skill. */\n\tprivate skillPaths: string[];\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/** Lazily-loaded agent registry (frontmatter definitions) for this pool's cwd. */\n\tprivate registry?: AgentRegistry;\n\t/** Tracks why a task was killed (stalled / timeout / user cancel) before exit handler fires. */\n\tprivate killReasons = new Map<string, \"stalled\" | \"timeout\" | \"cancelled\">();\n\t/** Persistent terminal status map, survives wait_for consumption. */\n\tprivate taskStatus = new Map<string, \"done\" | \"failed\" | \"stalled\" | \"timeout\" | \"cancelled\">();\n\t/** Settings for model category resolution. */\n\tprivate readonly settings?: Settings;\n\t/** Available models used to derive default model-category mappings (snapshot). */\n\tprivate readonly availableModels: readonly Model<Api>[];\n\n\tconstructor(options: SubagentPoolOptions) {\n\t\tsuper();\n\t\tthis.maxConcurrency = options.maxConcurrency ?? 5;\n\t\tthis.executable = options.executable;\n\t\tthis.prefixArgs = options.prefixArgs ?? [];\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.skillPaths = options.skillPaths ? [...options.skillPaths] : [];\n\t\tthis.settings = options.settings;\n\t\tthis.availableModels = options.availableModels ?? [];\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/** Update the non-default skill paths forwarded to new subagents. */\n\tupdateSkillPaths(paths: string[]): void {\n\t\tthis.skillPaths = [...paths];\n\t}\n\n\t/**\n\t * Report external in-process load (e.g. the number of background MCP tools\n\t * currently executing in the parent) to the lifeguard. This widens its\n\t * heartbeat/timeout tolerance so monitored subagents aren't false-positive\n\t * reaped when the parent's event loop is busy with concurrent background work.\n\t */\n\tsetExternalLoad(count: number): void {\n\t\tthis.lifeguard.setExternalLoad(count);\n\t}\n\n\t/** Lazily load the agent registry for this pool's cwd. */\n\tprivate getRegistry(): AgentRegistry {\n\t\tif (!this.registry) {\n\t\t\tthis.registry = loadAgentRegistry({ cwd: this.cwd });\n\t\t}\n\t\treturn this.registry;\n\t}\n\n\t/** Priority value: higher numbers run first. */\n\tprivate priorityOf(agent_type: string): number {\n\t\t// Read-only investigation (explore/plan) often unblocks downstream work, so\n\t\t// it runs ahead of other agents.\n\t\treturn agent_type === \"explore\" || agent_type === \"plan\" ? 2 : 1;\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(\n\t\ttask_id: string,\n\t): \"running\" | \"queued\" | \"done\" | \"failed\" | \"stalled\" | \"timeout\" | \"cancelled\" | \"unknown\" {\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.status === \"cancelled\") return \"cancelled\";\n\t\t\tif (result.ok) return \"done\";\n\t\t\treturn \"failed\";\n\t\t}\n\t\treturn \"unknown\";\n\t}\n\n\t/**\n\t * Cancel a task on the user's behalf (Esc/abort mid-turn). A queued task is\n\t * removed and settles immediately; a running task's whole process tree is\n\t * killed and settles with status \"cancelled\" when its exit is observed\n\t * (unless it already wrote a valid result.json — completed work is honored).\n\t * Returns false for unknown/settled task ids.\n\t */\n\tcancel(task_id: string): boolean {\n\t\tconst queued = this.queue.findIndex((t) => t.task_id === task_id);\n\t\tif (queued !== -1) {\n\t\t\tthis.queue.splice(queued, 1);\n\t\t\tconst result: SubagentResult = {\n\t\t\t\ttask_id,\n\t\t\t\tok: false,\n\t\t\t\tstdout: \"\",\n\t\t\t\tstderr: \"\",\n\t\t\t\texit_code: null,\n\t\t\t\terror: \"cancelled before start\",\n\t\t\t\tstatus: \"cancelled\",\n\t\t\t};\n\t\t\tthis.emit(\"task_cancelled\", { task_id });\n\t\t\tthis.resolveWaiter(task_id, result);\n\t\t\treturn true;\n\t\t}\n\t\tconst slot = this.slots.get(task_id);\n\t\tif (!slot) return false;\n\t\tthis.killReasons.set(task_id, \"cancelled\");\n\t\tif (slot.pid > 0) {\n\t\t\tkillProcessTree(slot.pid);\n\t\t} else if (!slot.process.killed) {\n\t\t\tslot.process.kill(\"SIGKILL\");\n\t\t}\n\t\treturn true;\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 `options.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, options: DispatchOptions = {}): Promise<TaskResult> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(new Error(\"SubagentPool has been disposed\"));\n\t\t}\n\t\tconst begin = this.beginDispatch(task, options);\n\t\tif (begin.handled_inline) {\n\t\t\treturn { handled_inline: true, reason: begin.reason };\n\t\t}\n\t\tconst result = await this.wait_for(begin.task_id);\n\t\treturn {\n\t\t\thandled_inline: false,\n\t\t\ttask_id: begin.task_id,\n\t\t\tagent_type: begin.agent_type,\n\t\t\treason: begin.reason,\n\t\t\tresult,\n\t\t\tduration: Date.now() - begin.startTime,\n\t\t};\n\t}\n\n\t/**\n\t * Fire-and-forget dispatch for background agents. Spawns the subagent and\n\t * returns its handle immediately; the caller polls get_status()/collect().\n\t */\n\tdispatchDetached(\n\t\ttask: string,\n\t\toptions: DispatchOptions = {},\n\t): { handled_inline: boolean; task_id?: string; agent_type?: string; reason?: string } {\n\t\tif (this.disposed) {\n\t\t\tthrow new Error(\"SubagentPool has been disposed\");\n\t\t}\n\t\tconst begin = this.beginDispatch(task, options);\n\t\tif (begin.handled_inline) {\n\t\t\treturn { handled_inline: true, reason: begin.reason };\n\t\t}\n\t\treturn { handled_inline: false, task_id: begin.task_id, agent_type: begin.agent_type, reason: begin.reason };\n\t}\n\n\t/**\n\t * Evaluate, log, and spawn a task without waiting. Shared by dispatch()\n\t * (blocking) and dispatchDetached() (background).\n\t */\n\tprivate beginDispatch(\n\t\ttask: string,\n\t\toptions: DispatchOptions,\n\t):\n\t\t| { handled_inline: true; reason?: string }\n\t\t| { handled_inline: false; task_id: string; agent_type: string; reason?: string; startTime: number } {\n\t\tconst { forceAgent, context, model, provider, sessionFile } = options;\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 = forceAgent ?? \"general-purpose\";\n\t\tconst task_id = options.taskId ?? `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\t\t// Depth of the child about to be spawned (this process's depth + 1). Surfaced\n\t\t// so a delegation tree's nesting is visible in logs without extra tooling.\n\t\tconst childDepth = currentSubagentDepth(this.env) + 1;\n\n\t\t// Pre-dispatch logging. Use stderr: stdout is reserved for the JSON event\n\t\t// stream / TUI render and must not be polluted.\n\t\tconsole.error(\n\t\t\t`[DISPATCH] agent=${agent_type} depth=${childDepth} reason=${reason} complexity=${complexity} task_id=${task_id}`,\n\t\t);\n\t\tthis.writeDispatchLog(task_id, agent_type, reason, complexity, task, childDepth);\n\n\t\tconst poolTask: SubagentPoolTask = {\n\t\t\ttask_id,\n\t\t\tagent_type,\n\t\t\ttask,\n\t\t\tcontext,\n\t\t\tmodel,\n\t\t\tprovider,\n\t\t\tsessionFile,\n\t\t\tcwd: this.cwd,\n\t\t};\n\t\tconst startTime = Date.now();\n\t\tthis.spawn(poolTask);\n\t\treturn { handled_inline: false, task_id, agent_type, reason, startTime };\n\t}\n\n\t/**\n\t * Non-destructively read a completed task's result (for background polling).\n\t * Returns undefined while the task is still running/queued, or if its result\n\t * was already consumed via wait_for().\n\t */\n\tcollect(task_id: string): SubagentResult | undefined {\n\t\treturn this.completed.get(task_id);\n\t}\n\n\t/** Absolute path of the persisted session file for a task. */\n\tgetSessionFile(task_id: string, cwd: string = this.cwd): string {\n\t\treturn join(getDispatchTaskDir(cwd, task_id), \"session.jsonl\");\n\t}\n\n\t/**\n\t * Resume a previously dispatched subagent, continuing its persisted session\n\t * with a follow-up prompt. Recovers the original agent type from its dispatch\n\t * log. Rejects if no resumable session exists for the task.\n\t */\n\tasync resume(\n\t\ttask_id: string,\n\t\tprompt: string,\n\t\toptions: Omit<DispatchOptions, \"forceAgent\" | \"sessionFile\"> = {},\n\t): Promise<TaskResult> {\n\t\tif (this.disposed) {\n\t\t\treturn Promise.reject(new Error(\"SubagentPool has been disposed\"));\n\t\t}\n\t\tconst sessionFile = this.getSessionFile(task_id);\n\t\tif (!existsSync(sessionFile)) {\n\t\t\treturn Promise.reject(new Error(`No resumable session for task \"${task_id}\" (expected ${sessionFile}).`));\n\t\t}\n\t\tconst agent_type = this.readDispatchAgentType(task_id) ?? \"general-purpose\";\n\t\treturn this.dispatch(prompt, { ...options, forceAgent: agent_type, sessionFile });\n\t}\n\n\t/** Recover the agent type a task was dispatched with, from its dispatch log. */\n\tprivate readDispatchAgentType(task_id: string): string | undefined {\n\t\tconst path = join(getDispatchTaskDir(this.cwd, task_id), \"dispatch-log.json\");\n\t\tif (!existsSync(path)) return undefined;\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\")) as { agent_type?: string };\n\t\t\treturn typeof parsed.agent_type === \"string\" ? parsed.agent_type : undefined;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\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\tdepth: number,\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\tdepth,\n\t\t\treason,\n\t\t\tcomplexity,\n\t\t\ttask,\n\t\t};\n\t\tconst path = join(getDispatchTaskDir(this.cwd, 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(getDispatchTaskDir(this.cwd, task_id), \"output.json\");\n\t\ttry {\n\t\t\t// Atomic like result.json: background pollers may read output.json while\n\t\t\t// it is being (re)written.\n\t\t\twriteFileAtomicSync(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/**\n\t * Remove a task's dispatch dir after a clean, verified success. Best-effort:\n\t * a cleanup failure must never fail an otherwise successful task.\n\t */\n\tprivate cleanupDispatchDir(task_id: string, cwd: string): void {\n\t\ttry {\n\t\t\trmSync(getDispatchTaskDir(cwd, task_id), { recursive: true, force: true });\n\t\t} catch {\n\t\t\t// Best-effort cleanup\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\t// Kill the whole process group/tree, not just the direct child: a\n\t\t\t// subagent's own children (bash commands, nested subagents) must not\n\t\t\t// outlive the pool.\n\t\t\tif (slot.pid > 0) {\n\t\t\t\tkillProcessTree(slot.pid);\n\t\t\t} else if (!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\t// Persist the child's session so a finished/interrupted subagent can be\n\t\t// resumed later (see resume()). SessionManager.open() creates the file on\n\t\t// first run and continues it on subsequent runs.\n\t\tconst sessionFile = task.sessionFile ?? this.getSessionFile(task.task_id, task.cwd ?? this.cwd);\n\t\tconst args: string[] = [\n\t\t\t...this.prefixArgs,\n\t\t\t\"--mode\",\n\t\t\t\"json\",\n\t\t\t\"--session\",\n\t\t\tsessionFile,\n\t\t\t\"--task-id\",\n\t\t\ttask.task_id,\n\t\t];\n\n\t\t// Prefer the data-driven agent definition from the registry; fall back to the\n\t\t// built-in mode prompt/allowlist for legacy modes not present in the registry.\n\t\tconst def = task.agent_type ? this.getRegistry().get(task.agent_type) : undefined;\n\n\t\tif (task.agent_type) {\n\t\t\tconst systemPrompt = def?.prompt;\n\t\t\tif (systemPrompt) {\n\t\t\t\targs.push(\"--system-prompt\", systemPrompt);\n\t\t\t}\n\n\t\t\t// A `delegate: true` agent may itself dispatch via the Task tool, but only\n\t\t\t// while the child it becomes can still nest (childDepth < cap) — so the\n\t\t\t// deepest permitted level cannot delegate further. Gating here keeps the\n\t\t\t// authorization explicit and bounded by the same cap as the depth guard.\n\t\t\tconst childDepth = currentSubagentDepth(this.env) + 1;\n\t\t\tconst canChildDelegate = def?.delegate === true && childDepth < resolveMaxSubagentDepth(undefined, this.env);\n\n\t\t\t// Tool allowlist comes from the agent definition's frontmatter `tools`\n\t\t\t// field (read-only built-ins declare their own sandbox). When omitted, no\n\t\t\t// --tools is passed and the subagent inherits all parent tools (so the Task\n\t\t\t// tool already survives). A delegating agent with an explicit allowlist must\n\t\t\t// have Task/TaskOutput added, or the child would filter them out.\n\t\t\tconst tools = def?.tools ? [...def.tools] : undefined;\n\t\t\tif (canChildDelegate && tools) {\n\t\t\t\tfor (const t of [\"Task\", \"TaskOutput\"]) {\n\t\t\t\t\tif (!tools.includes(t)) tools.push(t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (tools && tools.length > 0) {\n\t\t\t\targs.push(\"--tools\", tools.join(\",\"));\n\t\t\t}\n\t\t\tif (def?.disallowedTools && def.disallowedTools.length > 0) {\n\t\t\t\targs.push(\"--disallowed-tools\", def.disallowedTools.join(\",\"));\n\t\t\t}\n\n\t\t\t// Propagate subagent enablement so the child registers the Task tool; without\n\t\t\t// this the flag-based enablement would not reach a spawned child.\n\t\t\tif (canChildDelegate) {\n\t\t\t\targs.push(\"--enable-subagents\");\n\t\t\t\t// Scoped delegation: restrict which agent types this child may spawn.\n\t\t\t\tif (def?.delegateTo && def.delegateTo.length > 0) {\n\t\t\t\t\targs.push(\"--delegate-allow\", def.delegateTo.join(\",\"));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Model precedence: a definition's explicit model wins (unless it is the\n\t\t// `inherit` sentinel), otherwise use the caller-provided model. Built-in\n\t\t// agents can retry with the inherited model when their preferred model is\n\t\t// unavailable or quota-limited.\n\t\tconst explicitModel =\n\t\t\t!task.useInheritedModelFallback && def?.model && def.model !== MODEL_INHERIT ? def.model : undefined;\n\t\t// Resolve a model-category reference (fast/standard/capable) to its\n\t\t// configured or derived model id. An unresolvable category yields undefined,\n\t\t// so no `--model` is passed and the child keeps its default model.\n\t\tconst rawModel = explicitModel ?? task.model;\n\t\tconst modelToUse = rawModel ? resolveModelReference(rawModel, this.settings, this.availableModels) : undefined;\n\t\tif (modelToUse) {\n\t\t\targs.push(\"--model\", modelToUse);\n\t\t}\n\t\t// Only pass --provider when the model doesn't already encode the provider\n\t\t// (e.g. \"anthropic/claude-sonnet-4-5\"). When both --model and --provider are\n\t\t// present, --provider wins and filters candidates to that provider, which\n\t\t// breaks models whose IDs contain a provider prefix from a different provider.\n\t\tif (task.provider && (!modelToUse || !modelToUse.includes(\"/\"))) {\n\t\t\targs.push(\"--provider\", task.provider);\n\t\t}\n\n\t\t// Always give subagents a hard turn cap. With the token budget now advisory\n\t\t// (warn-only), this is the guaranteed hard stop for a runaway subagent.\n\t\tconst maxTurns = def?.maxTurns && def.maxTurns > 0 ? def.maxTurns : DEFAULT_SUBAGENT_MAX_TURNS;\n\t\targs.push(\"--max-turns\", String(maxTurns));\n\n\t\t// Forward non-default skill paths so the subagent has access to all parent skills.\n\t\t// Standard discovery locations (~/.hoocode/, .hoocode/, .claude/) are found automatically.\n\t\tfor (const skillPath of this.skillPaths) {\n\t\t\targs.push(\"--skill\", skillPath);\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/**\n\t * Environment for a spawned child.\n\t *\n\t * Stamps the child's depth (parent depth + 1) so its own guard knows where it\n\t * sits in the tree; the tree-wide cap (HOOCODE_SUBAGENT_MAX_DEPTH) is inherited\n\t * via the spread, so at the default cap of 1 the child lands at depth 1 and\n\t * cannot spawn further subagents. Also flags the child to skip MCP server\n\t * connection when its tool allowlist is explicit and MCP-free — connecting\n\t * external servers it can never call is pure boot latency.\n\t */\n\tprivate childSpawnEnv(task: SubagentPoolTask): NodeJS.ProcessEnv {\n\t\tconst env: NodeJS.ProcessEnv = {\n\t\t\t...this.env,\n\t\t\t[SUBAGENT_DEPTH_ENV]: String(currentSubagentDepth(this.env) + 1),\n\t\t};\n\t\t// Use the agent's own frontmatter allowlist (the same source buildArgs passes\n\t\t// via --tools). A delegating child also gets Task/TaskOutput appended there,\n\t\t// but neither is an MCP tool, so the decision is unchanged by that.\n\t\tconst def = task.agent_type ? this.getRegistry().get(task.agent_type) : undefined;\n\t\tif (!toolAllowlistNeedsMcp(def?.tools)) {\n\t\t\tenv[SUBAGENT_SKIP_MCP_ENV] = \"1\";\n\t\t}\n\t\t// A child never defers MCP schemas: if it needs MCP it eager-registers its\n\t\t// allowlisted tools at dispatch so they are immediately callable (spec §2).\n\t\tdelete env[DEFER_MCP_SCHEMAS_ENV];\n\t\treturn env;\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\t// The token budget is advisory: surface telemetry but never kill. The\n\t\t\t// guaranteed hard stop is the per-subagent turn cap (--max-turns); see\n\t\t\t// DEFAULT_SUBAGENT_MAX_TURNS.\n\t\t\tbudget.on(\"budget_exceeded\", (data: { task_id: string; used: number; limit: number }) => {\n\t\t\t\tthis.emit(\"budget_exceeded\", data);\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.childSpawnEnv(task),\n\t\t\t\tshell: false,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t// POSIX: the child leads its own process group so a kill can reach its\n\t\t\t\t// whole tree (see lifeguard/dispose). A single-PID SIGKILL orphans any\n\t\t\t\t// grandchildren the subagent spawned (its own bash commands, nested\n\t\t\t\t// subagents), which kept burning CPU after their parent was reaped.\n\t\t\t\tdetached: process.platform !== \"win32\",\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 detachStdoutReader: (() => void) | undefined;\n\n\t\tproc.stdout?.on(\"data\", (data: Buffer) => {\n\t\t\t// Capture (tail-capped) for diagnostics, and treat any output as a\n\t\t\t// heartbeat: a child busily streaming events is alive even when the\n\t\t\t// parent's starved event loop hasn't parsed its {\"ping\":true} line yet.\n\t\t\t// All *parsing* happens in the single JSONL reader below.\n\t\t\tstdout = appendTail(stdout, data.toString());\n\t\t\tthis.lifeguard.recordHeartbeat(task.task_id);\n\t\t});\n\n\t\t// The one parser on the child's stdout: UTF-8-safe, LF-only framing —\n\t\t// multi-byte characters and large events split across pipe chunks are\n\t\t// reassembled before parsing. Each complete line feeds the token budget\n\t\t// (usage telemetry) and the ping/forward/drop classifier; previously the\n\t\t// budget ran its own naive chunk-splitting parser on the same stream, which\n\t\t// corrupted lines split mid-character. The line buffer is capped so a\n\t\t// runaway writer cannot OOM the parent. Detached when the child exits (see\n\t\t// cleanup below); the reader also self-detaches on stream error/close.\n\t\tif (proc.stdout) {\n\t\t\tdetachStdoutReader = attachJsonlLineReader(\n\t\t\t\tproc.stdout,\n\t\t\t\t(line) => {\n\t\t\t\t\tbudget.processLine(line);\n\t\t\t\t\tconst action = classifySubagentLine(line);\n\t\t\t\t\tif (action.kind === \"heartbeat\") {\n\t\t\t\t\t\tthis.lifeguard.recordHeartbeat(task.task_id);\n\t\t\t\t\t} else if (action.kind === \"progress\") {\n\t\t\t\t\t\tthis.emit(\"task_progress\", {\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\tevent: action.event,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{ maxBuffer: MAX_SUBAGENT_EVENT_LINE_CHARS },\n\t\t\t);\n\t\t}\n\t\tproc.stderr?.on(\"data\", (data: Buffer) => {\n\t\t\tstderr = appendTail(stderr, data.toString());\n\t\t});\n\n\t\t// True when this attempt scheduled another run of the same task id (spawn\n\t\t// retry or inherited-model fallback). The retry reuses the task's TokenBudget\n\t\t// so usage accumulates across attempts; the .finally below must then leave\n\t\t// the budget (and its listeners) alone instead of tearing it down under the\n\t\t// retry's feet.\n\t\tlet retryScheduled = false;\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// A subagent's success is defined by a valid, verified result.json, not by\n\t\t\t\t// its exit code. A child that finished its work and wrote a valid result can\n\t\t\t\t// still be SIGKILLed by the lifeguard before it exits on its own (lingering\n\t\t\t\t// open handles delay a natural exit past the heartbeat threshold), which forces\n\t\t\t\t// exit_code === null. Keying completion off the verified result, not code === 0,\n\t\t\t\t// honors that genuine success instead of discarding it as a false stall.\n\t\t\t\tconst verification = this.verifier.verify(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\t// A well-formed result.json counts as clean completion unless its own\n\t\t\t\t// status field declares failure (e.g. \"failed\" from a provider quota\n\t\t\t\t// error). Without this check the pool would treat a child that wrote a\n\t\t\t\t// valid-but-failed result.json and exited non-zero as a success.\n\t\t\t\tlet cleanlyCompleted = code === 0 || verification.valid;\n\t\t\t\tif (cleanlyCompleted && verification.valid) {\n\t\t\t\t\tconst rd = this.tryReadResultJson(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\t\tif (rd && (rd as Record<string, unknown>).status === \"failed\") {\n\t\t\t\t\t\tcleanlyCompleted = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// If killed (lifeguard reap or user cancel) before producing a valid\n\t\t\t\t// result, honor the kill; a child that already wrote a verified\n\t\t\t\t// result.json completed its work and settles as a success below.\n\t\t\t\tif (killReason !== undefined && !verification.valid) {\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: cleanlyCompleted,\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\t// Advisory telemetry only: exceeding the budget never fails the task.\n\t\t\t\t\tbudget_exceeded: budgetExceeded,\n\t\t\t\t\tstatus: cleanlyCompleted ? \"complete\" : \"failed\",\n\t\t\t\t\tusedInheritedModelFallback: task.useInheritedModelFallback === true,\n\t\t\t\t};\n\n\t\t\t\tif (result.ok) {\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\t// Attach the verified result.json so callers can read the summary\n\t\t\t\t\t// without parsing the raw event stream.\n\t\t\t\t\tresult.result_data = this.tryReadResultJson(task.task_id, task.cwd ?? this.cwd);\n\n\t\t\t\t\t// Clean success: discard the per-task dispatch dir entirely\n\t\t\t\t\t// (session.jsonl, result.json, dispatch-log.json, budget.json). The\n\t\t\t\t\t// in-memory result already carries result_data, so callers lose\n\t\t\t\t\t// nothing. Trade-off: resume() only works for non-successful tasks.\n\t\t\t\t\tthis.cleanupDispatchDir(task.task_id, task.cwd ?? this.cwd);\n\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\tthis.resolveWaiter(task.task_id, result);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Failure path: keep the dispatch dir for debugging and persist output.\n\t\t\t\t// Attach the child's result.json (if any) and derive a concrete failure\n\t\t\t\t// reason so callers see the real cause (e.g. a provider usage/quota\n\t\t\t\t// error) instead of a generic \"subagent failed\".\n\t\t\t\tresult.result_data = this.tryReadResultJson(task.task_id, task.cwd ?? this.cwd);\n\t\t\t\tif (!result.error) {\n\t\t\t\t\tresult.error = this.deriveFailureReason(result);\n\t\t\t\t}\n\t\t\t\tif (this.shouldRetryWithInheritedModel(task, result)) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`[DISPATCH] agent=${task.agent_type} task_id=${task.task_id} preferred model failed; retrying with inherited model`,\n\t\t\t\t\t);\n\t\t\t\t\tthis.cleanupRetryArtifacts(task);\n\t\t\t\t\tretryScheduled = true;\n\t\t\t\t\tthis.queue.unshift({ ...task, useInheritedModelFallback: true });\n\t\t\t\t\treturn;\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: result.error ?? `Exited with code ${code}`,\n\t\t\t\t});\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\t// The retry (started synchronously here) looks the budget up from\n\t\t\t\t\t// this.budgets and reuses it; flag it so the .finally below doesn't\n\t\t\t\t\t// strip the budget's listeners or delete the entry the retry now owns.\n\t\t\t\t\tretryScheduled = true;\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\tusedInheritedModelFallback: task.useInheritedModelFallback === true,\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\t// Runs on success AND failure: the stdout reader used to be detached only\n\t\t\t\t// on the success path, leaking listeners (and their buffers) whenever a\n\t\t\t\t// child failed or the wait rejected.\n\t\t\t\tdetachStdoutReader?.();\n\t\t\t\tproc.stdout?.removeAllListeners(\"data\");\n\t\t\t\tproc.stderr?.removeAllListeners(\"data\");\n\t\t\t\tproc.stdout?.destroy();\n\t\t\t\tproc.stderr?.destroy();\n\t\t\t\tif (!retryScheduled) {\n\t\t\t\t\tbudget.removeAllListeners();\n\t\t\t\t\tthis.budgets.delete(task.task_id);\n\t\t\t\t}\n\t\t\t\tthis.pull();\n\t\t\t});\n\t}\n\n\t/** Whether a failed built-in subagent should be retried with `model: inherit`. */\n\tprivate shouldRetryWithInheritedModel(task: SubagentPoolTask, result: SubagentResult): boolean {\n\t\tif (task.useInheritedModelFallback) return false;\n\t\tif (task.sessionFile) return false;\n\t\t// Only the parent model is required: the provider may be unset when the\n\t\t// harness routes through a gateway. The retry inherits the parent model and\n\t\t// lets the child resolve the provider from its own default when none was threaded through.\n\t\tif (!task.model) return false;\n\n\t\tconst def = task.agent_type ? this.getRegistry().get(task.agent_type) : undefined;\n\t\t// Built-in agents always inherit; project agents may pin an explicit model in\n\t\t// frontmatter, so let them fall back too when that model is rejected.\n\t\tif (def?.source !== \"builtin\" && def?.source !== \"project\") return false;\n\t\tif (!def.model || def.model === MODEL_INHERIT) return false;\n\n\t\treturn this.isInheritedModelFallbackError(result);\n\t}\n\n\t/** Detect provider/model failures where inheriting the parent model can recover. */\n\tprivate isInheritedModelFallbackError(result: SubagentResult): boolean {\n\t\tconst text = [result.error, result.stderr, JSON.stringify(result.result_data ?? {})]\n\t\t\t.filter((part): part is string => typeof part === \"string\" && part.length > 0)\n\t\t\t.join(\"\\n\");\n\n\t\treturn INHERITED_MODEL_FALLBACK_ERROR.test(text);\n\t}\n\n\t/** Remove failed attempt artifacts before rerunning the same task id. */\n\tprivate cleanupRetryArtifacts(task: SubagentPoolTask): void {\n\t\tconst cwd = task.cwd ?? this.cwd;\n\t\tconst taskDir = getDispatchTaskDir(cwd, task.task_id);\n\t\tconst sessionFile = task.sessionFile ?? this.getSessionFile(task.task_id, cwd);\n\t\ttry {\n\t\t\trmSync(sessionFile, { force: true });\n\t\t\trmSync(join(taskDir, \"result.json\"), { force: true });\n\t\t\trmSync(join(taskDir, \"output.json\"), { force: true });\n\t\t} catch {\n\t\t\t// Best-effort cleanup; retry can still proceed with existing artifacts.\n\t\t}\n\t}\n\n\t/**\n\t * Best-effort concrete failure reason for a non-zero-exit subagent. Prefers\n\t * the child's result.json summary (which carries the provider/model error\n\t * message on failure), then the tail of stderr, then the exit code.\n\t */\n\tprivate deriveFailureReason(result: SubagentResult): string {\n\t\tconst summary = (result.result_data as { summary?: string } | undefined)?.summary?.trim();\n\t\tif (summary) {\n\t\t\treturn summary;\n\t\t}\n\t\tconst stderrTail = result.stderr\n\t\t\t.split(\"\\n\")\n\t\t\t.map((line) => line.trim())\n\t\t\t.filter((line) => line.length > 0)\n\t\t\t.slice(-5)\n\t\t\t.join(\"\\n\");\n\t\tif (stderrTail) {\n\t\t\treturn stderrTail;\n\t\t}\n\t\treturn `Exited with code ${result.exit_code}`;\n\t}\n\n\tprivate tryReadResultJson(task_id: string, cwd: string): Record<string, unknown> | undefined {\n\t\tconst path = join(getDispatchTaskDir(cwd, 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.status === \"cancelled\") this.taskStatus.set(task_id, \"cancelled\");\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"warm-subagent-pool.d.ts","sourceRoot":"","sources":["../../src/core/warm-subagent-pool.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Warm subagent worker pool (experimental, opt-in via `--warm-subagents` /\n * settings.warmSubagents; default off).\n *\n * The cold path (SubagentPool) re-execs the whole CLI for every dispatch, paying\n * a full module-load + resource-graph boot each time. A warm worker instead keeps\n * a long-lived child running in RPC mode and hands it one task at a time over the\n * existing JSON-line protocol: `new_session` resets its conversation between\n * tasks, `prompt` runs the task, and `agent_end` on the event stream signals\n * completion, after which the answer + usage are pulled inline (no result.json\n * disk round-trip, no OutputVerifier). The first task per worker still pays the\n * boot; every reuse after that skips it.\n *\n * Scope/limits (deliberately conservative — see the dispatch integration):\n * - Workers are pinned per agent type: RPC has no per-prompt system-prompt/tools\n * swap, so each (agentType, model, provider) is its own worker config.\n * - Only non-resume, non-fork dispatches are eligible; resume/fork need a\n * persisted/forked session the warm path does not own.\n * - Any worker/infra failure falls back to the cold pool, so enabling this can\n * only change latency, never whether a task can run.\n */\n\nimport type { AgentEvent } from \"@kolisachint/hoocode-agent-core\";\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport { getSubagentSpawnCommand } from \"../config.js\";\nimport { RpcClient } from \"../modes/rpc/rpc-client.js\";\nimport { MODEL_INHERIT } from \"./agent-frontmatter.js\";\nimport { type AgentRegistry, loadAgentRegistry } from \"./agent-registry.js\";\nimport { resolveModelReference } from \"./model-categories.js\";\nimport type { Settings } from \"./settings-manager.js\";\nimport {\n\tcurrentSubagentDepth,\n\tDEFER_MCP_SCHEMAS_ENV,\n\tresolveMaxSubagentDepth,\n\tSUBAGENT_DEPTH_ENV,\n\tSUBAGENT_SKIP_MCP_ENV,\n\ttoolAllowlistNeedsMcp,\n} from \"./subagent-depth.js\";\nimport { DEFAULT_SUBAGENT_MAX_TURNS } from \"./subagent-pool.js\";\n\n/** Usage totals pulled from a worker after a task, shaped like SubagentResultFile.usage. */\nexport interface WarmUsage {\n\tinput: number;\n\toutput: number;\n\tcacheRead: number;\n\tcacheWrite: number;\n\tcost: number;\n}\n\n/** Outcome of running one task on a warm worker. */\nexport interface WarmRunResult {\n\tok: boolean;\n\tstatus: \"complete\" | \"failed\";\n\t/** The subagent's final assistant text (its answer to the caller). */\n\tsummary: string;\n\tusage?: WarmUsage;\n\terror?: string;\n}\n\n/** Per-dispatch inputs that select and configure a worker. */\nexport interface WarmDispatchOptions {\n\tagentType: string;\n\tcwd: string;\n\t/** Model id or category (fast/standard/capable); resolved to a concrete id. */\n\tmodel?: string;\n\tprovider?: string;\n}\n\n/** Reports the tool a warm worker is currently running (\"\" = idle between tools). */\nexport type WarmProgressCallback = (activity: string) => void;\n\n/** A run failed at the infrastructure level (worker crash, timeout, protocol error). */\nexport class WarmWorkerError extends Error {}\n\n/** Default per-task wait before declaring a warm run stalled and failing over to cold. */\nconst WARM_RUN_TIMEOUT_MS = 180_000;\n\n/**\n * One long-lived RPC child pinned to a single agent-type configuration. Reused\n * across tasks via reset(); a crash makes it not-alive so the pool discards it.\n */\nexport class WarmSubagentWorker {\n\tprivate readonly client: RpcClient;\n\tprivate alive = true;\n\n\tconstructor(\n\t\treadonly key: string,\n\t\toptions: WarmDispatchOptions,\n\t\tprivate readonly env: NodeJS.ProcessEnv,\n\t\tregistry: AgentRegistry,\n\t\tskillPaths: readonly string[],\n\t\tsettings: Settings | undefined,\n\t\tavailableModels: readonly Model<Api>[],\n\t\t/** Spawn command override (tests inject a fake RPC child); defaults to the real spawn command. */\n\t\tspawnCommand?: { executable: string; prefixArgs: string[] },\n\t) {\n\t\tconst { executable, prefixArgs } = spawnCommand ?? getSubagentSpawnCommand();\n\t\tthis.client = new RpcClient({\n\t\t\texecutable,\n\t\t\tprefixArgs,\n\t\t\tcwd: options.cwd,\n\t\t\tenv: this.env as Record<string, string>,\n\t\t\targs: buildWorkerArgs(options, registry, skillPaths, settings, availableModels),\n\t\t});\n\t}\n\n\t/** Boot the child. Throws (as WarmWorkerError) if it fails to come up. */\n\tasync start(): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.start();\n\t\t} catch (error) {\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tisAlive(): boolean {\n\t\treturn this.alive;\n\t}\n\n\t/**\n\t * Run one task to completion and return its answer + usage. Throws\n\t * WarmWorkerError on an infra failure (crash/timeout/protocol) so the caller\n\t * can fall back to the cold pool; a task that ran but reported failure returns\n\t * `{ ok: false }` instead (no fall back — the work was actually done).\n\t */\n\tasync run(\n\t\tprompt: string,\n\t\tonActivity?: WarmProgressCallback,\n\t\ttimeoutMs = WARM_RUN_TIMEOUT_MS,\n\t): Promise<WarmRunResult> {\n\t\tif (!this.alive) throw new WarmWorkerError(\"worker is not alive\");\n\t\t// Mirror the cold pool's coarse progress: report the tool the child is\n\t\t// currently running so a warm dispatch's task row reads \"⋯ grep\" rather than a\n\t\t// static \"running…\". Cleared between tools and at turn end.\n\t\tconst detachProgress = onActivity ? this.tapProgress(onActivity) : undefined;\n\t\ttry {\n\t\t\tconst events = await this.client.promptAndWait(prompt, undefined, timeoutMs);\n\t\t\tconst failure = firstTurnError(events);\n\t\t\tconst summary = (await this.client.getLastAssistantText()) ?? \"\";\n\t\t\tconst usage = await this.readUsage();\n\t\t\tif (failure) {\n\t\t\t\treturn { ok: false, status: \"failed\", summary, usage, error: failure };\n\t\t\t}\n\t\t\treturn { ok: true, status: \"complete\", summary, usage };\n\t\t} catch (error) {\n\t\t\t// A prompt/await failure means the child is no longer trustworthy: mark it\n\t\t\t// dead so the pool discards it, and signal infra failure for fallback.\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t} finally {\n\t\t\tdetachProgress?.();\n\t\t\tonActivity?.(\"\");\n\t\t}\n\t}\n\n\t/** Forward the child's coarse tool-lifecycle events to an activity callback. */\n\tprivate tapProgress(onActivity: WarmProgressCallback): () => void {\n\t\treturn this.client.onEvent((event) => {\n\t\t\tconst e = event as { type?: string; toolName?: string };\n\t\t\tif (e.type === \"tool_execution_start\") onActivity(typeof e.toolName === \"string\" ? e.toolName : \"\");\n\t\t\telse if (e.type === \"tool_execution_end\" || e.type === \"turn_end\") onActivity(\"\");\n\t\t});\n\t}\n\n\t/** Reset the worker's conversation so it can take the next task cleanly. */\n\tasync reset(): Promise<void> {\n\t\tif (!this.alive) return;\n\t\ttry {\n\t\t\tawait this.client.newSession();\n\t\t} catch (error) {\n\t\t\t// A reset failure leaves the worker in an unknown state — retire it.\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tthis.alive = false;\n\t\tawait this.client.stop().catch(() => {});\n\t}\n\n\tprivate async readUsage(): Promise<WarmUsage | undefined> {\n\t\ttry {\n\t\t\tconst stats = await this.client.getSessionStats();\n\t\t\treturn {\n\t\t\t\tinput: stats.tokens.input,\n\t\t\t\toutput: stats.tokens.output,\n\t\t\t\tcacheRead: stats.tokens.cacheRead,\n\t\t\t\tcacheWrite: stats.tokens.cacheWrite,\n\t\t\t\tcost: stats.cost,\n\t\t\t};\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n}\n\n/**\n * Pool of warm workers keyed by agent-type configuration. Hands out an idle\n * worker (or boots a new one up to a per-key cap), and on release resets the\n * worker and returns it to the idle set with an idle-TTL reclaim timer.\n */\nexport class WarmSubagentPool {\n\tprivate idle = new Map<string, WarmSubagentWorker[]>();\n\tprivate reclaimTimers = new Map<WarmSubagentWorker, ReturnType<typeof setTimeout>>();\n\tprivate liveCount = new Map<string, number>();\n\tprivate disposed = false;\n\tprivate registry?: AgentRegistry;\n\n\tconstructor(\n\t\tprivate readonly cwd: string,\n\t\tprivate readonly settings: Settings | undefined,\n\t\tprivate skillPaths: string[] = [],\n\t\t/**\n\t\t * Available models used to derive default model-category mappings when a tier\n\t\t * is not explicitly set in `settings.modelCategories` (snapshot at creation).\n\t\t */\n\t\tprivate readonly availableModels: readonly Model<Api>[] = [],\n\t\tprivate readonly maxPerKey = 2,\n\t\tprivate readonly idleTtlMs = 30_000,\n\t\t/** Spawn command override (tests inject a fake RPC child); defaults to the real spawn command. */\n\t\tprivate readonly spawnCommand?: { executable: string; prefixArgs: string[] },\n\t) {}\n\n\tupdateSkillPaths(paths: string[]): void {\n\t\tthis.skillPaths = [...paths];\n\t}\n\n\tprivate getRegistry(): AgentRegistry {\n\t\tif (!this.registry) this.registry = loadAgentRegistry({ cwd: this.cwd });\n\t\treturn this.registry;\n\t}\n\n\t/** A registry agent is poolable when it is not a fork agent (fork needs a forked session). */\n\tisPoolable(agentType: string): boolean {\n\t\tconst def = this.getRegistry().get(agentType);\n\t\treturn def !== undefined && def.fork !== true;\n\t}\n\n\t/** Stable key for one worker configuration. */\n\tprivate keyFor(options: WarmDispatchOptions): string {\n\t\tconst resolved = options.model\n\t\t\t? resolveModelReference(options.model, this.settings, this.availableModels)\n\t\t\t: undefined;\n\t\treturn `${options.agentType}::${resolved ?? \"default\"}::${options.provider ?? \"default\"}`;\n\t}\n\n\t/** Environment for a warm child: depth stamp + MCP skip, mirroring SubagentPool.childSpawnEnv. */\n\tprivate childEnv(agentType: string): NodeJS.ProcessEnv {\n\t\tconst env: NodeJS.ProcessEnv = {\n\t\t\t...process.env,\n\t\t\t[SUBAGENT_DEPTH_ENV]: String(currentSubagentDepth(process.env) + 1),\n\t\t};\n\t\tconst def = this.getRegistry().get(agentType);\n\t\tif (!toolAllowlistNeedsMcp(def?.tools)) env[SUBAGENT_SKIP_MCP_ENV] = \"1\";\n\t\t// A child never defers MCP schemas: if it needs MCP it eager-registers its\n\t\t// allowlisted tools at dispatch so they are immediately callable (spec §2).\n\t\tdelete env[DEFER_MCP_SCHEMAS_ENV];\n\t\treturn env;\n\t}\n\n\t/**\n\t * Run a task on a warm worker end to end: acquire (reuse or boot), run, then\n\t * release back to the pool. Throws WarmWorkerError on infra failure so the\n\t * caller can fall back to the cold pool.\n\t */\n\tasync dispatch(\n\t\tprompt: string,\n\t\toptions: WarmDispatchOptions,\n\t\tonActivity?: WarmProgressCallback,\n\t): Promise<WarmRunResult> {\n\t\tif (this.disposed) throw new WarmWorkerError(\"warm pool disposed\");\n\t\tconst worker = await this.acquire(options);\n\t\ttry {\n\t\t\tconst result = await worker.run(prompt, onActivity);\n\t\t\tawait this.release(worker);\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\t// Infra failure: the worker already marked itself dead; drop it entirely.\n\t\t\tawait this.discard(worker);\n\t\t\tthrow error instanceof WarmWorkerError ? error : new WarmWorkerError(String(error));\n\t\t}\n\t}\n\n\tprivate async acquire(options: WarmDispatchOptions): Promise<WarmSubagentWorker> {\n\t\tconst key = this.keyFor(options);\n\t\tconst pool = this.idle.get(key);\n\t\twhile (pool && pool.length > 0) {\n\t\t\tconst worker = pool.pop()!;\n\t\t\tthis.clearReclaim(worker);\n\t\t\tif (worker.isAlive()) return worker;\n\t\t\t// Dead idle worker (child exited while parked): drop and try the next.\n\t\t\tthis.decLive(key);\n\t\t\tawait worker.dispose();\n\t\t}\n\n\t\tconst worker = new WarmSubagentWorker(\n\t\t\tkey,\n\t\t\toptions,\n\t\t\tthis.childEnv(options.agentType),\n\t\t\tthis.getRegistry(),\n\t\t\tthis.skillPaths,\n\t\t\tthis.settings,\n\t\t\tthis.availableModels,\n\t\t\tthis.spawnCommand,\n\t\t);\n\t\tthis.incLive(key);\n\t\ttry {\n\t\t\tawait worker.start();\n\t\t} catch (error) {\n\t\t\tthis.decLive(key);\n\t\t\tthrow error;\n\t\t}\n\t\treturn worker;\n\t}\n\n\tprivate async release(worker: WarmSubagentWorker): Promise<void> {\n\t\tif (this.disposed || !worker.isAlive()) {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait worker.reset();\n\t\t} catch {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\tconst key = worker.key;\n\t\tconst pool = this.idle.get(key) ?? [];\n\t\tif (pool.length >= this.maxPerKey) {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\tpool.push(worker);\n\t\tthis.idle.set(key, pool);\n\t\tthis.armReclaim(worker);\n\t}\n\n\tprivate async discard(worker: WarmSubagentWorker): Promise<void> {\n\t\tthis.clearReclaim(worker);\n\t\tthis.decLive(worker.key);\n\t\tawait worker.dispose();\n\t}\n\n\tprivate armReclaim(worker: WarmSubagentWorker): void {\n\t\tconst timer = setTimeout(() => {\n\t\t\tconst pool = this.idle.get(worker.key);\n\t\t\tif (pool) {\n\t\t\t\tconst idx = pool.indexOf(worker);\n\t\t\t\tif (idx !== -1) pool.splice(idx, 1);\n\t\t\t}\n\t\t\tvoid this.discard(worker);\n\t\t}, this.idleTtlMs);\n\t\ttimer.unref?.();\n\t\tthis.reclaimTimers.set(worker, timer);\n\t}\n\n\tprivate clearReclaim(worker: WarmSubagentWorker): void {\n\t\tconst timer = this.reclaimTimers.get(worker);\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.reclaimTimers.delete(worker);\n\t\t}\n\t}\n\n\tprivate incLive(key: string): void {\n\t\tthis.liveCount.set(key, (this.liveCount.get(key) ?? 0) + 1);\n\t}\n\n\tprivate decLive(key: string): void {\n\t\tconst n = (this.liveCount.get(key) ?? 1) - 1;\n\t\tif (n <= 0) this.liveCount.delete(key);\n\t\telse this.liveCount.set(key, n);\n\t}\n\n\t/** Number of currently idle (parked) workers — exposed for tests/diagnostics. */\n\tidleCount(): number {\n\t\tlet total = 0;\n\t\tfor (const pool of this.idle.values()) total += pool.length;\n\t\treturn total;\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\t\tconst all: WarmSubagentWorker[] = [];\n\t\tfor (const pool of this.idle.values()) all.push(...pool);\n\t\tthis.idle.clear();\n\t\tfor (const timer of this.reclaimTimers.values()) clearTimeout(timer);\n\t\tthis.reclaimTimers.clear();\n\t\tthis.liveCount.clear();\n\t\tawait Promise.all(all.map((w) => w.dispose()));\n\t}\n}\n\n/**\n * Build the RPC child's CLI args for a warm worker. Mirrors the agent-config\n * portion of SubagentPool.buildArgs (system prompt, tools, model/provider, turn\n * cap, skills) but omits the one-shot `--mode json` / `--session` / `--task-id`\n * bits, since the worker runs persistently in RPC mode and resets via new_session.\n */\nfunction buildWorkerArgs(\n\toptions: WarmDispatchOptions,\n\tregistry: AgentRegistry,\n\tskillPaths: readonly string[],\n\tsettings: Settings | undefined,\n\tavailableModels: readonly Model<Api>[],\n): string[] {\n\tconst args: string[] = [];\n\tconst def = registry.get(options.agentType);\n\n\tif (def?.prompt) args.push(\"--system-prompt\", def.prompt);\n\n\tconst childDepth = currentSubagentDepth(process.env) + 1;\n\tconst canChildDelegate = def?.delegate === true && childDepth < resolveMaxSubagentDepth(undefined, process.env);\n\n\tconst tools = def?.tools ? [...def.tools] : undefined;\n\tif (canChildDelegate && tools) {\n\t\tfor (const t of [\"Task\", \"TaskOutput\"]) if (!tools.includes(t)) tools.push(t);\n\t}\n\tif (tools && tools.length > 0) args.push(\"--tools\", tools.join(\",\"));\n\tif (def?.disallowedTools && def.disallowedTools.length > 0) {\n\t\targs.push(\"--disallowed-tools\", def.disallowedTools.join(\",\"));\n\t}\n\tif (canChildDelegate) {\n\t\targs.push(\"--enable-subagents\");\n\t\tif (def?.delegateTo && def.delegateTo.length > 0) args.push(\"--delegate-allow\", def.delegateTo.join(\",\"));\n\t}\n\n\t// Model precedence matches the cold path: a pinned (non-inherit) agent model\n\t// wins, else the requested model/category, resolved to a concrete id.\n\tconst explicitModel = def?.model && def.model !== MODEL_INHERIT ? def.model : undefined;\n\tconst rawModel = explicitModel ?? options.model;\n\tconst modelToUse = rawModel ? resolveModelReference(rawModel, settings, availableModels) : undefined;\n\tif (modelToUse) args.push(\"--model\", modelToUse);\n\tif (options.provider) args.push(\"--provider\", options.provider);\n\n\tconst maxTurns = def?.maxTurns && def.maxTurns > 0 ? def.maxTurns : DEFAULT_SUBAGENT_MAX_TURNS;\n\targs.push(\"--max-turns\", String(maxTurns));\n\n\tfor (const skillPath of skillPaths) args.push(\"--skill\", skillPath);\n\n\treturn args;\n}\n\n/**\n * Scan a completed run's events for a turn that ended in error/abort, returning\n * its message (or a generic marker) so the caller can report a task failure\n * without re-reading the transcript. Returns undefined for a clean run.\n */\nfunction firstTurnError(events: readonly AgentEvent[]): string | undefined {\n\tfor (const event of events) {\n\t\tconst e = event as { type?: string; message?: { stopReason?: string; errorMessage?: string } };\n\t\tif (e.type === \"turn_end\" && (e.message?.stopReason === \"error\" || e.message?.stopReason === \"aborted\")) {\n\t\t\treturn e.message.errorMessage || `turn ${e.message.stopReason}`;\n\t\t}\n\t}\n\treturn undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"warm-subagent-pool.d.ts","sourceRoot":"","sources":["../../src/core/warm-subagent-pool.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Warm subagent worker pool (experimental, opt-in via `--warm-subagents` /\n * settings.warmSubagents; default off).\n *\n * The cold path (SubagentPool) re-execs the whole CLI for every dispatch, paying\n * a full module-load + resource-graph boot each time. A warm worker instead keeps\n * a long-lived child running in RPC mode and hands it one task at a time over the\n * existing JSON-line protocol: `new_session` resets its conversation between\n * tasks, `prompt` runs the task, and `agent_end` on the event stream signals\n * completion, after which the answer + usage are pulled inline (no result.json\n * disk round-trip, no OutputVerifier). The first task per worker still pays the\n * boot; every reuse after that skips it.\n *\n * Scope/limits (deliberately conservative — see the dispatch integration):\n * - Workers are pinned per agent type: RPC has no per-prompt system-prompt/tools\n * swap, so each (agentType, model, provider) is its own worker config.\n * - Only non-resume, non-fork dispatches are eligible; resume/fork need a\n * persisted/forked session the warm path does not own.\n * - Any worker/infra failure falls back to the cold pool, so enabling this can\n * only change latency, never whether a task can run.\n */\n\nimport type { AgentEvent } from \"@kolisachint/hoocode-agent-core\";\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport { getSubagentSpawnCommand } from \"../config.js\";\nimport { RpcClient } from \"../modes/rpc/rpc-client.js\";\nimport { MODEL_INHERIT } from \"./agent-frontmatter.js\";\nimport { type AgentRegistry, loadAgentRegistry } from \"./agent-registry.js\";\nimport { resolveModelReference } from \"./model-categories.js\";\nimport type { Settings } from \"./settings-manager.js\";\nimport {\n\tcurrentSubagentDepth,\n\tDEFER_MCP_SCHEMAS_ENV,\n\tresolveMaxSubagentDepth,\n\tSUBAGENT_DEPTH_ENV,\n\tSUBAGENT_SKIP_MCP_ENV,\n\ttoolAllowlistNeedsMcp,\n} from \"./subagent-depth.js\";\nimport { DEFAULT_SUBAGENT_MAX_TURNS } from \"./subagent-pool.js\";\n\n/** Usage totals pulled from a worker after a task, shaped like SubagentResultFile.usage. */\nexport interface WarmUsage {\n\tinput: number;\n\toutput: number;\n\tcacheRead: number;\n\tcacheWrite: number;\n\tcost: number;\n}\n\n/** Outcome of running one task on a warm worker. */\nexport interface WarmRunResult {\n\tok: boolean;\n\tstatus: \"complete\" | \"failed\";\n\t/** The subagent's final assistant text (its answer to the caller). */\n\tsummary: string;\n\tusage?: WarmUsage;\n\terror?: string;\n}\n\n/** Per-dispatch inputs that select and configure a worker. */\nexport interface WarmDispatchOptions {\n\tagentType: string;\n\tcwd: string;\n\t/** Model id or category (fast/standard/capable); resolved to a concrete id. */\n\tmodel?: string;\n\tprovider?: string;\n}\n\n/** Reports the tool a warm worker is currently running (\"\" = idle between tools). */\nexport type WarmProgressCallback = (activity: string) => void;\n\n/** A run failed at the infrastructure level (worker crash, timeout, protocol error). */\nexport class WarmWorkerError extends Error {}\n\n/** Default per-task wait before declaring a warm run stalled and failing over to cold. */\nconst WARM_RUN_TIMEOUT_MS = 180_000;\n\n/**\n * One long-lived RPC child pinned to a single agent-type configuration. Reused\n * across tasks via reset(); a crash makes it not-alive so the pool discards it.\n */\nexport class WarmSubagentWorker {\n\tprivate readonly client: RpcClient;\n\tprivate alive = true;\n\n\tconstructor(\n\t\treadonly key: string,\n\t\toptions: WarmDispatchOptions,\n\t\tprivate readonly env: NodeJS.ProcessEnv,\n\t\tregistry: AgentRegistry,\n\t\tskillPaths: readonly string[],\n\t\tsettings: Settings | undefined,\n\t\tavailableModels: readonly Model<Api>[],\n\t\t/** Spawn command override (tests inject a fake RPC child); defaults to the real spawn command. */\n\t\tspawnCommand?: { executable: string; prefixArgs: string[] },\n\t) {\n\t\tconst { executable, prefixArgs } = spawnCommand ?? getSubagentSpawnCommand();\n\t\tthis.client = new RpcClient({\n\t\t\texecutable,\n\t\t\tprefixArgs,\n\t\t\tcwd: options.cwd,\n\t\t\tenv: this.env as Record<string, string>,\n\t\t\targs: buildWorkerArgs(options, registry, skillPaths, settings, availableModels),\n\t\t});\n\t}\n\n\t/** Boot the child. Throws (as WarmWorkerError) if it fails to come up. */\n\tasync start(): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.start();\n\t\t} catch (error) {\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tisAlive(): boolean {\n\t\treturn this.alive;\n\t}\n\n\t/**\n\t * Run one task to completion and return its answer + usage. Throws\n\t * WarmWorkerError on an infra failure (crash/timeout/protocol) so the caller\n\t * can fall back to the cold pool; a task that ran but reported failure returns\n\t * `{ ok: false }` instead (no fall back — the work was actually done).\n\t */\n\tasync run(\n\t\tprompt: string,\n\t\tonActivity?: WarmProgressCallback,\n\t\ttimeoutMs = WARM_RUN_TIMEOUT_MS,\n\t): Promise<WarmRunResult> {\n\t\tif (!this.alive) throw new WarmWorkerError(\"worker is not alive\");\n\t\t// Mirror the cold pool's coarse progress: report the tool the child is\n\t\t// currently running so a warm dispatch's task row reads \"⋯ grep\" rather than a\n\t\t// static \"running…\". Cleared between tools and at turn end.\n\t\tconst detachProgress = onActivity ? this.tapProgress(onActivity) : undefined;\n\t\ttry {\n\t\t\tconst events = await this.client.promptAndWait(prompt, undefined, timeoutMs);\n\t\t\tconst failure = firstTurnError(events);\n\t\t\tconst summary = (await this.client.getLastAssistantText()) ?? \"\";\n\t\t\tconst usage = await this.readUsage();\n\t\t\tif (failure) {\n\t\t\t\treturn { ok: false, status: \"failed\", summary, usage, error: failure };\n\t\t\t}\n\t\t\treturn { ok: true, status: \"complete\", summary, usage };\n\t\t} catch (error) {\n\t\t\t// A prompt/await failure means the child is no longer trustworthy: mark it\n\t\t\t// dead so the pool discards it, and signal infra failure for fallback.\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t} finally {\n\t\t\tdetachProgress?.();\n\t\t\tonActivity?.(\"\");\n\t\t}\n\t}\n\n\t/** Forward the child's coarse tool-lifecycle events to an activity callback. */\n\tprivate tapProgress(onActivity: WarmProgressCallback): () => void {\n\t\treturn this.client.onEvent((event) => {\n\t\t\tconst e = event as { type?: string; toolName?: string };\n\t\t\tif (e.type === \"tool_execution_start\") onActivity(typeof e.toolName === \"string\" ? e.toolName : \"\");\n\t\t\telse if (e.type === \"tool_execution_end\" || e.type === \"turn_end\") onActivity(\"\");\n\t\t});\n\t}\n\n\t/** Reset the worker's conversation so it can take the next task cleanly. */\n\tasync reset(): Promise<void> {\n\t\tif (!this.alive) return;\n\t\ttry {\n\t\t\tawait this.client.newSession();\n\t\t} catch (error) {\n\t\t\t// A reset failure leaves the worker in an unknown state — retire it.\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tthis.alive = false;\n\t\tawait this.client.stop().catch(() => {});\n\t}\n\n\tprivate async readUsage(): Promise<WarmUsage | undefined> {\n\t\ttry {\n\t\t\tconst stats = await this.client.getSessionStats();\n\t\t\treturn {\n\t\t\t\tinput: stats.tokens.input,\n\t\t\t\toutput: stats.tokens.output,\n\t\t\t\tcacheRead: stats.tokens.cacheRead,\n\t\t\t\tcacheWrite: stats.tokens.cacheWrite,\n\t\t\t\tcost: stats.cost,\n\t\t\t};\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n}\n\n/**\n * Pool of warm workers keyed by agent-type configuration. Hands out an idle\n * worker (or boots a new one up to a per-key cap), and on release resets the\n * worker and returns it to the idle set with an idle-TTL reclaim timer.\n */\nexport class WarmSubagentPool {\n\tprivate idle = new Map<string, WarmSubagentWorker[]>();\n\tprivate reclaimTimers = new Map<WarmSubagentWorker, ReturnType<typeof setTimeout>>();\n\tprivate liveCount = new Map<string, number>();\n\tprivate disposed = false;\n\tprivate registry?: AgentRegistry;\n\n\tconstructor(\n\t\tprivate readonly cwd: string,\n\t\tprivate readonly settings: Settings | undefined,\n\t\tprivate skillPaths: string[] = [],\n\t\t/**\n\t\t * Available models used to derive default model-category mappings when a tier\n\t\t * is not explicitly set in `settings.modelCategories` (snapshot at creation).\n\t\t */\n\t\tprivate readonly availableModels: readonly Model<Api>[] = [],\n\t\tprivate readonly maxPerKey = 2,\n\t\tprivate readonly idleTtlMs = 30_000,\n\t\t/** Spawn command override (tests inject a fake RPC child); defaults to the real spawn command. */\n\t\tprivate readonly spawnCommand?: { executable: string; prefixArgs: string[] },\n\t) {}\n\n\tupdateSkillPaths(paths: string[]): void {\n\t\tthis.skillPaths = [...paths];\n\t}\n\n\tprivate getRegistry(): AgentRegistry {\n\t\tif (!this.registry) this.registry = loadAgentRegistry({ cwd: this.cwd });\n\t\treturn this.registry;\n\t}\n\n\t/** A registry agent is poolable when it is not a fork agent (fork needs a forked session). */\n\tisPoolable(agentType: string): boolean {\n\t\tconst def = this.getRegistry().get(agentType);\n\t\treturn def !== undefined && def.fork !== true;\n\t}\n\n\t/** Stable key for one worker configuration. */\n\tprivate keyFor(options: WarmDispatchOptions): string {\n\t\tconst resolved = options.model\n\t\t\t? resolveModelReference(options.model, this.settings, this.availableModels)\n\t\t\t: undefined;\n\t\treturn `${options.agentType}::${resolved ?? \"default\"}::${options.provider ?? \"default\"}`;\n\t}\n\n\t/** Environment for a warm child: depth stamp + MCP skip, mirroring SubagentPool.childSpawnEnv. */\n\tprivate childEnv(agentType: string): NodeJS.ProcessEnv {\n\t\tconst env: NodeJS.ProcessEnv = {\n\t\t\t...process.env,\n\t\t\t[SUBAGENT_DEPTH_ENV]: String(currentSubagentDepth(process.env) + 1),\n\t\t};\n\t\tconst def = this.getRegistry().get(agentType);\n\t\tif (!toolAllowlistNeedsMcp(def?.tools)) env[SUBAGENT_SKIP_MCP_ENV] = \"1\";\n\t\t// A child never defers MCP schemas: if it needs MCP it eager-registers its\n\t\t// allowlisted tools at dispatch so they are immediately callable (spec §2).\n\t\tdelete env[DEFER_MCP_SCHEMAS_ENV];\n\t\treturn env;\n\t}\n\n\t/**\n\t * Run a task on a warm worker end to end: acquire (reuse or boot), run, then\n\t * release back to the pool. Throws WarmWorkerError on infra failure so the\n\t * caller can fall back to the cold pool.\n\t */\n\tasync dispatch(\n\t\tprompt: string,\n\t\toptions: WarmDispatchOptions,\n\t\tonActivity?: WarmProgressCallback,\n\t): Promise<WarmRunResult> {\n\t\tif (this.disposed) throw new WarmWorkerError(\"warm pool disposed\");\n\t\tconst worker = await this.acquire(options);\n\t\ttry {\n\t\t\tconst result = await worker.run(prompt, onActivity);\n\t\t\tawait this.release(worker);\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\t// Infra failure: the worker already marked itself dead; drop it entirely.\n\t\t\tawait this.discard(worker);\n\t\t\tthrow error instanceof WarmWorkerError ? error : new WarmWorkerError(String(error));\n\t\t}\n\t}\n\n\tprivate async acquire(options: WarmDispatchOptions): Promise<WarmSubagentWorker> {\n\t\tconst key = this.keyFor(options);\n\t\tconst pool = this.idle.get(key);\n\t\twhile (pool && pool.length > 0) {\n\t\t\tconst worker = pool.pop()!;\n\t\t\tthis.clearReclaim(worker);\n\t\t\tif (worker.isAlive()) return worker;\n\t\t\t// Dead idle worker (child exited while parked): drop and try the next.\n\t\t\tthis.decLive(key);\n\t\t\tawait worker.dispose();\n\t\t}\n\n\t\tconst worker = new WarmSubagentWorker(\n\t\t\tkey,\n\t\t\toptions,\n\t\t\tthis.childEnv(options.agentType),\n\t\t\tthis.getRegistry(),\n\t\t\tthis.skillPaths,\n\t\t\tthis.settings,\n\t\t\tthis.availableModels,\n\t\t\tthis.spawnCommand,\n\t\t);\n\t\tthis.incLive(key);\n\t\ttry {\n\t\t\tawait worker.start();\n\t\t} catch (error) {\n\t\t\tthis.decLive(key);\n\t\t\tthrow error;\n\t\t}\n\t\treturn worker;\n\t}\n\n\tprivate async release(worker: WarmSubagentWorker): Promise<void> {\n\t\tif (this.disposed || !worker.isAlive()) {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait worker.reset();\n\t\t} catch {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\tconst key = worker.key;\n\t\tconst pool = this.idle.get(key) ?? [];\n\t\tif (pool.length >= this.maxPerKey) {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\tpool.push(worker);\n\t\tthis.idle.set(key, pool);\n\t\tthis.armReclaim(worker);\n\t}\n\n\tprivate async discard(worker: WarmSubagentWorker): Promise<void> {\n\t\tthis.clearReclaim(worker);\n\t\tthis.decLive(worker.key);\n\t\tawait worker.dispose();\n\t}\n\n\tprivate armReclaim(worker: WarmSubagentWorker): void {\n\t\tconst timer = setTimeout(() => {\n\t\t\tconst pool = this.idle.get(worker.key);\n\t\t\tif (pool) {\n\t\t\t\tconst idx = pool.indexOf(worker);\n\t\t\t\tif (idx !== -1) pool.splice(idx, 1);\n\t\t\t}\n\t\t\tvoid this.discard(worker);\n\t\t}, this.idleTtlMs);\n\t\ttimer.unref?.();\n\t\tthis.reclaimTimers.set(worker, timer);\n\t}\n\n\tprivate clearReclaim(worker: WarmSubagentWorker): void {\n\t\tconst timer = this.reclaimTimers.get(worker);\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.reclaimTimers.delete(worker);\n\t\t}\n\t}\n\n\tprivate incLive(key: string): void {\n\t\tthis.liveCount.set(key, (this.liveCount.get(key) ?? 0) + 1);\n\t}\n\n\tprivate decLive(key: string): void {\n\t\tconst n = (this.liveCount.get(key) ?? 1) - 1;\n\t\tif (n <= 0) this.liveCount.delete(key);\n\t\telse this.liveCount.set(key, n);\n\t}\n\n\t/** Number of currently idle (parked) workers — exposed for tests/diagnostics. */\n\tidleCount(): number {\n\t\tlet total = 0;\n\t\tfor (const pool of this.idle.values()) total += pool.length;\n\t\treturn total;\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\t\tconst all: WarmSubagentWorker[] = [];\n\t\tfor (const pool of this.idle.values()) all.push(...pool);\n\t\tthis.idle.clear();\n\t\tfor (const timer of this.reclaimTimers.values()) clearTimeout(timer);\n\t\tthis.reclaimTimers.clear();\n\t\tthis.liveCount.clear();\n\t\tawait Promise.all(all.map((w) => w.dispose()));\n\t}\n}\n\n/**\n * Build the RPC child's CLI args for a warm worker. Mirrors the agent-config\n * portion of SubagentPool.buildArgs (system prompt, tools, model/provider, turn\n * cap, skills) but omits the one-shot `--mode json` / `--session` / `--task-id`\n * bits, since the worker runs persistently in RPC mode and resets via new_session.\n */\nfunction buildWorkerArgs(\n\toptions: WarmDispatchOptions,\n\tregistry: AgentRegistry,\n\tskillPaths: readonly string[],\n\tsettings: Settings | undefined,\n\tavailableModels: readonly Model<Api>[],\n): string[] {\n\tconst args: string[] = [];\n\tconst def = registry.get(options.agentType);\n\n\tif (def?.prompt) args.push(\"--system-prompt\", def.prompt);\n\n\tconst childDepth = currentSubagentDepth(process.env) + 1;\n\tconst canChildDelegate = def?.delegate === true && childDepth < resolveMaxSubagentDepth(undefined, process.env);\n\n\tconst tools = def?.tools ? [...def.tools] : undefined;\n\tif (canChildDelegate && tools) {\n\t\tfor (const t of [\"Task\", \"TaskOutput\"]) if (!tools.includes(t)) tools.push(t);\n\t}\n\tif (tools && tools.length > 0) args.push(\"--tools\", tools.join(\",\"));\n\tif (def?.disallowedTools && def.disallowedTools.length > 0) {\n\t\targs.push(\"--disallowed-tools\", def.disallowedTools.join(\",\"));\n\t}\n\tif (canChildDelegate) {\n\t\targs.push(\"--enable-subagents\");\n\t\tif (def?.delegateTo && def.delegateTo.length > 0) args.push(\"--delegate-allow\", def.delegateTo.join(\",\"));\n\t}\n\n\t// Model precedence matches the cold path: a pinned (non-inherit) agent model\n\t// wins, else the requested model/category, resolved to a concrete id.\n\tconst explicitModel = def?.model && def.model !== MODEL_INHERIT ? def.model : undefined;\n\tconst rawModel = explicitModel ?? options.model;\n\tconst modelToUse = rawModel ? resolveModelReference(rawModel, settings, availableModels) : undefined;\n\tif (modelToUse) args.push(\"--model\", modelToUse);\n\t// Only pass --provider when the model doesn't already encode the provider\n\t// (e.g. \"anthropic/claude-sonnet-4-5\"). When both --model and --provider are\n\t// present, --provider wins and filters candidates to that provider, which\n\t// breaks models whose IDs contain a provider prefix from a different provider.\n\tif (options.provider && (!modelToUse || !modelToUse.includes(\"/\"))) args.push(\"--provider\", options.provider);\n\n\tconst maxTurns = def?.maxTurns && def.maxTurns > 0 ? def.maxTurns : DEFAULT_SUBAGENT_MAX_TURNS;\n\targs.push(\"--max-turns\", String(maxTurns));\n\n\tfor (const skillPath of skillPaths) args.push(\"--skill\", skillPath);\n\n\treturn args;\n}\n\n/**\n * Scan a completed run's events for a turn that ended in error/abort, returning\n * its message (or a generic marker) so the caller can report a task failure\n * without re-reading the transcript. Returns undefined for a clean run.\n */\nfunction firstTurnError(events: readonly AgentEvent[]): string | undefined {\n\tfor (const event of events) {\n\t\tconst e = event as { type?: string; message?: { stopReason?: string; errorMessage?: string } };\n\t\tif (e.type === \"turn_end\" && (e.message?.stopReason === \"error\" || e.message?.stopReason === \"aborted\")) {\n\t\t\treturn e.message.errorMessage || `turn ${e.message.stopReason}`;\n\t\t}\n\t}\n\treturn undefined;\n}\n"]}
|
|
@@ -370,7 +370,11 @@ function buildWorkerArgs(options, registry, skillPaths, settings, availableModel
|
|
|
370
370
|
const modelToUse = rawModel ? resolveModelReference(rawModel, settings, availableModels) : undefined;
|
|
371
371
|
if (modelToUse)
|
|
372
372
|
args.push("--model", modelToUse);
|
|
373
|
-
|
|
373
|
+
// Only pass --provider when the model doesn't already encode the provider
|
|
374
|
+
// (e.g. "anthropic/claude-sonnet-4-5"). When both --model and --provider are
|
|
375
|
+
// present, --provider wins and filters candidates to that provider, which
|
|
376
|
+
// breaks models whose IDs contain a provider prefix from a different provider.
|
|
377
|
+
if (options.provider && (!modelToUse || !modelToUse.includes("/")))
|
|
374
378
|
args.push("--provider", options.provider);
|
|
375
379
|
const maxTurns = def?.maxTurns && def.maxTurns > 0 ? def.maxTurns : DEFAULT_SUBAGENT_MAX_TURNS;
|
|
376
380
|
args.push("--max-turns", String(maxTurns));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"warm-subagent-pool.js","sourceRoot":"","sources":["../../src/core/warm-subagent-pool.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Warm subagent worker pool (experimental, opt-in via `--warm-subagents` /\n * settings.warmSubagents; default off).\n *\n * The cold path (SubagentPool) re-execs the whole CLI for every dispatch, paying\n * a full module-load + resource-graph boot each time. A warm worker instead keeps\n * a long-lived child running in RPC mode and hands it one task at a time over the\n * existing JSON-line protocol: `new_session` resets its conversation between\n * tasks, `prompt` runs the task, and `agent_end` on the event stream signals\n * completion, after which the answer + usage are pulled inline (no result.json\n * disk round-trip, no OutputVerifier). The first task per worker still pays the\n * boot; every reuse after that skips it.\n *\n * Scope/limits (deliberately conservative — see the dispatch integration):\n * - Workers are pinned per agent type: RPC has no per-prompt system-prompt/tools\n * swap, so each (agentType, model, provider) is its own worker config.\n * - Only non-resume, non-fork dispatches are eligible; resume/fork need a\n * persisted/forked session the warm path does not own.\n * - Any worker/infra failure falls back to the cold pool, so enabling this can\n * only change latency, never whether a task can run.\n */\n\nimport type { AgentEvent } from \"@kolisachint/hoocode-agent-core\";\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport { getSubagentSpawnCommand } from \"../config.js\";\nimport { RpcClient } from \"../modes/rpc/rpc-client.js\";\nimport { MODEL_INHERIT } from \"./agent-frontmatter.js\";\nimport { type AgentRegistry, loadAgentRegistry } from \"./agent-registry.js\";\nimport { resolveModelReference } from \"./model-categories.js\";\nimport type { Settings } from \"./settings-manager.js\";\nimport {\n\tcurrentSubagentDepth,\n\tDEFER_MCP_SCHEMAS_ENV,\n\tresolveMaxSubagentDepth,\n\tSUBAGENT_DEPTH_ENV,\n\tSUBAGENT_SKIP_MCP_ENV,\n\ttoolAllowlistNeedsMcp,\n} from \"./subagent-depth.js\";\nimport { DEFAULT_SUBAGENT_MAX_TURNS } from \"./subagent-pool.js\";\n\n/** Usage totals pulled from a worker after a task, shaped like SubagentResultFile.usage. */\nexport interface WarmUsage {\n\tinput: number;\n\toutput: number;\n\tcacheRead: number;\n\tcacheWrite: number;\n\tcost: number;\n}\n\n/** Outcome of running one task on a warm worker. */\nexport interface WarmRunResult {\n\tok: boolean;\n\tstatus: \"complete\" | \"failed\";\n\t/** The subagent's final assistant text (its answer to the caller). */\n\tsummary: string;\n\tusage?: WarmUsage;\n\terror?: string;\n}\n\n/** Per-dispatch inputs that select and configure a worker. */\nexport interface WarmDispatchOptions {\n\tagentType: string;\n\tcwd: string;\n\t/** Model id or category (fast/standard/capable); resolved to a concrete id. */\n\tmodel?: string;\n\tprovider?: string;\n}\n\n/** Reports the tool a warm worker is currently running (\"\" = idle between tools). */\nexport type WarmProgressCallback = (activity: string) => void;\n\n/** A run failed at the infrastructure level (worker crash, timeout, protocol error). */\nexport class WarmWorkerError extends Error {}\n\n/** Default per-task wait before declaring a warm run stalled and failing over to cold. */\nconst WARM_RUN_TIMEOUT_MS = 180_000;\n\n/**\n * One long-lived RPC child pinned to a single agent-type configuration. Reused\n * across tasks via reset(); a crash makes it not-alive so the pool discards it.\n */\nexport class WarmSubagentWorker {\n\tprivate readonly client: RpcClient;\n\tprivate alive = true;\n\n\tconstructor(\n\t\treadonly key: string,\n\t\toptions: WarmDispatchOptions,\n\t\tprivate readonly env: NodeJS.ProcessEnv,\n\t\tregistry: AgentRegistry,\n\t\tskillPaths: readonly string[],\n\t\tsettings: Settings | undefined,\n\t\tavailableModels: readonly Model<Api>[],\n\t\t/** Spawn command override (tests inject a fake RPC child); defaults to the real spawn command. */\n\t\tspawnCommand?: { executable: string; prefixArgs: string[] },\n\t) {\n\t\tconst { executable, prefixArgs } = spawnCommand ?? getSubagentSpawnCommand();\n\t\tthis.client = new RpcClient({\n\t\t\texecutable,\n\t\t\tprefixArgs,\n\t\t\tcwd: options.cwd,\n\t\t\tenv: this.env as Record<string, string>,\n\t\t\targs: buildWorkerArgs(options, registry, skillPaths, settings, availableModels),\n\t\t});\n\t}\n\n\t/** Boot the child. Throws (as WarmWorkerError) if it fails to come up. */\n\tasync start(): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.start();\n\t\t} catch (error) {\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tisAlive(): boolean {\n\t\treturn this.alive;\n\t}\n\n\t/**\n\t * Run one task to completion and return its answer + usage. Throws\n\t * WarmWorkerError on an infra failure (crash/timeout/protocol) so the caller\n\t * can fall back to the cold pool; a task that ran but reported failure returns\n\t * `{ ok: false }` instead (no fall back — the work was actually done).\n\t */\n\tasync run(\n\t\tprompt: string,\n\t\tonActivity?: WarmProgressCallback,\n\t\ttimeoutMs = WARM_RUN_TIMEOUT_MS,\n\t): Promise<WarmRunResult> {\n\t\tif (!this.alive) throw new WarmWorkerError(\"worker is not alive\");\n\t\t// Mirror the cold pool's coarse progress: report the tool the child is\n\t\t// currently running so a warm dispatch's task row reads \"⋯ grep\" rather than a\n\t\t// static \"running…\". Cleared between tools and at turn end.\n\t\tconst detachProgress = onActivity ? this.tapProgress(onActivity) : undefined;\n\t\ttry {\n\t\t\tconst events = await this.client.promptAndWait(prompt, undefined, timeoutMs);\n\t\t\tconst failure = firstTurnError(events);\n\t\t\tconst summary = (await this.client.getLastAssistantText()) ?? \"\";\n\t\t\tconst usage = await this.readUsage();\n\t\t\tif (failure) {\n\t\t\t\treturn { ok: false, status: \"failed\", summary, usage, error: failure };\n\t\t\t}\n\t\t\treturn { ok: true, status: \"complete\", summary, usage };\n\t\t} catch (error) {\n\t\t\t// A prompt/await failure means the child is no longer trustworthy: mark it\n\t\t\t// dead so the pool discards it, and signal infra failure for fallback.\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t} finally {\n\t\t\tdetachProgress?.();\n\t\t\tonActivity?.(\"\");\n\t\t}\n\t}\n\n\t/** Forward the child's coarse tool-lifecycle events to an activity callback. */\n\tprivate tapProgress(onActivity: WarmProgressCallback): () => void {\n\t\treturn this.client.onEvent((event) => {\n\t\t\tconst e = event as { type?: string; toolName?: string };\n\t\t\tif (e.type === \"tool_execution_start\") onActivity(typeof e.toolName === \"string\" ? e.toolName : \"\");\n\t\t\telse if (e.type === \"tool_execution_end\" || e.type === \"turn_end\") onActivity(\"\");\n\t\t});\n\t}\n\n\t/** Reset the worker's conversation so it can take the next task cleanly. */\n\tasync reset(): Promise<void> {\n\t\tif (!this.alive) return;\n\t\ttry {\n\t\t\tawait this.client.newSession();\n\t\t} catch (error) {\n\t\t\t// A reset failure leaves the worker in an unknown state — retire it.\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tthis.alive = false;\n\t\tawait this.client.stop().catch(() => {});\n\t}\n\n\tprivate async readUsage(): Promise<WarmUsage | undefined> {\n\t\ttry {\n\t\t\tconst stats = await this.client.getSessionStats();\n\t\t\treturn {\n\t\t\t\tinput: stats.tokens.input,\n\t\t\t\toutput: stats.tokens.output,\n\t\t\t\tcacheRead: stats.tokens.cacheRead,\n\t\t\t\tcacheWrite: stats.tokens.cacheWrite,\n\t\t\t\tcost: stats.cost,\n\t\t\t};\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n}\n\n/**\n * Pool of warm workers keyed by agent-type configuration. Hands out an idle\n * worker (or boots a new one up to a per-key cap), and on release resets the\n * worker and returns it to the idle set with an idle-TTL reclaim timer.\n */\nexport class WarmSubagentPool {\n\tprivate idle = new Map<string, WarmSubagentWorker[]>();\n\tprivate reclaimTimers = new Map<WarmSubagentWorker, ReturnType<typeof setTimeout>>();\n\tprivate liveCount = new Map<string, number>();\n\tprivate disposed = false;\n\tprivate registry?: AgentRegistry;\n\n\tconstructor(\n\t\tprivate readonly cwd: string,\n\t\tprivate readonly settings: Settings | undefined,\n\t\tprivate skillPaths: string[] = [],\n\t\t/**\n\t\t * Available models used to derive default model-category mappings when a tier\n\t\t * is not explicitly set in `settings.modelCategories` (snapshot at creation).\n\t\t */\n\t\tprivate readonly availableModels: readonly Model<Api>[] = [],\n\t\tprivate readonly maxPerKey = 2,\n\t\tprivate readonly idleTtlMs = 30_000,\n\t\t/** Spawn command override (tests inject a fake RPC child); defaults to the real spawn command. */\n\t\tprivate readonly spawnCommand?: { executable: string; prefixArgs: string[] },\n\t) {}\n\n\tupdateSkillPaths(paths: string[]): void {\n\t\tthis.skillPaths = [...paths];\n\t}\n\n\tprivate getRegistry(): AgentRegistry {\n\t\tif (!this.registry) this.registry = loadAgentRegistry({ cwd: this.cwd });\n\t\treturn this.registry;\n\t}\n\n\t/** A registry agent is poolable when it is not a fork agent (fork needs a forked session). */\n\tisPoolable(agentType: string): boolean {\n\t\tconst def = this.getRegistry().get(agentType);\n\t\treturn def !== undefined && def.fork !== true;\n\t}\n\n\t/** Stable key for one worker configuration. */\n\tprivate keyFor(options: WarmDispatchOptions): string {\n\t\tconst resolved = options.model\n\t\t\t? resolveModelReference(options.model, this.settings, this.availableModels)\n\t\t\t: undefined;\n\t\treturn `${options.agentType}::${resolved ?? \"default\"}::${options.provider ?? \"default\"}`;\n\t}\n\n\t/** Environment for a warm child: depth stamp + MCP skip, mirroring SubagentPool.childSpawnEnv. */\n\tprivate childEnv(agentType: string): NodeJS.ProcessEnv {\n\t\tconst env: NodeJS.ProcessEnv = {\n\t\t\t...process.env,\n\t\t\t[SUBAGENT_DEPTH_ENV]: String(currentSubagentDepth(process.env) + 1),\n\t\t};\n\t\tconst def = this.getRegistry().get(agentType);\n\t\tif (!toolAllowlistNeedsMcp(def?.tools)) env[SUBAGENT_SKIP_MCP_ENV] = \"1\";\n\t\t// A child never defers MCP schemas: if it needs MCP it eager-registers its\n\t\t// allowlisted tools at dispatch so they are immediately callable (spec §2).\n\t\tdelete env[DEFER_MCP_SCHEMAS_ENV];\n\t\treturn env;\n\t}\n\n\t/**\n\t * Run a task on a warm worker end to end: acquire (reuse or boot), run, then\n\t * release back to the pool. Throws WarmWorkerError on infra failure so the\n\t * caller can fall back to the cold pool.\n\t */\n\tasync dispatch(\n\t\tprompt: string,\n\t\toptions: WarmDispatchOptions,\n\t\tonActivity?: WarmProgressCallback,\n\t): Promise<WarmRunResult> {\n\t\tif (this.disposed) throw new WarmWorkerError(\"warm pool disposed\");\n\t\tconst worker = await this.acquire(options);\n\t\ttry {\n\t\t\tconst result = await worker.run(prompt, onActivity);\n\t\t\tawait this.release(worker);\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\t// Infra failure: the worker already marked itself dead; drop it entirely.\n\t\t\tawait this.discard(worker);\n\t\t\tthrow error instanceof WarmWorkerError ? error : new WarmWorkerError(String(error));\n\t\t}\n\t}\n\n\tprivate async acquire(options: WarmDispatchOptions): Promise<WarmSubagentWorker> {\n\t\tconst key = this.keyFor(options);\n\t\tconst pool = this.idle.get(key);\n\t\twhile (pool && pool.length > 0) {\n\t\t\tconst worker = pool.pop()!;\n\t\t\tthis.clearReclaim(worker);\n\t\t\tif (worker.isAlive()) return worker;\n\t\t\t// Dead idle worker (child exited while parked): drop and try the next.\n\t\t\tthis.decLive(key);\n\t\t\tawait worker.dispose();\n\t\t}\n\n\t\tconst worker = new WarmSubagentWorker(\n\t\t\tkey,\n\t\t\toptions,\n\t\t\tthis.childEnv(options.agentType),\n\t\t\tthis.getRegistry(),\n\t\t\tthis.skillPaths,\n\t\t\tthis.settings,\n\t\t\tthis.availableModels,\n\t\t\tthis.spawnCommand,\n\t\t);\n\t\tthis.incLive(key);\n\t\ttry {\n\t\t\tawait worker.start();\n\t\t} catch (error) {\n\t\t\tthis.decLive(key);\n\t\t\tthrow error;\n\t\t}\n\t\treturn worker;\n\t}\n\n\tprivate async release(worker: WarmSubagentWorker): Promise<void> {\n\t\tif (this.disposed || !worker.isAlive()) {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait worker.reset();\n\t\t} catch {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\tconst key = worker.key;\n\t\tconst pool = this.idle.get(key) ?? [];\n\t\tif (pool.length >= this.maxPerKey) {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\tpool.push(worker);\n\t\tthis.idle.set(key, pool);\n\t\tthis.armReclaim(worker);\n\t}\n\n\tprivate async discard(worker: WarmSubagentWorker): Promise<void> {\n\t\tthis.clearReclaim(worker);\n\t\tthis.decLive(worker.key);\n\t\tawait worker.dispose();\n\t}\n\n\tprivate armReclaim(worker: WarmSubagentWorker): void {\n\t\tconst timer = setTimeout(() => {\n\t\t\tconst pool = this.idle.get(worker.key);\n\t\t\tif (pool) {\n\t\t\t\tconst idx = pool.indexOf(worker);\n\t\t\t\tif (idx !== -1) pool.splice(idx, 1);\n\t\t\t}\n\t\t\tvoid this.discard(worker);\n\t\t}, this.idleTtlMs);\n\t\ttimer.unref?.();\n\t\tthis.reclaimTimers.set(worker, timer);\n\t}\n\n\tprivate clearReclaim(worker: WarmSubagentWorker): void {\n\t\tconst timer = this.reclaimTimers.get(worker);\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.reclaimTimers.delete(worker);\n\t\t}\n\t}\n\n\tprivate incLive(key: string): void {\n\t\tthis.liveCount.set(key, (this.liveCount.get(key) ?? 0) + 1);\n\t}\n\n\tprivate decLive(key: string): void {\n\t\tconst n = (this.liveCount.get(key) ?? 1) - 1;\n\t\tif (n <= 0) this.liveCount.delete(key);\n\t\telse this.liveCount.set(key, n);\n\t}\n\n\t/** Number of currently idle (parked) workers — exposed for tests/diagnostics. */\n\tidleCount(): number {\n\t\tlet total = 0;\n\t\tfor (const pool of this.idle.values()) total += pool.length;\n\t\treturn total;\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\t\tconst all: WarmSubagentWorker[] = [];\n\t\tfor (const pool of this.idle.values()) all.push(...pool);\n\t\tthis.idle.clear();\n\t\tfor (const timer of this.reclaimTimers.values()) clearTimeout(timer);\n\t\tthis.reclaimTimers.clear();\n\t\tthis.liveCount.clear();\n\t\tawait Promise.all(all.map((w) => w.dispose()));\n\t}\n}\n\n/**\n * Build the RPC child's CLI args for a warm worker. Mirrors the agent-config\n * portion of SubagentPool.buildArgs (system prompt, tools, model/provider, turn\n * cap, skills) but omits the one-shot `--mode json` / `--session` / `--task-id`\n * bits, since the worker runs persistently in RPC mode and resets via new_session.\n */\nfunction buildWorkerArgs(\n\toptions: WarmDispatchOptions,\n\tregistry: AgentRegistry,\n\tskillPaths: readonly string[],\n\tsettings: Settings | undefined,\n\tavailableModels: readonly Model<Api>[],\n): string[] {\n\tconst args: string[] = [];\n\tconst def = registry.get(options.agentType);\n\n\tif (def?.prompt) args.push(\"--system-prompt\", def.prompt);\n\n\tconst childDepth = currentSubagentDepth(process.env) + 1;\n\tconst canChildDelegate = def?.delegate === true && childDepth < resolveMaxSubagentDepth(undefined, process.env);\n\n\tconst tools = def?.tools ? [...def.tools] : undefined;\n\tif (canChildDelegate && tools) {\n\t\tfor (const t of [\"Task\", \"TaskOutput\"]) if (!tools.includes(t)) tools.push(t);\n\t}\n\tif (tools && tools.length > 0) args.push(\"--tools\", tools.join(\",\"));\n\tif (def?.disallowedTools && def.disallowedTools.length > 0) {\n\t\targs.push(\"--disallowed-tools\", def.disallowedTools.join(\",\"));\n\t}\n\tif (canChildDelegate) {\n\t\targs.push(\"--enable-subagents\");\n\t\tif (def?.delegateTo && def.delegateTo.length > 0) args.push(\"--delegate-allow\", def.delegateTo.join(\",\"));\n\t}\n\n\t// Model precedence matches the cold path: a pinned (non-inherit) agent model\n\t// wins, else the requested model/category, resolved to a concrete id.\n\tconst explicitModel = def?.model && def.model !== MODEL_INHERIT ? def.model : undefined;\n\tconst rawModel = explicitModel ?? options.model;\n\tconst modelToUse = rawModel ? resolveModelReference(rawModel, settings, availableModels) : undefined;\n\tif (modelToUse) args.push(\"--model\", modelToUse);\n\tif (options.provider) args.push(\"--provider\", options.provider);\n\n\tconst maxTurns = def?.maxTurns && def.maxTurns > 0 ? def.maxTurns : DEFAULT_SUBAGENT_MAX_TURNS;\n\targs.push(\"--max-turns\", String(maxTurns));\n\n\tfor (const skillPath of skillPaths) args.push(\"--skill\", skillPath);\n\n\treturn args;\n}\n\n/**\n * Scan a completed run's events for a turn that ended in error/abort, returning\n * its message (or a generic marker) so the caller can report a task failure\n * without re-reading the transcript. Returns undefined for a clean run.\n */\nfunction firstTurnError(events: readonly AgentEvent[]): string | undefined {\n\tfor (const event of events) {\n\t\tconst e = event as { type?: string; message?: { stopReason?: string; errorMessage?: string } };\n\t\tif (e.type === \"turn_end\" && (e.message?.stopReason === \"error\" || e.message?.stopReason === \"aborted\")) {\n\t\t\treturn e.message.errorMessage || `turn ${e.message.stopReason}`;\n\t\t}\n\t}\n\treturn undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"warm-subagent-pool.js","sourceRoot":"","sources":["../../src/core/warm-subagent-pool.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Warm subagent worker pool (experimental, opt-in via `--warm-subagents` /\n * settings.warmSubagents; default off).\n *\n * The cold path (SubagentPool) re-execs the whole CLI for every dispatch, paying\n * a full module-load + resource-graph boot each time. A warm worker instead keeps\n * a long-lived child running in RPC mode and hands it one task at a time over the\n * existing JSON-line protocol: `new_session` resets its conversation between\n * tasks, `prompt` runs the task, and `agent_end` on the event stream signals\n * completion, after which the answer + usage are pulled inline (no result.json\n * disk round-trip, no OutputVerifier). The first task per worker still pays the\n * boot; every reuse after that skips it.\n *\n * Scope/limits (deliberately conservative — see the dispatch integration):\n * - Workers are pinned per agent type: RPC has no per-prompt system-prompt/tools\n * swap, so each (agentType, model, provider) is its own worker config.\n * - Only non-resume, non-fork dispatches are eligible; resume/fork need a\n * persisted/forked session the warm path does not own.\n * - Any worker/infra failure falls back to the cold pool, so enabling this can\n * only change latency, never whether a task can run.\n */\n\nimport type { AgentEvent } from \"@kolisachint/hoocode-agent-core\";\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport { getSubagentSpawnCommand } from \"../config.js\";\nimport { RpcClient } from \"../modes/rpc/rpc-client.js\";\nimport { MODEL_INHERIT } from \"./agent-frontmatter.js\";\nimport { type AgentRegistry, loadAgentRegistry } from \"./agent-registry.js\";\nimport { resolveModelReference } from \"./model-categories.js\";\nimport type { Settings } from \"./settings-manager.js\";\nimport {\n\tcurrentSubagentDepth,\n\tDEFER_MCP_SCHEMAS_ENV,\n\tresolveMaxSubagentDepth,\n\tSUBAGENT_DEPTH_ENV,\n\tSUBAGENT_SKIP_MCP_ENV,\n\ttoolAllowlistNeedsMcp,\n} from \"./subagent-depth.js\";\nimport { DEFAULT_SUBAGENT_MAX_TURNS } from \"./subagent-pool.js\";\n\n/** Usage totals pulled from a worker after a task, shaped like SubagentResultFile.usage. */\nexport interface WarmUsage {\n\tinput: number;\n\toutput: number;\n\tcacheRead: number;\n\tcacheWrite: number;\n\tcost: number;\n}\n\n/** Outcome of running one task on a warm worker. */\nexport interface WarmRunResult {\n\tok: boolean;\n\tstatus: \"complete\" | \"failed\";\n\t/** The subagent's final assistant text (its answer to the caller). */\n\tsummary: string;\n\tusage?: WarmUsage;\n\terror?: string;\n}\n\n/** Per-dispatch inputs that select and configure a worker. */\nexport interface WarmDispatchOptions {\n\tagentType: string;\n\tcwd: string;\n\t/** Model id or category (fast/standard/capable); resolved to a concrete id. */\n\tmodel?: string;\n\tprovider?: string;\n}\n\n/** Reports the tool a warm worker is currently running (\"\" = idle between tools). */\nexport type WarmProgressCallback = (activity: string) => void;\n\n/** A run failed at the infrastructure level (worker crash, timeout, protocol error). */\nexport class WarmWorkerError extends Error {}\n\n/** Default per-task wait before declaring a warm run stalled and failing over to cold. */\nconst WARM_RUN_TIMEOUT_MS = 180_000;\n\n/**\n * One long-lived RPC child pinned to a single agent-type configuration. Reused\n * across tasks via reset(); a crash makes it not-alive so the pool discards it.\n */\nexport class WarmSubagentWorker {\n\tprivate readonly client: RpcClient;\n\tprivate alive = true;\n\n\tconstructor(\n\t\treadonly key: string,\n\t\toptions: WarmDispatchOptions,\n\t\tprivate readonly env: NodeJS.ProcessEnv,\n\t\tregistry: AgentRegistry,\n\t\tskillPaths: readonly string[],\n\t\tsettings: Settings | undefined,\n\t\tavailableModels: readonly Model<Api>[],\n\t\t/** Spawn command override (tests inject a fake RPC child); defaults to the real spawn command. */\n\t\tspawnCommand?: { executable: string; prefixArgs: string[] },\n\t) {\n\t\tconst { executable, prefixArgs } = spawnCommand ?? getSubagentSpawnCommand();\n\t\tthis.client = new RpcClient({\n\t\t\texecutable,\n\t\t\tprefixArgs,\n\t\t\tcwd: options.cwd,\n\t\t\tenv: this.env as Record<string, string>,\n\t\t\targs: buildWorkerArgs(options, registry, skillPaths, settings, availableModels),\n\t\t});\n\t}\n\n\t/** Boot the child. Throws (as WarmWorkerError) if it fails to come up. */\n\tasync start(): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.start();\n\t\t} catch (error) {\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tisAlive(): boolean {\n\t\treturn this.alive;\n\t}\n\n\t/**\n\t * Run one task to completion and return its answer + usage. Throws\n\t * WarmWorkerError on an infra failure (crash/timeout/protocol) so the caller\n\t * can fall back to the cold pool; a task that ran but reported failure returns\n\t * `{ ok: false }` instead (no fall back — the work was actually done).\n\t */\n\tasync run(\n\t\tprompt: string,\n\t\tonActivity?: WarmProgressCallback,\n\t\ttimeoutMs = WARM_RUN_TIMEOUT_MS,\n\t): Promise<WarmRunResult> {\n\t\tif (!this.alive) throw new WarmWorkerError(\"worker is not alive\");\n\t\t// Mirror the cold pool's coarse progress: report the tool the child is\n\t\t// currently running so a warm dispatch's task row reads \"⋯ grep\" rather than a\n\t\t// static \"running…\". Cleared between tools and at turn end.\n\t\tconst detachProgress = onActivity ? this.tapProgress(onActivity) : undefined;\n\t\ttry {\n\t\t\tconst events = await this.client.promptAndWait(prompt, undefined, timeoutMs);\n\t\t\tconst failure = firstTurnError(events);\n\t\t\tconst summary = (await this.client.getLastAssistantText()) ?? \"\";\n\t\t\tconst usage = await this.readUsage();\n\t\t\tif (failure) {\n\t\t\t\treturn { ok: false, status: \"failed\", summary, usage, error: failure };\n\t\t\t}\n\t\t\treturn { ok: true, status: \"complete\", summary, usage };\n\t\t} catch (error) {\n\t\t\t// A prompt/await failure means the child is no longer trustworthy: mark it\n\t\t\t// dead so the pool discards it, and signal infra failure for fallback.\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t} finally {\n\t\t\tdetachProgress?.();\n\t\t\tonActivity?.(\"\");\n\t\t}\n\t}\n\n\t/** Forward the child's coarse tool-lifecycle events to an activity callback. */\n\tprivate tapProgress(onActivity: WarmProgressCallback): () => void {\n\t\treturn this.client.onEvent((event) => {\n\t\t\tconst e = event as { type?: string; toolName?: string };\n\t\t\tif (e.type === \"tool_execution_start\") onActivity(typeof e.toolName === \"string\" ? e.toolName : \"\");\n\t\t\telse if (e.type === \"tool_execution_end\" || e.type === \"turn_end\") onActivity(\"\");\n\t\t});\n\t}\n\n\t/** Reset the worker's conversation so it can take the next task cleanly. */\n\tasync reset(): Promise<void> {\n\t\tif (!this.alive) return;\n\t\ttry {\n\t\t\tawait this.client.newSession();\n\t\t} catch (error) {\n\t\t\t// A reset failure leaves the worker in an unknown state — retire it.\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tthis.alive = false;\n\t\tawait this.client.stop().catch(() => {});\n\t}\n\n\tprivate async readUsage(): Promise<WarmUsage | undefined> {\n\t\ttry {\n\t\t\tconst stats = await this.client.getSessionStats();\n\t\t\treturn {\n\t\t\t\tinput: stats.tokens.input,\n\t\t\t\toutput: stats.tokens.output,\n\t\t\t\tcacheRead: stats.tokens.cacheRead,\n\t\t\t\tcacheWrite: stats.tokens.cacheWrite,\n\t\t\t\tcost: stats.cost,\n\t\t\t};\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n}\n\n/**\n * Pool of warm workers keyed by agent-type configuration. Hands out an idle\n * worker (or boots a new one up to a per-key cap), and on release resets the\n * worker and returns it to the idle set with an idle-TTL reclaim timer.\n */\nexport class WarmSubagentPool {\n\tprivate idle = new Map<string, WarmSubagentWorker[]>();\n\tprivate reclaimTimers = new Map<WarmSubagentWorker, ReturnType<typeof setTimeout>>();\n\tprivate liveCount = new Map<string, number>();\n\tprivate disposed = false;\n\tprivate registry?: AgentRegistry;\n\n\tconstructor(\n\t\tprivate readonly cwd: string,\n\t\tprivate readonly settings: Settings | undefined,\n\t\tprivate skillPaths: string[] = [],\n\t\t/**\n\t\t * Available models used to derive default model-category mappings when a tier\n\t\t * is not explicitly set in `settings.modelCategories` (snapshot at creation).\n\t\t */\n\t\tprivate readonly availableModels: readonly Model<Api>[] = [],\n\t\tprivate readonly maxPerKey = 2,\n\t\tprivate readonly idleTtlMs = 30_000,\n\t\t/** Spawn command override (tests inject a fake RPC child); defaults to the real spawn command. */\n\t\tprivate readonly spawnCommand?: { executable: string; prefixArgs: string[] },\n\t) {}\n\n\tupdateSkillPaths(paths: string[]): void {\n\t\tthis.skillPaths = [...paths];\n\t}\n\n\tprivate getRegistry(): AgentRegistry {\n\t\tif (!this.registry) this.registry = loadAgentRegistry({ cwd: this.cwd });\n\t\treturn this.registry;\n\t}\n\n\t/** A registry agent is poolable when it is not a fork agent (fork needs a forked session). */\n\tisPoolable(agentType: string): boolean {\n\t\tconst def = this.getRegistry().get(agentType);\n\t\treturn def !== undefined && def.fork !== true;\n\t}\n\n\t/** Stable key for one worker configuration. */\n\tprivate keyFor(options: WarmDispatchOptions): string {\n\t\tconst resolved = options.model\n\t\t\t? resolveModelReference(options.model, this.settings, this.availableModels)\n\t\t\t: undefined;\n\t\treturn `${options.agentType}::${resolved ?? \"default\"}::${options.provider ?? \"default\"}`;\n\t}\n\n\t/** Environment for a warm child: depth stamp + MCP skip, mirroring SubagentPool.childSpawnEnv. */\n\tprivate childEnv(agentType: string): NodeJS.ProcessEnv {\n\t\tconst env: NodeJS.ProcessEnv = {\n\t\t\t...process.env,\n\t\t\t[SUBAGENT_DEPTH_ENV]: String(currentSubagentDepth(process.env) + 1),\n\t\t};\n\t\tconst def = this.getRegistry().get(agentType);\n\t\tif (!toolAllowlistNeedsMcp(def?.tools)) env[SUBAGENT_SKIP_MCP_ENV] = \"1\";\n\t\t// A child never defers MCP schemas: if it needs MCP it eager-registers its\n\t\t// allowlisted tools at dispatch so they are immediately callable (spec §2).\n\t\tdelete env[DEFER_MCP_SCHEMAS_ENV];\n\t\treturn env;\n\t}\n\n\t/**\n\t * Run a task on a warm worker end to end: acquire (reuse or boot), run, then\n\t * release back to the pool. Throws WarmWorkerError on infra failure so the\n\t * caller can fall back to the cold pool.\n\t */\n\tasync dispatch(\n\t\tprompt: string,\n\t\toptions: WarmDispatchOptions,\n\t\tonActivity?: WarmProgressCallback,\n\t): Promise<WarmRunResult> {\n\t\tif (this.disposed) throw new WarmWorkerError(\"warm pool disposed\");\n\t\tconst worker = await this.acquire(options);\n\t\ttry {\n\t\t\tconst result = await worker.run(prompt, onActivity);\n\t\t\tawait this.release(worker);\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\t// Infra failure: the worker already marked itself dead; drop it entirely.\n\t\t\tawait this.discard(worker);\n\t\t\tthrow error instanceof WarmWorkerError ? error : new WarmWorkerError(String(error));\n\t\t}\n\t}\n\n\tprivate async acquire(options: WarmDispatchOptions): Promise<WarmSubagentWorker> {\n\t\tconst key = this.keyFor(options);\n\t\tconst pool = this.idle.get(key);\n\t\twhile (pool && pool.length > 0) {\n\t\t\tconst worker = pool.pop()!;\n\t\t\tthis.clearReclaim(worker);\n\t\t\tif (worker.isAlive()) return worker;\n\t\t\t// Dead idle worker (child exited while parked): drop and try the next.\n\t\t\tthis.decLive(key);\n\t\t\tawait worker.dispose();\n\t\t}\n\n\t\tconst worker = new WarmSubagentWorker(\n\t\t\tkey,\n\t\t\toptions,\n\t\t\tthis.childEnv(options.agentType),\n\t\t\tthis.getRegistry(),\n\t\t\tthis.skillPaths,\n\t\t\tthis.settings,\n\t\t\tthis.availableModels,\n\t\t\tthis.spawnCommand,\n\t\t);\n\t\tthis.incLive(key);\n\t\ttry {\n\t\t\tawait worker.start();\n\t\t} catch (error) {\n\t\t\tthis.decLive(key);\n\t\t\tthrow error;\n\t\t}\n\t\treturn worker;\n\t}\n\n\tprivate async release(worker: WarmSubagentWorker): Promise<void> {\n\t\tif (this.disposed || !worker.isAlive()) {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait worker.reset();\n\t\t} catch {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\tconst key = worker.key;\n\t\tconst pool = this.idle.get(key) ?? [];\n\t\tif (pool.length >= this.maxPerKey) {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\tpool.push(worker);\n\t\tthis.idle.set(key, pool);\n\t\tthis.armReclaim(worker);\n\t}\n\n\tprivate async discard(worker: WarmSubagentWorker): Promise<void> {\n\t\tthis.clearReclaim(worker);\n\t\tthis.decLive(worker.key);\n\t\tawait worker.dispose();\n\t}\n\n\tprivate armReclaim(worker: WarmSubagentWorker): void {\n\t\tconst timer = setTimeout(() => {\n\t\t\tconst pool = this.idle.get(worker.key);\n\t\t\tif (pool) {\n\t\t\t\tconst idx = pool.indexOf(worker);\n\t\t\t\tif (idx !== -1) pool.splice(idx, 1);\n\t\t\t}\n\t\t\tvoid this.discard(worker);\n\t\t}, this.idleTtlMs);\n\t\ttimer.unref?.();\n\t\tthis.reclaimTimers.set(worker, timer);\n\t}\n\n\tprivate clearReclaim(worker: WarmSubagentWorker): void {\n\t\tconst timer = this.reclaimTimers.get(worker);\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.reclaimTimers.delete(worker);\n\t\t}\n\t}\n\n\tprivate incLive(key: string): void {\n\t\tthis.liveCount.set(key, (this.liveCount.get(key) ?? 0) + 1);\n\t}\n\n\tprivate decLive(key: string): void {\n\t\tconst n = (this.liveCount.get(key) ?? 1) - 1;\n\t\tif (n <= 0) this.liveCount.delete(key);\n\t\telse this.liveCount.set(key, n);\n\t}\n\n\t/** Number of currently idle (parked) workers — exposed for tests/diagnostics. */\n\tidleCount(): number {\n\t\tlet total = 0;\n\t\tfor (const pool of this.idle.values()) total += pool.length;\n\t\treturn total;\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\t\tconst all: WarmSubagentWorker[] = [];\n\t\tfor (const pool of this.idle.values()) all.push(...pool);\n\t\tthis.idle.clear();\n\t\tfor (const timer of this.reclaimTimers.values()) clearTimeout(timer);\n\t\tthis.reclaimTimers.clear();\n\t\tthis.liveCount.clear();\n\t\tawait Promise.all(all.map((w) => w.dispose()));\n\t}\n}\n\n/**\n * Build the RPC child's CLI args for a warm worker. Mirrors the agent-config\n * portion of SubagentPool.buildArgs (system prompt, tools, model/provider, turn\n * cap, skills) but omits the one-shot `--mode json` / `--session` / `--task-id`\n * bits, since the worker runs persistently in RPC mode and resets via new_session.\n */\nfunction buildWorkerArgs(\n\toptions: WarmDispatchOptions,\n\tregistry: AgentRegistry,\n\tskillPaths: readonly string[],\n\tsettings: Settings | undefined,\n\tavailableModels: readonly Model<Api>[],\n): string[] {\n\tconst args: string[] = [];\n\tconst def = registry.get(options.agentType);\n\n\tif (def?.prompt) args.push(\"--system-prompt\", def.prompt);\n\n\tconst childDepth = currentSubagentDepth(process.env) + 1;\n\tconst canChildDelegate = def?.delegate === true && childDepth < resolveMaxSubagentDepth(undefined, process.env);\n\n\tconst tools = def?.tools ? [...def.tools] : undefined;\n\tif (canChildDelegate && tools) {\n\t\tfor (const t of [\"Task\", \"TaskOutput\"]) if (!tools.includes(t)) tools.push(t);\n\t}\n\tif (tools && tools.length > 0) args.push(\"--tools\", tools.join(\",\"));\n\tif (def?.disallowedTools && def.disallowedTools.length > 0) {\n\t\targs.push(\"--disallowed-tools\", def.disallowedTools.join(\",\"));\n\t}\n\tif (canChildDelegate) {\n\t\targs.push(\"--enable-subagents\");\n\t\tif (def?.delegateTo && def.delegateTo.length > 0) args.push(\"--delegate-allow\", def.delegateTo.join(\",\"));\n\t}\n\n\t// Model precedence matches the cold path: a pinned (non-inherit) agent model\n\t// wins, else the requested model/category, resolved to a concrete id.\n\tconst explicitModel = def?.model && def.model !== MODEL_INHERIT ? def.model : undefined;\n\tconst rawModel = explicitModel ?? options.model;\n\tconst modelToUse = rawModel ? resolveModelReference(rawModel, settings, availableModels) : undefined;\n\tif (modelToUse) args.push(\"--model\", modelToUse);\n\t// Only pass --provider when the model doesn't already encode the provider\n\t// (e.g. \"anthropic/claude-sonnet-4-5\"). When both --model and --provider are\n\t// present, --provider wins and filters candidates to that provider, which\n\t// breaks models whose IDs contain a provider prefix from a different provider.\n\tif (options.provider && (!modelToUse || !modelToUse.includes(\"/\"))) args.push(\"--provider\", options.provider);\n\n\tconst maxTurns = def?.maxTurns && def.maxTurns > 0 ? def.maxTurns : DEFAULT_SUBAGENT_MAX_TURNS;\n\targs.push(\"--max-turns\", String(maxTurns));\n\n\tfor (const skillPath of skillPaths) args.push(\"--skill\", skillPath);\n\n\treturn args;\n}\n\n/**\n * Scan a completed run's events for a turn that ended in error/abort, returning\n * its message (or a generic marker) so the caller can report a task failure\n * without re-reading the transcript. Returns undefined for a clean run.\n */\nfunction firstTurnError(events: readonly AgentEvent[]): string | undefined {\n\tfor (const event of events) {\n\t\tconst e = event as { type?: string; message?: { stopReason?: string; errorMessage?: string } };\n\t\tif (e.type === \"turn_end\" && (e.message?.stopReason === \"error\" || e.message?.stopReason === \"aborted\")) {\n\t\t\treturn e.message.errorMessage || `turn ${e.message.stopReason}`;\n\t\t}\n\t}\n\treturn undefined;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolisachint/hoocode-agent",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.159",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"hoocodeConfig": {
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"prepublishOnly": "npm run clean && npm run build"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@kolisachint/hoocode-agent-core": "^0.4.
|
|
49
|
-
"@kolisachint/hoocode-ai": "^0.4.
|
|
50
|
-
"@kolisachint/hoocode-tui": "^0.4.
|
|
48
|
+
"@kolisachint/hoocode-agent-core": "^0.4.159",
|
|
49
|
+
"@kolisachint/hoocode-ai": "^0.4.159",
|
|
50
|
+
"@kolisachint/hoocode-tui": "^0.4.159",
|
|
51
51
|
"@silvia-odwyer/photon-node": "^0.3.4",
|
|
52
52
|
"chalk": "^5.5.0",
|
|
53
53
|
"cli-highlight": "^2.1.11",
|