{"version":3,"file":"subagent-depth.d.ts","sourceRoot":"","sources":["../../src/core/subagent-depth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,eAAO,MAAM,kBAAkB,2BAA2B,CAAC;AAC3D,eAAO,MAAM,sBAAsB,+BAA+B,CAAC;AACnE,eAAO,MAAM,sBAAsB,wCAAwC,CAAC;AAC5E,uFAAuF;AACvF,eAAO,MAAM,kBAAkB,2BAA2B,CAAC;AAC3D;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AAExD;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,8BAA8B,CAAC;AAEjE,mFAAmF;AACnF,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C,uFAAuF;AACvF,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C,uFAAuF;AACvF,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED,4DAA4D;AAC5D,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAGjF;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAU3G;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAErG;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAU5G;AAED,kFAAkF;AAClF,wBAAgB,uBAAuB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,SAAS,CAEhG;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,EAAE,GAAG,SAAS,CAQ5F;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAGrG;AAED,sGAAsG;AACtG,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAE7E;AAED,sFAAsF;AACtF,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAE7E;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAAG,OAAO,CAGnF","sourcesContent":["/**\n * Subagent nesting depth + tree-wide safety bounds.\n *\n * Nesting is governed entirely by environment variables so every process in a\n * delegation tree agrees without any runtime cross-process coordination (no\n * shared lock files, no slots to leak on crash):\n *\n *   - HOOCODE_SUBAGENT_DEPTH      current process's depth (root unset/0, its\n *                                 children 1, grandchildren 2, ...).\n *   - HOOCODE_SUBAGENT_MAX_DEPTH  the tree-wide cap, seeded once by the root from\n *                                 its `maxSubagentDepth` setting and inherited by\n *                                 every descendant.\n *\n * The default cap is 1 — a subagent may not spawn further subagents — which\n * reproduces the original hard guard exactly. Raising it is an opt-in feature.\n *\n * Fan-out is bounded deterministically rather than by a global counter: pools at\n * depth >= 1 run with NESTED_SUBAGENT_CONCURRENCY instead of the root's default,\n * so the worst-case live process count is a fixed function of depth and the per\n * level caps (e.g. 5 + 5*2 = 15 at depth 2), with nothing to reclaim on crash.\n */\n\nexport const SUBAGENT_DEPTH_ENV = \"HOOCODE_SUBAGENT_DEPTH\";\nexport const SUBAGENT_MAX_DEPTH_ENV = \"HOOCODE_SUBAGENT_MAX_DEPTH\";\nexport const NESTED_CONCURRENCY_ENV = \"HOOCODE_NESTED_SUBAGENT_CONCURRENCY\";\n/** Comma-separated allowlist of subagent types the current process may delegate to. */\nexport const DELEGATE_ALLOW_ENV = \"HOOCODE_DELEGATE_ALLOW\";\n/**\n * Set by the parent pool when a spawned subagent's tool allowlist contains no MCP\n * tools, telling the child's MCP loader to skip connecting external servers at\n * startup. Connecting them (one ~15s handshake apiece) is pure boot latency for a\n * subagent that can never call them. Only set when the allowlist is explicit and\n * MCP-free; an inherit-all agent leaves it unset and connects as usual.\n */\nexport const SUBAGENT_SKIP_MCP_ENV = \"HOOCODE_SKIP_MCP\";\n\n/**\n * When set to \"1\", the MCP loader defers tool *schemas*: it injects tool names\n * only (via a ResolveMcpTools tool) and materializes each full schema on demand,\n * instead of registering every MCP tool's full JSON schema up front (spec §2).\n *\n * Set on the top-level agent (opt-in via the deferMcpSchemas setting) and cleared\n * for subagent children — a child that needs MCP resolves its allowlisted tools\n * eagerly at dispatch (the dispatch ↔ schema interaction), so its scoped tools\n * are immediately callable.\n */\nexport const DEFER_MCP_SCHEMAS_ENV = \"HOOCODE_DEFER_MCP_SCHEMAS\";\n\n/** Default tree-wide cap: subagents cannot spawn subagents (original behavior). */\nexport const DEFAULT_MAX_SUBAGENT_DEPTH = 1;\n\n/** Concurrency cap for pools running at depth >= 1, keeping nested fan-out bounded. */\nexport const NESTED_SUBAGENT_CONCURRENCY = 2;\n\n/**\n * Hard ceiling on the configurable nesting depth. The worst-case live process\n * count grows geometrically with depth (each level's pool can run\n * NESTED_SUBAGENT_CONCURRENCY children), so an unbounded cap would let a\n * mis-configured setting exhaust the host. At this ceiling the worst case stays\n * modest: 5 * (2^3 - 1) = 35 processes.\n */\nexport const ABSOLUTE_MAX_SUBAGENT_DEPTH = 3;\n\n/** Clamp a requested cap into the supported range [1, ABSOLUTE_MAX_SUBAGENT_DEPTH]. */\nexport function clampMaxSubagentDepth(n: number): number {\n\tif (!Number.isFinite(n)) return DEFAULT_MAX_SUBAGENT_DEPTH;\n\treturn Math.min(Math.max(1, Math.floor(n)), ABSOLUTE_MAX_SUBAGENT_DEPTH);\n}\n\n/** Depth of the current process (0 = root/main session). */\nexport function currentSubagentDepth(env: NodeJS.ProcessEnv = process.env): number {\n\tconst n = Number.parseInt(env[SUBAGENT_DEPTH_ENV] ?? \"0\", 10);\n\treturn Number.isFinite(n) && n > 0 ? n : 0;\n}\n\n/**\n * Tree-wide max depth. Reads the inherited env value when present (any spawned\n * process), otherwise falls back to the provided setting (the root seeds env\n * from this). Clamped to >= 1 so the cap can never disable delegation entirely.\n */\nexport function resolveMaxSubagentDepth(settingValue?: number, env: NodeJS.ProcessEnv = process.env): number {\n\tconst raw = env[SUBAGENT_MAX_DEPTH_ENV];\n\tif (raw !== undefined) {\n\t\tconst n = Number.parseInt(raw, 10);\n\t\tif (Number.isFinite(n) && n >= 1) return clampMaxSubagentDepth(n);\n\t}\n\tif (settingValue !== undefined && Number.isFinite(settingValue) && settingValue >= 1) {\n\t\treturn clampMaxSubagentDepth(settingValue);\n\t}\n\treturn DEFAULT_MAX_SUBAGENT_DEPTH;\n}\n\n/** True when a process at the current depth may still spawn subagents. */\nexport function canSpawnSubagent(settingValue?: number, env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn currentSubagentDepth(env) < resolveMaxSubagentDepth(settingValue, env);\n}\n\n/**\n * Concurrency cap for pools running at depth >= 1. Reads the inherited env value\n * (seeded by the root from the `nestedSubagentConcurrency` setting) when present,\n * else the provided setting, else the default. Clamped to >= 1.\n */\nexport function resolveNestedConcurrency(settingValue?: number, env: NodeJS.ProcessEnv = process.env): number {\n\tconst raw = env[NESTED_CONCURRENCY_ENV];\n\tif (raw !== undefined) {\n\t\tconst n = Number.parseInt(raw, 10);\n\t\tif (Number.isFinite(n) && n >= 1) return n;\n\t}\n\tif (settingValue !== undefined && Number.isFinite(settingValue) && settingValue >= 1) {\n\t\treturn Math.floor(settingValue);\n\t}\n\treturn NESTED_SUBAGENT_CONCURRENCY;\n}\n\n/** Concurrency for a pool created in the current process: reduced when nested. */\nexport function poolConcurrencyForDepth(env: NodeJS.ProcessEnv = process.env): number | undefined {\n\treturn currentSubagentDepth(env) >= 1 ? resolveNestedConcurrency(undefined, env) : undefined;\n}\n\n/**\n * Subagent types the current process is restricted to delegating to, or undefined\n * when unrestricted (may delegate to any type). Set per spawned agent from its\n * `delegate: <types>` frontmatter; the root is always unrestricted.\n */\nexport function delegateAllowList(env: NodeJS.ProcessEnv = process.env): string[] | undefined {\n\tconst raw = env[DELEGATE_ALLOW_ENV];\n\tif (raw === undefined) return undefined;\n\tconst list = raw\n\t\t.split(\",\")\n\t\t.map((s) => s.trim())\n\t\t.filter((s) => s.length > 0);\n\treturn list.length > 0 ? list : undefined;\n}\n\n/** Whether the current process may delegate to the given subagent type. */\nexport function isDelegateAllowed(subagentType: string, env: NodeJS.ProcessEnv = process.env): boolean {\n\tconst allow = delegateAllowList(env);\n\treturn !allow || allow.includes(subagentType);\n}\n\n/** Whether this process should skip connecting MCP servers at startup (see SUBAGENT_SKIP_MCP_ENV). */\nexport function subagentSkipMcp(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn env[SUBAGENT_SKIP_MCP_ENV] === \"1\";\n}\n\n/** Whether this process should defer MCP tool schemas (see DEFER_MCP_SCHEMAS_ENV). */\nexport function deferMcpSchemas(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn env[DEFER_MCP_SCHEMAS_ENV] === \"1\";\n}\n\n/**\n * Decide whether a child with the given tool allowlist needs MCP servers. A tool\n * name is MCP-sourced when it carries the `mcp_<server>_<tool>` prefix. An\n * undefined allowlist means \"inherit every tool\", so MCP must stay available.\n */\nexport function toolAllowlistNeedsMcp(tools: readonly string[] | undefined): boolean {\n\tif (!tools) return true;\n\treturn tools.some((t) => /^mcp[_-]?/i.test(t.trim()));\n}\n"]}