@kolisachint/hoocode-agent 0.4.37 → 0.4.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/cli/args.d.ts.map +1 -1
  3. package/dist/cli/args.js +1 -1
  4. package/dist/cli/args.js.map +1 -1
  5. package/dist/core/lifeguard.d.ts +11 -0
  6. package/dist/core/lifeguard.d.ts.map +1 -1
  7. package/dist/core/lifeguard.js +68 -3
  8. package/dist/core/lifeguard.js.map +1 -1
  9. package/dist/core/messages.d.ts +38 -3
  10. package/dist/core/messages.d.ts.map +1 -1
  11. package/dist/core/messages.js +64 -10
  12. package/dist/core/messages.js.map +1 -1
  13. package/dist/core/sdk.d.ts.map +1 -1
  14. package/dist/core/sdk.js +2 -1
  15. package/dist/core/sdk.js.map +1 -1
  16. package/dist/core/subagent-pool.d.ts +2 -1
  17. package/dist/core/subagent-pool.d.ts.map +1 -1
  18. package/dist/core/subagent-pool.js +2 -1
  19. package/dist/core/subagent-pool.js.map +1 -1
  20. package/dist/core/task-store.d.ts +6 -1
  21. package/dist/core/task-store.d.ts.map +1 -1
  22. package/dist/core/task-store.js +3 -0
  23. package/dist/core/task-store.js.map +1 -1
  24. package/dist/core/tools/subagent.d.ts.map +1 -1
  25. package/dist/core/tools/subagent.js +3 -2
  26. package/dist/core/tools/subagent.js.map +1 -1
  27. package/dist/extensions/core/hoo-core.d.ts.map +1 -1
  28. package/dist/extensions/core/hoo-core.js +17 -1
  29. package/dist/extensions/core/hoo-core.js.map +1 -1
  30. package/dist/modes/interactive/components/task-panel.d.ts +3 -1
  31. package/dist/modes/interactive/components/task-panel.d.ts.map +1 -1
  32. package/dist/modes/interactive/components/task-panel.js +27 -5
  33. package/dist/modes/interactive/components/task-panel.js.map +1 -1
  34. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  35. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  36. package/examples/extensions/sandbox/package.json +1 -1
  37. package/examples/extensions/with-deps/package.json +1 -1
  38. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.38] - 2026-06-05
4
+
5
+ ### Fixed
6
+
7
+ - Subagents no longer get killed under heavy load. When many subagents (plus
8
+ background MCP tools) run at once, CPU contention starved the parent event loop
9
+ so it missed a healthy child's heartbeat and SIGKILLed it as "stalled" (or hit a
10
+ wall-clock timeout that inflated faster than real work). The lifeguard now scales
11
+ the heartbeat-miss and hard-timeout budgets by the number of concurrent
12
+ subagents and forgives measured event-loop lag, so contention alone no longer
13
+ reaps a working subagent. A genuinely stuck agent is still reaped at a hard
14
+ ceiling.
15
+
16
+ ### Changed
17
+
18
+ - Background tools now explain themselves verbosely and consistently in chat. Both
19
+ background subagents and background MCP tools get the same shape: a verbose
20
+ "started" line that names the subagent/MCP tool and summarizes its arguments, and
21
+ a matching "finished/failed" follow-up message using the same label. Subagent and
22
+ MCP background work already appear in the task pane (pending → in_progress →
23
+ done/failed); the chat side is now in sync with it.
24
+ - MCP tool calls now render a clean, prefixed title in chat — `MCP [server › tool]
25
+ <args>` — parallel to the subagent `Task [type] <desc>` line, instead of falling
26
+ back to the raw `mcp_<server>_<tool>` tool name. The args summary reuses the same
27
+ helper as the background start/finish messages so the title stays in sync with them.
28
+ - The task pane now shows a single-cell source glyph before the id — ⚙ for subagent
29
+ rows, ⧉ for MCP rows (plain tasks reserve a blank cell) — so the two kinds of
30
+ background work are distinguishable at a glance. The pane stays tag-free: the
31
+ subagent *mode* tag (e.g. "[explore]") is still not shown.
32
+ - Task-pane ids are padded to a uniform column width, so titles stay aligned across
33
+ rows regardless of id digit count (#1 vs #10 vs #100) instead of jagging right.
34
+
3
35
  ## [0.4.37] - 2026-06-05
4
36
 
5
37
  ## [0.4.36] - 2026-06-05
@@ -1 +1 @@
1
- {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEjE,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAE3C,MAAM,WAAW,IAAI;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,0FAA0F;IAC1F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yGAAyG;IACzG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,8EAA8E;IAC9E,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC;IAC5C,WAAW,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnE;AAID,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,aAAa,CAE1E;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAmJ9C;AAED,wBAAgB,SAAS,CAAC,cAAc,CAAC,EAAE,aAAa,EAAE,GAAG,IAAI,CA2KhE","sourcesContent":["/**\n * CLI argument parsing and help display\n */\n\nimport type { ThinkingLevel } from \"@kolisachint/hoocode-agent-core\";\nimport chalk from \"chalk\";\nimport { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR, ENV_SESSION_DIR } from \"../config.js\";\nimport type { ExtensionFlag } from \"../core/extensions/types.js\";\n\nexport type Mode = \"text\" | \"json\" | \"rpc\";\n\nexport interface Args {\n\tprovider?: string;\n\tmodel?: string;\n\tapiKey?: string;\n\tsystemPrompt?: string;\n\tthinking?: ThinkingLevel;\n\tcontinue?: boolean;\n\tresume?: boolean;\n\thelp?: boolean;\n\tversion?: boolean;\n\tmode?: Mode;\n\tnoSession?: boolean;\n\t/** Internal: task id assigned by SubagentPool when this process is a spawned subagent. */\n\ttaskId?: string;\n\t/** Hard cap on assistant turns. Near the cap the agent is asked to wrap up; at the cap it is stopped. */\n\tmaxTurns?: number;\n\tsession?: string;\n\tfork?: string;\n\tsessionDir?: string;\n\tmodels?: string[];\n\ttools?: string[];\n\tnoTools?: boolean;\n\tnoBuiltinTools?: boolean;\n\tsubagent?: boolean;\n\textensions?: string[];\n\tnoExtensions?: boolean;\n\tprint?: boolean;\n\texport?: string;\n\tnoSkills?: boolean;\n\tskills?: string[];\n\tagents?: string[];\n\tpromptTemplates?: string[];\n\tnoPromptTemplates?: boolean;\n\tslashCommands?: string[];\n\tnoSlashCommands?: boolean;\n\tthemes?: string[];\n\tnoThemes?: boolean;\n\tmodePaths?: string[];\n\tnoContextFiles?: boolean;\n\tlistModels?: string | true;\n\toffline?: boolean;\n\tverbose?: boolean;\n\tmessages: string[];\n\tfileArgs: string[];\n\t/** Unknown flags (potentially extension flags) - map of flag name to value */\n\tunknownFlags: Map<string, boolean | string>;\n\tdiagnostics: Array<{ type: \"warning\" | \"error\"; message: string }>;\n}\n\nconst VALID_THINKING_LEVELS = [\"off\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\"] as const;\n\nexport function isValidThinkingLevel(level: string): level is ThinkingLevel {\n\treturn VALID_THINKING_LEVELS.includes(level as ThinkingLevel);\n}\n\nexport function parseArgs(args: string[]): Args {\n\tconst result: Args = {\n\t\tmessages: [],\n\t\tfileArgs: [],\n\t\tunknownFlags: new Map(),\n\t\tdiagnostics: [],\n\t};\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\n\t\tif (arg === \"--help\" || arg === \"-h\") {\n\t\t\tresult.help = true;\n\t\t} else if (arg === \"--version\" || arg === \"-v\") {\n\t\t\tresult.version = true;\n\t\t} else if (arg === \"--mode\" && i + 1 < args.length) {\n\t\t\tconst mode = args[++i];\n\t\t\tif (mode === \"text\" || mode === \"json\" || mode === \"rpc\") {\n\t\t\t\tresult.mode = mode;\n\t\t\t}\n\t\t} else if (arg === \"--continue\" || arg === \"-c\") {\n\t\t\tresult.continue = true;\n\t\t} else if (arg === \"--resume\" || arg === \"-r\") {\n\t\t\tresult.resume = true;\n\t\t} else if (arg === \"--provider\" && i + 1 < args.length) {\n\t\t\tresult.provider = args[++i];\n\t\t} else if (arg === \"--model\" && i + 1 < args.length) {\n\t\t\tresult.model = args[++i];\n\t\t} else if (arg === \"--api-key\" && i + 1 < args.length) {\n\t\t\tresult.apiKey = args[++i];\n\t\t} else if (arg === \"--system-prompt\" && i + 1 < args.length) {\n\t\t\tresult.systemPrompt = args[++i];\n\t\t} else if (arg === \"--no-session\") {\n\t\t\tresult.noSession = true;\n\t\t} else if (arg === \"--task-id\" && i + 1 < args.length) {\n\t\t\tresult.taskId = args[++i];\n\t\t} else if (arg === \"--max-turns\" && i + 1 < args.length) {\n\t\t\tconst n = Number.parseInt(args[++i], 10);\n\t\t\tif (Number.isInteger(n) && n > 0) {\n\t\t\t\tresult.maxTurns = n;\n\t\t\t}\n\t\t} else if (arg === \"--session\" && i + 1 < args.length) {\n\t\t\tresult.session = args[++i];\n\t\t} else if (arg === \"--fork\" && i + 1 < args.length) {\n\t\t\tresult.fork = args[++i];\n\t\t} else if (arg === \"--session-dir\" && i + 1 < args.length) {\n\t\t\tresult.sessionDir = args[++i];\n\t\t} else if (arg === \"--models\" && i + 1 < args.length) {\n\t\t\tresult.models = args[++i].split(\",\").map((s) => s.trim());\n\t\t} else if (arg === \"--no-tools\" || arg === \"-nt\") {\n\t\t\tresult.noTools = true;\n\t\t} else if (arg === \"--no-builtin-tools\" || arg === \"-nbt\") {\n\t\t\tresult.noBuiltinTools = true;\n\t\t} else if (arg === \"--enable-subagents\") {\n\t\t\tresult.subagent = true;\n\t\t} else if ((arg === \"--tools\" || arg === \"-t\") && i + 1 < args.length) {\n\t\t\tresult.tools = args[++i]\n\t\t\t\t.split(\",\")\n\t\t\t\t.map((s) => s.trim())\n\t\t\t\t.filter((name) => name.length > 0);\n\t\t} else if (arg === \"--thinking\" && i + 1 < args.length) {\n\t\t\tconst level = args[++i];\n\t\t\tif (isValidThinkingLevel(level)) {\n\t\t\t\tresult.thinking = level;\n\t\t\t} else {\n\t\t\t\tresult.diagnostics.push({\n\t\t\t\t\ttype: \"warning\",\n\t\t\t\t\tmessage: `Invalid thinking level \"${level}\". Valid values: ${VALID_THINKING_LEVELS.join(\", \")}`,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (arg === \"--print\" || arg === \"-p\") {\n\t\t\tresult.print = true;\n\t\t\tconst next = args[i + 1];\n\t\t\tif (next !== undefined && !next.startsWith(\"@\") && (!next.startsWith(\"-\") || next.startsWith(\"---\"))) {\n\t\t\t\tresult.messages.push(next);\n\t\t\t\ti++;\n\t\t\t}\n\t\t} else if (arg === \"--export\" && i + 1 < args.length) {\n\t\t\tresult.export = args[++i];\n\t\t} else if ((arg === \"--extension\" || arg === \"-e\") && i + 1 < args.length) {\n\t\t\tresult.extensions = result.extensions ?? [];\n\t\t\tresult.extensions.push(args[++i]);\n\t\t} else if (arg === \"--no-extensions\" || arg === \"-ne\") {\n\t\t\tresult.noExtensions = true;\n\t\t} else if (arg === \"--skill\" && i + 1 < args.length) {\n\t\t\tresult.skills = result.skills ?? [];\n\t\t\tresult.skills.push(args[++i]);\n\t\t} else if (arg === \"--agent\" && i + 1 < args.length) {\n\t\t\tresult.agents = result.agents ?? [];\n\t\t\tresult.agents.push(args[++i]);\n\t\t} else if (arg === \"--prompt-template\" && i + 1 < args.length) {\n\t\t\tresult.promptTemplates = result.promptTemplates ?? [];\n\t\t\tresult.promptTemplates.push(args[++i]);\n\t\t} else if (arg === \"--slash-command\" && i + 1 < args.length) {\n\t\t\tresult.slashCommands = result.slashCommands ?? [];\n\t\t\tresult.slashCommands.push(args[++i]);\n\t\t} else if (arg === \"--theme\" && i + 1 < args.length) {\n\t\t\tresult.themes = result.themes ?? [];\n\t\t\tresult.themes.push(args[++i]);\n\t\t} else if (arg === \"--mode-path\" && i + 1 < args.length) {\n\t\t\tresult.modePaths = result.modePaths ?? [];\n\t\t\tresult.modePaths.push(args[++i]);\n\t\t} else if (arg === \"--no-skills\" || arg === \"-ns\") {\n\t\t\tresult.noSkills = true;\n\t\t} else if (arg === \"--no-prompt-templates\" || arg === \"-np\") {\n\t\t\tresult.noPromptTemplates = true;\n\t\t} else if (arg === \"--no-slash-commands\" || arg === \"-nsc\") {\n\t\t\tresult.noSlashCommands = true;\n\t\t} else if (arg === \"--no-themes\") {\n\t\t\tresult.noThemes = true;\n\t\t} else if (arg === \"--no-context-files\" || arg === \"-nc\") {\n\t\t\tresult.noContextFiles = true;\n\t\t} else if (arg === \"--list-models\") {\n\t\t\t// Check if next arg is a search pattern (not a flag or file arg)\n\t\t\tif (i + 1 < args.length && !args[i + 1].startsWith(\"-\") && !args[i + 1].startsWith(\"@\")) {\n\t\t\t\tresult.listModels = args[++i];\n\t\t\t} else {\n\t\t\t\tresult.listModels = true;\n\t\t\t}\n\t\t} else if (arg === \"--verbose\") {\n\t\t\tresult.verbose = true;\n\t\t} else if (arg === \"--offline\") {\n\t\t\tresult.offline = true;\n\t\t} else if (arg.startsWith(\"@\")) {\n\t\t\tresult.fileArgs.push(arg.slice(1)); // Remove @ prefix\n\t\t} else if (arg.startsWith(\"--\")) {\n\t\t\tconst eqIndex = arg.indexOf(\"=\");\n\t\t\tif (eqIndex !== -1) {\n\t\t\t\tresult.unknownFlags.set(arg.slice(2, eqIndex), arg.slice(eqIndex + 1));\n\t\t\t} else {\n\t\t\t\tconst flagName = arg.slice(2);\n\t\t\t\tconst next = args[i + 1];\n\t\t\t\tif (next !== undefined && !next.startsWith(\"-\") && !next.startsWith(\"@\")) {\n\t\t\t\t\tresult.unknownFlags.set(flagName, next);\n\t\t\t\t\ti++;\n\t\t\t\t} else {\n\t\t\t\t\tresult.unknownFlags.set(flagName, true);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (arg.startsWith(\"-\") && !arg.startsWith(\"--\")) {\n\t\t\tresult.diagnostics.push({ type: \"error\", message: `Unknown option: ${arg}` });\n\t\t} else if (!arg.startsWith(\"-\")) {\n\t\t\tresult.messages.push(arg);\n\t\t}\n\t}\n\n\treturn result;\n}\n\nexport function printHelp(extensionFlags?: ExtensionFlag[]): void {\n\tconst extensionFlagsText =\n\t\textensionFlags && extensionFlags.length > 0\n\t\t\t? `\\n${chalk.bold(\"Extension CLI Flags:\")}\\n${extensionFlags\n\t\t\t\t\t.map((flag) => {\n\t\t\t\t\t\tconst value = flag.type === \"string\" ? \" <value>\" : \"\";\n\t\t\t\t\t\tconst description = flag.description ?? `Registered by ${flag.extensionPath}`;\n\t\t\t\t\t\treturn ` --${flag.name}${value}`.padEnd(30) + description;\n\t\t\t\t\t})\n\t\t\t\t\t.join(\"\\n\")}\\n`\n\t\t\t: \"\";\n\tconsole.log(`${chalk.bold(APP_NAME)} - AI coding assistant with read, bash, edit, write tools\n\n${chalk.bold(\"Usage:\")}\n ${APP_NAME} [options] [@files...] [messages...]\n\n${chalk.bold(\"Commands:\")}\n ${APP_NAME} install <source> [-l] Install extension source and add to settings\n ${APP_NAME} remove <source> [-l] Remove extension source from settings\n ${APP_NAME} uninstall <source> [-l] Alias for remove\n ${APP_NAME} update [source|self|pi] Update hoocode and installed extensions\n ${APP_NAME} list List installed extensions from settings\n ${APP_NAME} config Open TUI to enable/disable package resources\n ${APP_NAME} <command> --help Show help for install/remove/uninstall/update/list\n\n${chalk.bold(\"Options:\")}\n --provider <name> Provider name (default: google)\n --model <pattern> Model pattern or ID (supports \"provider/id\" and optional \":<thinking>\")\n --api-key <key> API key (defaults to env vars)\n --system-prompt <text> System prompt (default: coding assistant prompt)\n --mode <mode> Output mode: text (default), json, or rpc\n --print, -p Non-interactive mode: process prompt and exit\n --continue, -c Continue previous session\n --resume, -r Select a session to resume\n --session <path|id> Use specific session file or partial UUID\n --fork <path|id> Fork specific session file or partial UUID into a new session\n --session-dir <dir> Directory for session storage and lookup\n --no-session Don't save session (ephemeral)\n --models <patterns> Comma-separated model patterns for Ctrl+P cycling\n Supports globs (anthropic/*, *sonnet*) and fuzzy matching\n --no-tools, -nt Disable all tools by default (built-in and extension)\n --no-builtin-tools, -nbt Disable built-in tools by default but keep extension/custom tools enabled\n --tools, -t <tools> Comma-separated allowlist of tool names to enable\n Applies to built-in, extension, and custom tools\n --max-turns <n> Hard cap on assistant turns; the agent is asked to wrap up near the\n cap and stopped at it (mainly used for spawned subagents)\n --enable-subagents Enable the subagent tool (delegate tasks to isolated agent loops)\n Also enablable via the \"enableSubagent\" setting\n --thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh\n --extension, -e <path> Load an extension file (can be used multiple times)\n --no-extensions, -ne Disable extension discovery (explicit -e paths still work)\n --skill <path> Load a skill file or directory (can be used multiple times)\n --no-skills, -ns Disable skills discovery and loading\n --agent <path> Load an agent file or directory (can be used multiple times)\n --prompt-template <path> Load a prompt template / slash command file or directory (repeatable)\n --slash-command <path> Alias of --prompt-template (prompts and slash commands are one feature)\n --no-prompt-templates, -np Disable prompt template / slash command discovery (same as --no-slash-commands)\n --no-slash-commands, -nsc Disable prompt template / slash command discovery (same as --no-prompt-templates)\n --theme <path> Load a theme file or directory (can be used multiple times)\n --no-themes Disable theme discovery and loading\n --mode-path <dir> Add a directory to search for {name}/system.md mode files (can be used multiple times)\n --no-context-files, -nc Disable AGENTS.md and CLAUDE.md discovery and loading\n --export <file> Export session file to HTML and exit\n --list-models [search] List available models (with optional fuzzy search)\n --verbose Force verbose startup (overrides quietStartup setting)\n --offline Disable startup network operations (same as HOOCODE_OFFLINE=1)\n --help, -h Show this help\n --version, -v Show version number\n\nExtensions can register additional flags (e.g., --plan from plan-mode extension).${extensionFlagsText}\n\n${chalk.bold(\"Examples:\")}\n # Interactive mode\n ${APP_NAME}\n\n # Interactive mode with initial prompt\n ${APP_NAME} \"List all .ts files in src/\"\n\n # Include files in initial message\n ${APP_NAME} @prompt.md @image.png \"What color is the sky?\"\n\n # Non-interactive mode (process and exit)\n ${APP_NAME} -p \"List all .ts files in src/\"\n\n # Multiple messages (interactive)\n ${APP_NAME} \"Read package.json\" \"What dependencies do we have?\"\n\n # Continue previous session\n ${APP_NAME} --continue \"What did we discuss?\"\n\n # Use different model\n ${APP_NAME} --provider openai --model gpt-4o-mini \"Help me refactor this code\"\n\n # Use model with provider prefix (no --provider needed)\n ${APP_NAME} --model openai/gpt-4o \"Help me refactor this code\"\n\n # Use model with thinking level shorthand\n ${APP_NAME} --model sonnet:high \"Solve this complex problem\"\n\n # Limit model cycling to specific models\n ${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o\n\n # Limit to a specific provider with glob pattern\n ${APP_NAME} --models \"github-copilot/*\"\n\n # Cycle models with fixed thinking levels\n ${APP_NAME} --models sonnet:high,haiku:low\n\n # Start with a specific thinking level\n ${APP_NAME} --thinking high \"Solve this complex problem\"\n\n # Read-only mode (no file modifications possible)\n ${APP_NAME} --tools read,grep,find,ls -p \"Review the code in src/\"\n\n # Export a session file to HTML\n ${APP_NAME} --export ~/${CONFIG_DIR_NAME}/agent/sessions/--path--/session.jsonl\n ${APP_NAME} --export session.jsonl output.html\n\n${chalk.bold(\"Environment Variables:\")}\n ANTHROPIC_API_KEY - Anthropic Claude API key\n ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth token (alternative to API key)\n OPENAI_API_KEY - OpenAI GPT API key\n AZURE_OPENAI_API_KEY - Azure OpenAI API key\n AZURE_OPENAI_BASE_URL - Azure OpenAI/Cognitive Services base URL (e.g. https://{resource}.openai.azure.com)\n AZURE_OPENAI_RESOURCE_NAME - Azure OpenAI resource name (alternative to base URL)\n AZURE_OPENAI_API_VERSION - Azure OpenAI API version (default: v1)\n AZURE_OPENAI_DEPLOYMENT_NAME_MAP - Azure OpenAI model=deployment map (comma-separated)\n DEEPSEEK_API_KEY - DeepSeek API key\n GEMINI_API_KEY - Google Gemini API key\n GROQ_API_KEY - Groq API key\n CEREBRAS_API_KEY - Cerebras API key\n XAI_API_KEY - xAI Grok API key\n FIREWORKS_API_KEY - Fireworks API key\n TOGETHER_API_KEY - Together AI API key\n OPENROUTER_API_KEY - OpenRouter API key\n AI_GATEWAY_API_KEY - Vercel AI Gateway API key\n ZAI_API_KEY - ZAI API key\n MISTRAL_API_KEY - Mistral API key\n MINIMAX_API_KEY - MiniMax API key\n MOONSHOT_API_KEY - Moonshot AI API key\n OPENCODE_API_KEY - OpenCode Zen/OpenCode Go API key\n KIMI_API_KEY - Kimi For Coding API key\n CLOUDFLARE_API_KEY - Cloudflare API token (Workers AI and AI Gateway)\n CLOUDFLARE_ACCOUNT_ID - Cloudflare account id (required for both)\n CLOUDFLARE_GATEWAY_ID - Cloudflare AI Gateway slug (required for AI Gateway)\n XIAOMI_API_KEY - Xiaomi MiMo API key (api.xiaomimimo.com billing)\n XIAOMI_TOKEN_PLAN_CN_API_KEY - Xiaomi MiMo Token Plan API key (China region)\n XIAOMI_TOKEN_PLAN_AMS_API_KEY - Xiaomi MiMo Token Plan API key (Amsterdam region)\n XIAOMI_TOKEN_PLAN_SGP_API_KEY - Xiaomi MiMo Token Plan API key (Singapore region)\n NVIDIA_API_KEY - NVIDIA API key (integrate.api.nvidia.com)\n AWS_PROFILE - AWS profile for Amazon Bedrock\n AWS_ACCESS_KEY_ID - AWS access key for Amazon Bedrock\n AWS_SECRET_ACCESS_KEY - AWS secret key for Amazon Bedrock\n AWS_BEARER_TOKEN_BEDROCK - Bedrock API key (bearer token)\n AWS_REGION - AWS region for Amazon Bedrock (e.g., us-east-1)\n ${ENV_AGENT_DIR.padEnd(32)} - Config directory (default: ~/${CONFIG_DIR_NAME}/agent)\n ${ENV_SESSION_DIR.padEnd(32)} - Session storage directory (overridden by --session-dir)\n HOOCODE_PACKAGE_DIR - Override package directory (for Nix/Guix store paths). Legacy: PI_PACKAGE_DIR\n HOOCODE_OFFLINE - Disable startup network operations when set to 1/true/yes. Legacy: PI_OFFLINE\n HOOCODE_TELEMETRY - Override install telemetry when set to 1/true/yes or 0/false/no. Legacy: PI_TELEMETRY\n HOOCODE_SHARE_VIEWER_URL - Base URL for /share command (default: https://pi.dev/session/). Legacy: PI_SHARE_VIEWER_URL\n\n${chalk.bold(\"Built-in Tool Names:\")}\n read - Read file contents\n bash - Execute bash commands\n edit - Edit files with find/replace\n write - Write files (creates/overwrites)\n grep - Search file contents (read-only, off by default)\n find - Find files by glob pattern (read-only, off by default)\n ls - List directory contents (read-only, off by default)\n`);\n}\n"]}
1
+ {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEjE,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAE3C,MAAM,WAAW,IAAI;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,0FAA0F;IAC1F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yGAAyG;IACzG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,8EAA8E;IAC9E,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC;IAC5C,WAAW,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnE;AAID,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,aAAa,CAE1E;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAmJ9C;AAED,wBAAgB,SAAS,CAAC,cAAc,CAAC,EAAE,aAAa,EAAE,GAAG,IAAI,CA2KhE","sourcesContent":["/**\n * CLI argument parsing and help display\n */\n\nimport type { ThinkingLevel } from \"@kolisachint/hoocode-agent-core\";\nimport chalk from \"chalk\";\nimport { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR, ENV_SESSION_DIR } from \"../config.js\";\nimport type { ExtensionFlag } from \"../core/extensions/types.js\";\n\nexport type Mode = \"text\" | \"json\" | \"rpc\";\n\nexport interface Args {\n\tprovider?: string;\n\tmodel?: string;\n\tapiKey?: string;\n\tsystemPrompt?: string;\n\tthinking?: ThinkingLevel;\n\tcontinue?: boolean;\n\tresume?: boolean;\n\thelp?: boolean;\n\tversion?: boolean;\n\tmode?: Mode;\n\tnoSession?: boolean;\n\t/** Internal: task id assigned by SubagentPool when this process is a spawned subagent. */\n\ttaskId?: string;\n\t/** Hard cap on assistant turns. Near the cap the agent is asked to wrap up; at the cap it is stopped. */\n\tmaxTurns?: number;\n\tsession?: string;\n\tfork?: string;\n\tsessionDir?: string;\n\tmodels?: string[];\n\ttools?: string[];\n\tnoTools?: boolean;\n\tnoBuiltinTools?: boolean;\n\tsubagent?: boolean;\n\textensions?: string[];\n\tnoExtensions?: boolean;\n\tprint?: boolean;\n\texport?: string;\n\tnoSkills?: boolean;\n\tskills?: string[];\n\tagents?: string[];\n\tpromptTemplates?: string[];\n\tnoPromptTemplates?: boolean;\n\tslashCommands?: string[];\n\tnoSlashCommands?: boolean;\n\tthemes?: string[];\n\tnoThemes?: boolean;\n\tmodePaths?: string[];\n\tnoContextFiles?: boolean;\n\tlistModels?: string | true;\n\toffline?: boolean;\n\tverbose?: boolean;\n\tmessages: string[];\n\tfileArgs: string[];\n\t/** Unknown flags (potentially extension flags) - map of flag name to value */\n\tunknownFlags: Map<string, boolean | string>;\n\tdiagnostics: Array<{ type: \"warning\" | \"error\"; message: string }>;\n}\n\nconst VALID_THINKING_LEVELS = [\"off\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\"] as const;\n\nexport function isValidThinkingLevel(level: string): level is ThinkingLevel {\n\treturn VALID_THINKING_LEVELS.includes(level as ThinkingLevel);\n}\n\nexport function parseArgs(args: string[]): Args {\n\tconst result: Args = {\n\t\tmessages: [],\n\t\tfileArgs: [],\n\t\tunknownFlags: new Map(),\n\t\tdiagnostics: [],\n\t};\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\n\t\tif (arg === \"--help\" || arg === \"-h\") {\n\t\t\tresult.help = true;\n\t\t} else if (arg === \"--version\" || arg === \"-v\") {\n\t\t\tresult.version = true;\n\t\t} else if (arg === \"--mode\" && i + 1 < args.length) {\n\t\t\tconst mode = args[++i];\n\t\t\tif (mode === \"text\" || mode === \"json\" || mode === \"rpc\") {\n\t\t\t\tresult.mode = mode;\n\t\t\t}\n\t\t} else if (arg === \"--continue\" || arg === \"-c\") {\n\t\t\tresult.continue = true;\n\t\t} else if (arg === \"--resume\" || arg === \"-r\") {\n\t\t\tresult.resume = true;\n\t\t} else if (arg === \"--provider\" && i + 1 < args.length) {\n\t\t\tresult.provider = args[++i];\n\t\t} else if (arg === \"--model\" && i + 1 < args.length) {\n\t\t\tresult.model = args[++i];\n\t\t} else if (arg === \"--api-key\" && i + 1 < args.length) {\n\t\t\tresult.apiKey = args[++i];\n\t\t} else if (arg === \"--system-prompt\" && i + 1 < args.length) {\n\t\t\tresult.systemPrompt = args[++i];\n\t\t} else if (arg === \"--no-session\") {\n\t\t\tresult.noSession = true;\n\t\t} else if (arg === \"--task-id\" && i + 1 < args.length) {\n\t\t\tresult.taskId = args[++i];\n\t\t} else if (arg === \"--max-turns\" && i + 1 < args.length) {\n\t\t\tconst n = Number.parseInt(args[++i], 10);\n\t\t\tif (Number.isInteger(n) && n > 0) {\n\t\t\t\tresult.maxTurns = n;\n\t\t\t}\n\t\t} else if (arg === \"--session\" && i + 1 < args.length) {\n\t\t\tresult.session = args[++i];\n\t\t} else if (arg === \"--fork\" && i + 1 < args.length) {\n\t\t\tresult.fork = args[++i];\n\t\t} else if (arg === \"--session-dir\" && i + 1 < args.length) {\n\t\t\tresult.sessionDir = args[++i];\n\t\t} else if (arg === \"--models\" && i + 1 < args.length) {\n\t\t\tresult.models = args[++i].split(\",\").map((s) => s.trim());\n\t\t} else if (arg === \"--no-tools\" || arg === \"-nt\") {\n\t\t\tresult.noTools = true;\n\t\t} else if (arg === \"--no-builtin-tools\" || arg === \"-nbt\") {\n\t\t\tresult.noBuiltinTools = true;\n\t\t} else if (arg === \"--enable-subagents\") {\n\t\t\tresult.subagent = true;\n\t\t} else if ((arg === \"--tools\" || arg === \"-t\") && i + 1 < args.length) {\n\t\t\tresult.tools = args[++i]\n\t\t\t\t.split(\",\")\n\t\t\t\t.map((s) => s.trim())\n\t\t\t\t.filter((name) => name.length > 0);\n\t\t} else if (arg === \"--thinking\" && i + 1 < args.length) {\n\t\t\tconst level = args[++i];\n\t\t\tif (isValidThinkingLevel(level)) {\n\t\t\t\tresult.thinking = level;\n\t\t\t} else {\n\t\t\t\tresult.diagnostics.push({\n\t\t\t\t\ttype: \"warning\",\n\t\t\t\t\tmessage: `Invalid thinking level \"${level}\". Valid values: ${VALID_THINKING_LEVELS.join(\", \")}`,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (arg === \"--print\" || arg === \"-p\") {\n\t\t\tresult.print = true;\n\t\t\tconst next = args[i + 1];\n\t\t\tif (next !== undefined && !next.startsWith(\"@\") && (!next.startsWith(\"-\") || next.startsWith(\"---\"))) {\n\t\t\t\tresult.messages.push(next);\n\t\t\t\ti++;\n\t\t\t}\n\t\t} else if (arg === \"--export\" && i + 1 < args.length) {\n\t\t\tresult.export = args[++i];\n\t\t} else if ((arg === \"--extension\" || arg === \"-e\") && i + 1 < args.length) {\n\t\t\tresult.extensions = result.extensions ?? [];\n\t\t\tresult.extensions.push(args[++i]);\n\t\t} else if (arg === \"--no-extensions\" || arg === \"-ne\") {\n\t\t\tresult.noExtensions = true;\n\t\t} else if (arg === \"--skill\" && i + 1 < args.length) {\n\t\t\tresult.skills = result.skills ?? [];\n\t\t\tresult.skills.push(args[++i]);\n\t\t} else if (arg === \"--agent\" && i + 1 < args.length) {\n\t\t\tresult.agents = result.agents ?? [];\n\t\t\tresult.agents.push(args[++i]);\n\t\t} else if (arg === \"--prompt-template\" && i + 1 < args.length) {\n\t\t\tresult.promptTemplates = result.promptTemplates ?? [];\n\t\t\tresult.promptTemplates.push(args[++i]);\n\t\t} else if (arg === \"--slash-command\" && i + 1 < args.length) {\n\t\t\tresult.slashCommands = result.slashCommands ?? [];\n\t\t\tresult.slashCommands.push(args[++i]);\n\t\t} else if (arg === \"--theme\" && i + 1 < args.length) {\n\t\t\tresult.themes = result.themes ?? [];\n\t\t\tresult.themes.push(args[++i]);\n\t\t} else if (arg === \"--mode-path\" && i + 1 < args.length) {\n\t\t\tresult.modePaths = result.modePaths ?? [];\n\t\t\tresult.modePaths.push(args[++i]);\n\t\t} else if (arg === \"--no-skills\" || arg === \"-ns\") {\n\t\t\tresult.noSkills = true;\n\t\t} else if (arg === \"--no-prompt-templates\" || arg === \"-np\") {\n\t\t\tresult.noPromptTemplates = true;\n\t\t} else if (arg === \"--no-slash-commands\" || arg === \"-nsc\") {\n\t\t\tresult.noSlashCommands = true;\n\t\t} else if (arg === \"--no-themes\") {\n\t\t\tresult.noThemes = true;\n\t\t} else if (arg === \"--no-context-files\" || arg === \"-nc\") {\n\t\t\tresult.noContextFiles = true;\n\t\t} else if (arg === \"--list-models\") {\n\t\t\t// Check if next arg is a search pattern (not a flag or file arg)\n\t\t\tif (i + 1 < args.length && !args[i + 1].startsWith(\"-\") && !args[i + 1].startsWith(\"@\")) {\n\t\t\t\tresult.listModels = args[++i];\n\t\t\t} else {\n\t\t\t\tresult.listModels = true;\n\t\t\t}\n\t\t} else if (arg === \"--verbose\") {\n\t\t\tresult.verbose = true;\n\t\t} else if (arg === \"--offline\") {\n\t\t\tresult.offline = true;\n\t\t} else if (arg.startsWith(\"@\")) {\n\t\t\tresult.fileArgs.push(arg.slice(1)); // Remove @ prefix\n\t\t} else if (arg.startsWith(\"--\")) {\n\t\t\tconst eqIndex = arg.indexOf(\"=\");\n\t\t\tif (eqIndex !== -1) {\n\t\t\t\tresult.unknownFlags.set(arg.slice(2, eqIndex), arg.slice(eqIndex + 1));\n\t\t\t} else {\n\t\t\t\tconst flagName = arg.slice(2);\n\t\t\t\tconst next = args[i + 1];\n\t\t\t\tif (next !== undefined && !next.startsWith(\"-\") && !next.startsWith(\"@\")) {\n\t\t\t\t\tresult.unknownFlags.set(flagName, next);\n\t\t\t\t\ti++;\n\t\t\t\t} else {\n\t\t\t\t\tresult.unknownFlags.set(flagName, true);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (arg.startsWith(\"-\") && !arg.startsWith(\"--\")) {\n\t\t\tresult.diagnostics.push({ type: \"error\", message: `Unknown option: ${arg}` });\n\t\t} else if (!arg.startsWith(\"-\")) {\n\t\t\tresult.messages.push(arg);\n\t\t}\n\t}\n\n\treturn result;\n}\n\nexport function printHelp(extensionFlags?: ExtensionFlag[]): void {\n\tconst extensionFlagsText =\n\t\textensionFlags && extensionFlags.length > 0\n\t\t\t? `\\n${chalk.bold(\"Extension CLI Flags:\")}\\n${extensionFlags\n\t\t\t\t\t.map((flag) => {\n\t\t\t\t\t\tconst value = flag.type === \"string\" ? \" <value>\" : \"\";\n\t\t\t\t\t\tconst description = flag.description ?? `Registered by ${flag.extensionPath}`;\n\t\t\t\t\t\treturn ` --${flag.name}${value}`.padEnd(30) + description;\n\t\t\t\t\t})\n\t\t\t\t\t.join(\"\\n\")}\\n`\n\t\t\t: \"\";\n\tconsole.log(`${chalk.bold(APP_NAME)} - AI coding assistant with read, bash, edit, write tools\n\n${chalk.bold(\"Usage:\")}\n ${APP_NAME} [options] [@files...] [messages...]\n\n${chalk.bold(\"Commands:\")}\n ${APP_NAME} install <source> [-l] Install extension source and add to settings\n ${APP_NAME} remove <source> [-l] Remove extension source from settings\n ${APP_NAME} uninstall <source> [-l] Alias for remove\n ${APP_NAME} update [source|self|pi] Update hoocode and installed extensions\n ${APP_NAME} list List installed extensions from settings\n ${APP_NAME} config Open TUI to enable/disable package resources\n ${APP_NAME} <command> --help Show help for install/remove/uninstall/update/list\n\n${chalk.bold(\"Options:\")}\n --provider <name> Provider name (default: google)\n --model <pattern> Model pattern or ID (supports \"provider/id\" and optional \":<thinking>\")\n --api-key <key> API key (defaults to env vars)\n --system-prompt <text> System prompt (default: coding assistant prompt)\n --mode <mode> Output mode: text (default), json, or rpc\n --print, -p Non-interactive mode: process prompt and exit\n --continue, -c Continue previous session\n --resume, -r Select a session to resume\n --session <path|id> Use specific session file or partial UUID\n --fork <path|id> Fork specific session file or partial UUID into a new session\n --session-dir <dir> Directory for session storage and lookup\n --no-session Don't save session (ephemeral)\n --models <patterns> Comma-separated model patterns for Ctrl+P cycling\n Supports globs (anthropic/*, *sonnet*) and fuzzy matching\n --no-tools, -nt Disable all tools by default (built-in and extension)\n --no-builtin-tools, -nbt Disable built-in tools by default but keep extension/custom tools enabled\n --tools, -t <tools> Comma-separated allowlist of tool names to enable\n Applies to built-in, extension, and custom tools\n --max-turns <n> Hard cap on assistant turns; the agent is asked to wrap up near the\n cap and stopped at it (mainly used for spawned subagents)\n --enable-subagents Enable the subagent tool (delegate tasks to isolated agent loops)\n Can also be enabled via the \"enableSubagent\" setting\n --thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh\n --extension, -e <path> Load an extension file (can be used multiple times)\n --no-extensions, -ne Disable extension discovery (explicit -e paths still work)\n --skill <path> Load a skill file or directory (can be used multiple times)\n --no-skills, -ns Disable skills discovery and loading\n --agent <path> Load an agent file or directory (can be used multiple times)\n --prompt-template <path> Load a prompt template / slash command file or directory (repeatable)\n --slash-command <path> Alias of --prompt-template (prompts and slash commands are one feature)\n --no-prompt-templates, -np Disable prompt template / slash command discovery (same as --no-slash-commands)\n --no-slash-commands, -nsc Disable prompt template / slash command discovery (same as --no-prompt-templates)\n --theme <path> Load a theme file or directory (can be used multiple times)\n --no-themes Disable theme discovery and loading\n --mode-path <dir> Add a directory to search for {name}/system.md mode files (can be used multiple times)\n --no-context-files, -nc Disable AGENTS.md and CLAUDE.md discovery and loading\n --export <file> Export session file to HTML and exit\n --list-models [search] List available models (with optional fuzzy search)\n --verbose Force verbose startup (overrides quietStartup setting)\n --offline Disable startup network operations (same as HOOCODE_OFFLINE=1)\n --help, -h Show this help\n --version, -v Show version number\n\nExtensions can register additional flags (e.g., --plan from plan-mode extension).${extensionFlagsText}\n\n${chalk.bold(\"Examples:\")}\n # Interactive mode\n ${APP_NAME}\n\n # Interactive mode with initial prompt\n ${APP_NAME} \"List all .ts files in src/\"\n\n # Include files in initial message\n ${APP_NAME} @prompt.md @image.png \"What color is the sky?\"\n\n # Non-interactive mode (process and exit)\n ${APP_NAME} -p \"List all .ts files in src/\"\n\n # Multiple messages (interactive)\n ${APP_NAME} \"Read package.json\" \"What dependencies do we have?\"\n\n # Continue previous session\n ${APP_NAME} --continue \"What did we discuss?\"\n\n # Use different model\n ${APP_NAME} --provider openai --model gpt-4o-mini \"Help me refactor this code\"\n\n # Use model with provider prefix (no --provider needed)\n ${APP_NAME} --model openai/gpt-4o \"Help me refactor this code\"\n\n # Use model with thinking level shorthand\n ${APP_NAME} --model sonnet:high \"Solve this complex problem\"\n\n # Limit model cycling to specific models\n ${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o\n\n # Limit to a specific provider with glob pattern\n ${APP_NAME} --models \"github-copilot/*\"\n\n # Cycle models with fixed thinking levels\n ${APP_NAME} --models sonnet:high,haiku:low\n\n # Start with a specific thinking level\n ${APP_NAME} --thinking high \"Solve this complex problem\"\n\n # Read-only mode (no file modifications possible)\n ${APP_NAME} --tools read,grep,find,ls -p \"Review the code in src/\"\n\n # Export a session file to HTML\n ${APP_NAME} --export ~/${CONFIG_DIR_NAME}/agent/sessions/--path--/session.jsonl\n ${APP_NAME} --export session.jsonl output.html\n\n${chalk.bold(\"Environment Variables:\")}\n ANTHROPIC_API_KEY - Anthropic Claude API key\n ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth token (alternative to API key)\n OPENAI_API_KEY - OpenAI GPT API key\n AZURE_OPENAI_API_KEY - Azure OpenAI API key\n AZURE_OPENAI_BASE_URL - Azure OpenAI/Cognitive Services base URL (e.g. https://{resource}.openai.azure.com)\n AZURE_OPENAI_RESOURCE_NAME - Azure OpenAI resource name (alternative to base URL)\n AZURE_OPENAI_API_VERSION - Azure OpenAI API version (default: v1)\n AZURE_OPENAI_DEPLOYMENT_NAME_MAP - Azure OpenAI model=deployment map (comma-separated)\n DEEPSEEK_API_KEY - DeepSeek API key\n GEMINI_API_KEY - Google Gemini API key\n GROQ_API_KEY - Groq API key\n CEREBRAS_API_KEY - Cerebras API key\n XAI_API_KEY - xAI Grok API key\n FIREWORKS_API_KEY - Fireworks API key\n TOGETHER_API_KEY - Together AI API key\n OPENROUTER_API_KEY - OpenRouter API key\n AI_GATEWAY_API_KEY - Vercel AI Gateway API key\n ZAI_API_KEY - ZAI API key\n MISTRAL_API_KEY - Mistral API key\n MINIMAX_API_KEY - MiniMax API key\n MOONSHOT_API_KEY - Moonshot AI API key\n OPENCODE_API_KEY - OpenCode Zen/OpenCode Go API key\n KIMI_API_KEY - Kimi For Coding API key\n CLOUDFLARE_API_KEY - Cloudflare API token (Workers AI and AI Gateway)\n CLOUDFLARE_ACCOUNT_ID - Cloudflare account id (required for both)\n CLOUDFLARE_GATEWAY_ID - Cloudflare AI Gateway slug (required for AI Gateway)\n XIAOMI_API_KEY - Xiaomi MiMo API key (api.xiaomimimo.com billing)\n XIAOMI_TOKEN_PLAN_CN_API_KEY - Xiaomi MiMo Token Plan API key (China region)\n XIAOMI_TOKEN_PLAN_AMS_API_KEY - Xiaomi MiMo Token Plan API key (Amsterdam region)\n XIAOMI_TOKEN_PLAN_SGP_API_KEY - Xiaomi MiMo Token Plan API key (Singapore region)\n NVIDIA_API_KEY - NVIDIA API key (integrate.api.nvidia.com)\n AWS_PROFILE - AWS profile for Amazon Bedrock\n AWS_ACCESS_KEY_ID - AWS access key for Amazon Bedrock\n AWS_SECRET_ACCESS_KEY - AWS secret key for Amazon Bedrock\n AWS_BEARER_TOKEN_BEDROCK - Bedrock API key (bearer token)\n AWS_REGION - AWS region for Amazon Bedrock (e.g., us-east-1)\n ${ENV_AGENT_DIR.padEnd(32)} - Config directory (default: ~/${CONFIG_DIR_NAME}/agent)\n ${ENV_SESSION_DIR.padEnd(32)} - Session storage directory (overridden by --session-dir)\n HOOCODE_PACKAGE_DIR - Override package directory (for Nix/Guix store paths). Legacy: PI_PACKAGE_DIR\n HOOCODE_OFFLINE - Disable startup network operations when set to 1/true/yes. Legacy: PI_OFFLINE\n HOOCODE_TELEMETRY - Override install telemetry when set to 1/true/yes or 0/false/no. Legacy: PI_TELEMETRY\n HOOCODE_SHARE_VIEWER_URL - Base URL for /share command (default: https://pi.dev/session/). Legacy: PI_SHARE_VIEWER_URL\n\n${chalk.bold(\"Built-in Tool Names:\")}\n read - Read file contents\n bash - Execute bash commands\n edit - Edit files with find/replace\n write - Write files (creates/overwrites)\n grep - Search file contents (read-only, off by default)\n find - Find files by glob pattern (read-only, off by default)\n ls - List directory contents (read-only, off by default)\n`);\n}\n"]}
package/dist/cli/args.js CHANGED
@@ -244,7 +244,7 @@ ${chalk.bold("Options:")}
244
244
  --max-turns <n> Hard cap on assistant turns; the agent is asked to wrap up near the
245
245
  cap and stopped at it (mainly used for spawned subagents)
246
246
  --enable-subagents Enable the subagent tool (delegate tasks to isolated agent loops)
247
- Also enablable via the "enableSubagent" setting
247
+ Can also be enabled via the "enableSubagent" setting
248
248
  --thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh
249
249
  --extension, -e <path> Load an extension file (can be used multiple times)
250
250
  --no-extensions, -ne Disable extension discovery (explicit -e paths still work)
@@ -1 +1 @@
1
- {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * CLI argument parsing and help display\n */\n\nimport type { ThinkingLevel } from \"@kolisachint/hoocode-agent-core\";\nimport chalk from \"chalk\";\nimport { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR, ENV_SESSION_DIR } from \"../config.js\";\nimport type { ExtensionFlag } from \"../core/extensions/types.js\";\n\nexport type Mode = \"text\" | \"json\" | \"rpc\";\n\nexport interface Args {\n\tprovider?: string;\n\tmodel?: string;\n\tapiKey?: string;\n\tsystemPrompt?: string;\n\tthinking?: ThinkingLevel;\n\tcontinue?: boolean;\n\tresume?: boolean;\n\thelp?: boolean;\n\tversion?: boolean;\n\tmode?: Mode;\n\tnoSession?: boolean;\n\t/** Internal: task id assigned by SubagentPool when this process is a spawned subagent. */\n\ttaskId?: string;\n\t/** Hard cap on assistant turns. Near the cap the agent is asked to wrap up; at the cap it is stopped. */\n\tmaxTurns?: number;\n\tsession?: string;\n\tfork?: string;\n\tsessionDir?: string;\n\tmodels?: string[];\n\ttools?: string[];\n\tnoTools?: boolean;\n\tnoBuiltinTools?: boolean;\n\tsubagent?: boolean;\n\textensions?: string[];\n\tnoExtensions?: boolean;\n\tprint?: boolean;\n\texport?: string;\n\tnoSkills?: boolean;\n\tskills?: string[];\n\tagents?: string[];\n\tpromptTemplates?: string[];\n\tnoPromptTemplates?: boolean;\n\tslashCommands?: string[];\n\tnoSlashCommands?: boolean;\n\tthemes?: string[];\n\tnoThemes?: boolean;\n\tmodePaths?: string[];\n\tnoContextFiles?: boolean;\n\tlistModels?: string | true;\n\toffline?: boolean;\n\tverbose?: boolean;\n\tmessages: string[];\n\tfileArgs: string[];\n\t/** Unknown flags (potentially extension flags) - map of flag name to value */\n\tunknownFlags: Map<string, boolean | string>;\n\tdiagnostics: Array<{ type: \"warning\" | \"error\"; message: string }>;\n}\n\nconst VALID_THINKING_LEVELS = [\"off\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\"] as const;\n\nexport function isValidThinkingLevel(level: string): level is ThinkingLevel {\n\treturn VALID_THINKING_LEVELS.includes(level as ThinkingLevel);\n}\n\nexport function parseArgs(args: string[]): Args {\n\tconst result: Args = {\n\t\tmessages: [],\n\t\tfileArgs: [],\n\t\tunknownFlags: new Map(),\n\t\tdiagnostics: [],\n\t};\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\n\t\tif (arg === \"--help\" || arg === \"-h\") {\n\t\t\tresult.help = true;\n\t\t} else if (arg === \"--version\" || arg === \"-v\") {\n\t\t\tresult.version = true;\n\t\t} else if (arg === \"--mode\" && i + 1 < args.length) {\n\t\t\tconst mode = args[++i];\n\t\t\tif (mode === \"text\" || mode === \"json\" || mode === \"rpc\") {\n\t\t\t\tresult.mode = mode;\n\t\t\t}\n\t\t} else if (arg === \"--continue\" || arg === \"-c\") {\n\t\t\tresult.continue = true;\n\t\t} else if (arg === \"--resume\" || arg === \"-r\") {\n\t\t\tresult.resume = true;\n\t\t} else if (arg === \"--provider\" && i + 1 < args.length) {\n\t\t\tresult.provider = args[++i];\n\t\t} else if (arg === \"--model\" && i + 1 < args.length) {\n\t\t\tresult.model = args[++i];\n\t\t} else if (arg === \"--api-key\" && i + 1 < args.length) {\n\t\t\tresult.apiKey = args[++i];\n\t\t} else if (arg === \"--system-prompt\" && i + 1 < args.length) {\n\t\t\tresult.systemPrompt = args[++i];\n\t\t} else if (arg === \"--no-session\") {\n\t\t\tresult.noSession = true;\n\t\t} else if (arg === \"--task-id\" && i + 1 < args.length) {\n\t\t\tresult.taskId = args[++i];\n\t\t} else if (arg === \"--max-turns\" && i + 1 < args.length) {\n\t\t\tconst n = Number.parseInt(args[++i], 10);\n\t\t\tif (Number.isInteger(n) && n > 0) {\n\t\t\t\tresult.maxTurns = n;\n\t\t\t}\n\t\t} else if (arg === \"--session\" && i + 1 < args.length) {\n\t\t\tresult.session = args[++i];\n\t\t} else if (arg === \"--fork\" && i + 1 < args.length) {\n\t\t\tresult.fork = args[++i];\n\t\t} else if (arg === \"--session-dir\" && i + 1 < args.length) {\n\t\t\tresult.sessionDir = args[++i];\n\t\t} else if (arg === \"--models\" && i + 1 < args.length) {\n\t\t\tresult.models = args[++i].split(\",\").map((s) => s.trim());\n\t\t} else if (arg === \"--no-tools\" || arg === \"-nt\") {\n\t\t\tresult.noTools = true;\n\t\t} else if (arg === \"--no-builtin-tools\" || arg === \"-nbt\") {\n\t\t\tresult.noBuiltinTools = true;\n\t\t} else if (arg === \"--enable-subagents\") {\n\t\t\tresult.subagent = true;\n\t\t} else if ((arg === \"--tools\" || arg === \"-t\") && i + 1 < args.length) {\n\t\t\tresult.tools = args[++i]\n\t\t\t\t.split(\",\")\n\t\t\t\t.map((s) => s.trim())\n\t\t\t\t.filter((name) => name.length > 0);\n\t\t} else if (arg === \"--thinking\" && i + 1 < args.length) {\n\t\t\tconst level = args[++i];\n\t\t\tif (isValidThinkingLevel(level)) {\n\t\t\t\tresult.thinking = level;\n\t\t\t} else {\n\t\t\t\tresult.diagnostics.push({\n\t\t\t\t\ttype: \"warning\",\n\t\t\t\t\tmessage: `Invalid thinking level \"${level}\". Valid values: ${VALID_THINKING_LEVELS.join(\", \")}`,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (arg === \"--print\" || arg === \"-p\") {\n\t\t\tresult.print = true;\n\t\t\tconst next = args[i + 1];\n\t\t\tif (next !== undefined && !next.startsWith(\"@\") && (!next.startsWith(\"-\") || next.startsWith(\"---\"))) {\n\t\t\t\tresult.messages.push(next);\n\t\t\t\ti++;\n\t\t\t}\n\t\t} else if (arg === \"--export\" && i + 1 < args.length) {\n\t\t\tresult.export = args[++i];\n\t\t} else if ((arg === \"--extension\" || arg === \"-e\") && i + 1 < args.length) {\n\t\t\tresult.extensions = result.extensions ?? [];\n\t\t\tresult.extensions.push(args[++i]);\n\t\t} else if (arg === \"--no-extensions\" || arg === \"-ne\") {\n\t\t\tresult.noExtensions = true;\n\t\t} else if (arg === \"--skill\" && i + 1 < args.length) {\n\t\t\tresult.skills = result.skills ?? [];\n\t\t\tresult.skills.push(args[++i]);\n\t\t} else if (arg === \"--agent\" && i + 1 < args.length) {\n\t\t\tresult.agents = result.agents ?? [];\n\t\t\tresult.agents.push(args[++i]);\n\t\t} else if (arg === \"--prompt-template\" && i + 1 < args.length) {\n\t\t\tresult.promptTemplates = result.promptTemplates ?? [];\n\t\t\tresult.promptTemplates.push(args[++i]);\n\t\t} else if (arg === \"--slash-command\" && i + 1 < args.length) {\n\t\t\tresult.slashCommands = result.slashCommands ?? [];\n\t\t\tresult.slashCommands.push(args[++i]);\n\t\t} else if (arg === \"--theme\" && i + 1 < args.length) {\n\t\t\tresult.themes = result.themes ?? [];\n\t\t\tresult.themes.push(args[++i]);\n\t\t} else if (arg === \"--mode-path\" && i + 1 < args.length) {\n\t\t\tresult.modePaths = result.modePaths ?? [];\n\t\t\tresult.modePaths.push(args[++i]);\n\t\t} else if (arg === \"--no-skills\" || arg === \"-ns\") {\n\t\t\tresult.noSkills = true;\n\t\t} else if (arg === \"--no-prompt-templates\" || arg === \"-np\") {\n\t\t\tresult.noPromptTemplates = true;\n\t\t} else if (arg === \"--no-slash-commands\" || arg === \"-nsc\") {\n\t\t\tresult.noSlashCommands = true;\n\t\t} else if (arg === \"--no-themes\") {\n\t\t\tresult.noThemes = true;\n\t\t} else if (arg === \"--no-context-files\" || arg === \"-nc\") {\n\t\t\tresult.noContextFiles = true;\n\t\t} else if (arg === \"--list-models\") {\n\t\t\t// Check if next arg is a search pattern (not a flag or file arg)\n\t\t\tif (i + 1 < args.length && !args[i + 1].startsWith(\"-\") && !args[i + 1].startsWith(\"@\")) {\n\t\t\t\tresult.listModels = args[++i];\n\t\t\t} else {\n\t\t\t\tresult.listModels = true;\n\t\t\t}\n\t\t} else if (arg === \"--verbose\") {\n\t\t\tresult.verbose = true;\n\t\t} else if (arg === \"--offline\") {\n\t\t\tresult.offline = true;\n\t\t} else if (arg.startsWith(\"@\")) {\n\t\t\tresult.fileArgs.push(arg.slice(1)); // Remove @ prefix\n\t\t} else if (arg.startsWith(\"--\")) {\n\t\t\tconst eqIndex = arg.indexOf(\"=\");\n\t\t\tif (eqIndex !== -1) {\n\t\t\t\tresult.unknownFlags.set(arg.slice(2, eqIndex), arg.slice(eqIndex + 1));\n\t\t\t} else {\n\t\t\t\tconst flagName = arg.slice(2);\n\t\t\t\tconst next = args[i + 1];\n\t\t\t\tif (next !== undefined && !next.startsWith(\"-\") && !next.startsWith(\"@\")) {\n\t\t\t\t\tresult.unknownFlags.set(flagName, next);\n\t\t\t\t\ti++;\n\t\t\t\t} else {\n\t\t\t\t\tresult.unknownFlags.set(flagName, true);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (arg.startsWith(\"-\") && !arg.startsWith(\"--\")) {\n\t\t\tresult.diagnostics.push({ type: \"error\", message: `Unknown option: ${arg}` });\n\t\t} else if (!arg.startsWith(\"-\")) {\n\t\t\tresult.messages.push(arg);\n\t\t}\n\t}\n\n\treturn result;\n}\n\nexport function printHelp(extensionFlags?: ExtensionFlag[]): void {\n\tconst extensionFlagsText =\n\t\textensionFlags && extensionFlags.length > 0\n\t\t\t? `\\n${chalk.bold(\"Extension CLI Flags:\")}\\n${extensionFlags\n\t\t\t\t\t.map((flag) => {\n\t\t\t\t\t\tconst value = flag.type === \"string\" ? \" <value>\" : \"\";\n\t\t\t\t\t\tconst description = flag.description ?? `Registered by ${flag.extensionPath}`;\n\t\t\t\t\t\treturn ` --${flag.name}${value}`.padEnd(30) + description;\n\t\t\t\t\t})\n\t\t\t\t\t.join(\"\\n\")}\\n`\n\t\t\t: \"\";\n\tconsole.log(`${chalk.bold(APP_NAME)} - AI coding assistant with read, bash, edit, write tools\n\n${chalk.bold(\"Usage:\")}\n ${APP_NAME} [options] [@files...] [messages...]\n\n${chalk.bold(\"Commands:\")}\n ${APP_NAME} install <source> [-l] Install extension source and add to settings\n ${APP_NAME} remove <source> [-l] Remove extension source from settings\n ${APP_NAME} uninstall <source> [-l] Alias for remove\n ${APP_NAME} update [source|self|pi] Update hoocode and installed extensions\n ${APP_NAME} list List installed extensions from settings\n ${APP_NAME} config Open TUI to enable/disable package resources\n ${APP_NAME} <command> --help Show help for install/remove/uninstall/update/list\n\n${chalk.bold(\"Options:\")}\n --provider <name> Provider name (default: google)\n --model <pattern> Model pattern or ID (supports \"provider/id\" and optional \":<thinking>\")\n --api-key <key> API key (defaults to env vars)\n --system-prompt <text> System prompt (default: coding assistant prompt)\n --mode <mode> Output mode: text (default), json, or rpc\n --print, -p Non-interactive mode: process prompt and exit\n --continue, -c Continue previous session\n --resume, -r Select a session to resume\n --session <path|id> Use specific session file or partial UUID\n --fork <path|id> Fork specific session file or partial UUID into a new session\n --session-dir <dir> Directory for session storage and lookup\n --no-session Don't save session (ephemeral)\n --models <patterns> Comma-separated model patterns for Ctrl+P cycling\n Supports globs (anthropic/*, *sonnet*) and fuzzy matching\n --no-tools, -nt Disable all tools by default (built-in and extension)\n --no-builtin-tools, -nbt Disable built-in tools by default but keep extension/custom tools enabled\n --tools, -t <tools> Comma-separated allowlist of tool names to enable\n Applies to built-in, extension, and custom tools\n --max-turns <n> Hard cap on assistant turns; the agent is asked to wrap up near the\n cap and stopped at it (mainly used for spawned subagents)\n --enable-subagents Enable the subagent tool (delegate tasks to isolated agent loops)\n Also enablable via the \"enableSubagent\" setting\n --thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh\n --extension, -e <path> Load an extension file (can be used multiple times)\n --no-extensions, -ne Disable extension discovery (explicit -e paths still work)\n --skill <path> Load a skill file or directory (can be used multiple times)\n --no-skills, -ns Disable skills discovery and loading\n --agent <path> Load an agent file or directory (can be used multiple times)\n --prompt-template <path> Load a prompt template / slash command file or directory (repeatable)\n --slash-command <path> Alias of --prompt-template (prompts and slash commands are one feature)\n --no-prompt-templates, -np Disable prompt template / slash command discovery (same as --no-slash-commands)\n --no-slash-commands, -nsc Disable prompt template / slash command discovery (same as --no-prompt-templates)\n --theme <path> Load a theme file or directory (can be used multiple times)\n --no-themes Disable theme discovery and loading\n --mode-path <dir> Add a directory to search for {name}/system.md mode files (can be used multiple times)\n --no-context-files, -nc Disable AGENTS.md and CLAUDE.md discovery and loading\n --export <file> Export session file to HTML and exit\n --list-models [search] List available models (with optional fuzzy search)\n --verbose Force verbose startup (overrides quietStartup setting)\n --offline Disable startup network operations (same as HOOCODE_OFFLINE=1)\n --help, -h Show this help\n --version, -v Show version number\n\nExtensions can register additional flags (e.g., --plan from plan-mode extension).${extensionFlagsText}\n\n${chalk.bold(\"Examples:\")}\n # Interactive mode\n ${APP_NAME}\n\n # Interactive mode with initial prompt\n ${APP_NAME} \"List all .ts files in src/\"\n\n # Include files in initial message\n ${APP_NAME} @prompt.md @image.png \"What color is the sky?\"\n\n # Non-interactive mode (process and exit)\n ${APP_NAME} -p \"List all .ts files in src/\"\n\n # Multiple messages (interactive)\n ${APP_NAME} \"Read package.json\" \"What dependencies do we have?\"\n\n # Continue previous session\n ${APP_NAME} --continue \"What did we discuss?\"\n\n # Use different model\n ${APP_NAME} --provider openai --model gpt-4o-mini \"Help me refactor this code\"\n\n # Use model with provider prefix (no --provider needed)\n ${APP_NAME} --model openai/gpt-4o \"Help me refactor this code\"\n\n # Use model with thinking level shorthand\n ${APP_NAME} --model sonnet:high \"Solve this complex problem\"\n\n # Limit model cycling to specific models\n ${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o\n\n # Limit to a specific provider with glob pattern\n ${APP_NAME} --models \"github-copilot/*\"\n\n # Cycle models with fixed thinking levels\n ${APP_NAME} --models sonnet:high,haiku:low\n\n # Start with a specific thinking level\n ${APP_NAME} --thinking high \"Solve this complex problem\"\n\n # Read-only mode (no file modifications possible)\n ${APP_NAME} --tools read,grep,find,ls -p \"Review the code in src/\"\n\n # Export a session file to HTML\n ${APP_NAME} --export ~/${CONFIG_DIR_NAME}/agent/sessions/--path--/session.jsonl\n ${APP_NAME} --export session.jsonl output.html\n\n${chalk.bold(\"Environment Variables:\")}\n ANTHROPIC_API_KEY - Anthropic Claude API key\n ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth token (alternative to API key)\n OPENAI_API_KEY - OpenAI GPT API key\n AZURE_OPENAI_API_KEY - Azure OpenAI API key\n AZURE_OPENAI_BASE_URL - Azure OpenAI/Cognitive Services base URL (e.g. https://{resource}.openai.azure.com)\n AZURE_OPENAI_RESOURCE_NAME - Azure OpenAI resource name (alternative to base URL)\n AZURE_OPENAI_API_VERSION - Azure OpenAI API version (default: v1)\n AZURE_OPENAI_DEPLOYMENT_NAME_MAP - Azure OpenAI model=deployment map (comma-separated)\n DEEPSEEK_API_KEY - DeepSeek API key\n GEMINI_API_KEY - Google Gemini API key\n GROQ_API_KEY - Groq API key\n CEREBRAS_API_KEY - Cerebras API key\n XAI_API_KEY - xAI Grok API key\n FIREWORKS_API_KEY - Fireworks API key\n TOGETHER_API_KEY - Together AI API key\n OPENROUTER_API_KEY - OpenRouter API key\n AI_GATEWAY_API_KEY - Vercel AI Gateway API key\n ZAI_API_KEY - ZAI API key\n MISTRAL_API_KEY - Mistral API key\n MINIMAX_API_KEY - MiniMax API key\n MOONSHOT_API_KEY - Moonshot AI API key\n OPENCODE_API_KEY - OpenCode Zen/OpenCode Go API key\n KIMI_API_KEY - Kimi For Coding API key\n CLOUDFLARE_API_KEY - Cloudflare API token (Workers AI and AI Gateway)\n CLOUDFLARE_ACCOUNT_ID - Cloudflare account id (required for both)\n CLOUDFLARE_GATEWAY_ID - Cloudflare AI Gateway slug (required for AI Gateway)\n XIAOMI_API_KEY - Xiaomi MiMo API key (api.xiaomimimo.com billing)\n XIAOMI_TOKEN_PLAN_CN_API_KEY - Xiaomi MiMo Token Plan API key (China region)\n XIAOMI_TOKEN_PLAN_AMS_API_KEY - Xiaomi MiMo Token Plan API key (Amsterdam region)\n XIAOMI_TOKEN_PLAN_SGP_API_KEY - Xiaomi MiMo Token Plan API key (Singapore region)\n NVIDIA_API_KEY - NVIDIA API key (integrate.api.nvidia.com)\n AWS_PROFILE - AWS profile for Amazon Bedrock\n AWS_ACCESS_KEY_ID - AWS access key for Amazon Bedrock\n AWS_SECRET_ACCESS_KEY - AWS secret key for Amazon Bedrock\n AWS_BEARER_TOKEN_BEDROCK - Bedrock API key (bearer token)\n AWS_REGION - AWS region for Amazon Bedrock (e.g., us-east-1)\n ${ENV_AGENT_DIR.padEnd(32)} - Config directory (default: ~/${CONFIG_DIR_NAME}/agent)\n ${ENV_SESSION_DIR.padEnd(32)} - Session storage directory (overridden by --session-dir)\n HOOCODE_PACKAGE_DIR - Override package directory (for Nix/Guix store paths). Legacy: PI_PACKAGE_DIR\n HOOCODE_OFFLINE - Disable startup network operations when set to 1/true/yes. Legacy: PI_OFFLINE\n HOOCODE_TELEMETRY - Override install telemetry when set to 1/true/yes or 0/false/no. Legacy: PI_TELEMETRY\n HOOCODE_SHARE_VIEWER_URL - Base URL for /share command (default: https://pi.dev/session/). Legacy: PI_SHARE_VIEWER_URL\n\n${chalk.bold(\"Built-in Tool Names:\")}\n read - Read file contents\n bash - Execute bash commands\n edit - Edit files with find/replace\n write - Write files (creates/overwrites)\n grep - Search file contents (read-only, off by default)\n find - Find files by glob pattern (read-only, off by default)\n ls - List directory contents (read-only, off by default)\n`);\n}\n"]}
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * CLI argument parsing and help display\n */\n\nimport type { ThinkingLevel } from \"@kolisachint/hoocode-agent-core\";\nimport chalk from \"chalk\";\nimport { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR, ENV_SESSION_DIR } from \"../config.js\";\nimport type { ExtensionFlag } from \"../core/extensions/types.js\";\n\nexport type Mode = \"text\" | \"json\" | \"rpc\";\n\nexport interface Args {\n\tprovider?: string;\n\tmodel?: string;\n\tapiKey?: string;\n\tsystemPrompt?: string;\n\tthinking?: ThinkingLevel;\n\tcontinue?: boolean;\n\tresume?: boolean;\n\thelp?: boolean;\n\tversion?: boolean;\n\tmode?: Mode;\n\tnoSession?: boolean;\n\t/** Internal: task id assigned by SubagentPool when this process is a spawned subagent. */\n\ttaskId?: string;\n\t/** Hard cap on assistant turns. Near the cap the agent is asked to wrap up; at the cap it is stopped. */\n\tmaxTurns?: number;\n\tsession?: string;\n\tfork?: string;\n\tsessionDir?: string;\n\tmodels?: string[];\n\ttools?: string[];\n\tnoTools?: boolean;\n\tnoBuiltinTools?: boolean;\n\tsubagent?: boolean;\n\textensions?: string[];\n\tnoExtensions?: boolean;\n\tprint?: boolean;\n\texport?: string;\n\tnoSkills?: boolean;\n\tskills?: string[];\n\tagents?: string[];\n\tpromptTemplates?: string[];\n\tnoPromptTemplates?: boolean;\n\tslashCommands?: string[];\n\tnoSlashCommands?: boolean;\n\tthemes?: string[];\n\tnoThemes?: boolean;\n\tmodePaths?: string[];\n\tnoContextFiles?: boolean;\n\tlistModels?: string | true;\n\toffline?: boolean;\n\tverbose?: boolean;\n\tmessages: string[];\n\tfileArgs: string[];\n\t/** Unknown flags (potentially extension flags) - map of flag name to value */\n\tunknownFlags: Map<string, boolean | string>;\n\tdiagnostics: Array<{ type: \"warning\" | \"error\"; message: string }>;\n}\n\nconst VALID_THINKING_LEVELS = [\"off\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\"] as const;\n\nexport function isValidThinkingLevel(level: string): level is ThinkingLevel {\n\treturn VALID_THINKING_LEVELS.includes(level as ThinkingLevel);\n}\n\nexport function parseArgs(args: string[]): Args {\n\tconst result: Args = {\n\t\tmessages: [],\n\t\tfileArgs: [],\n\t\tunknownFlags: new Map(),\n\t\tdiagnostics: [],\n\t};\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\n\t\tif (arg === \"--help\" || arg === \"-h\") {\n\t\t\tresult.help = true;\n\t\t} else if (arg === \"--version\" || arg === \"-v\") {\n\t\t\tresult.version = true;\n\t\t} else if (arg === \"--mode\" && i + 1 < args.length) {\n\t\t\tconst mode = args[++i];\n\t\t\tif (mode === \"text\" || mode === \"json\" || mode === \"rpc\") {\n\t\t\t\tresult.mode = mode;\n\t\t\t}\n\t\t} else if (arg === \"--continue\" || arg === \"-c\") {\n\t\t\tresult.continue = true;\n\t\t} else if (arg === \"--resume\" || arg === \"-r\") {\n\t\t\tresult.resume = true;\n\t\t} else if (arg === \"--provider\" && i + 1 < args.length) {\n\t\t\tresult.provider = args[++i];\n\t\t} else if (arg === \"--model\" && i + 1 < args.length) {\n\t\t\tresult.model = args[++i];\n\t\t} else if (arg === \"--api-key\" && i + 1 < args.length) {\n\t\t\tresult.apiKey = args[++i];\n\t\t} else if (arg === \"--system-prompt\" && i + 1 < args.length) {\n\t\t\tresult.systemPrompt = args[++i];\n\t\t} else if (arg === \"--no-session\") {\n\t\t\tresult.noSession = true;\n\t\t} else if (arg === \"--task-id\" && i + 1 < args.length) {\n\t\t\tresult.taskId = args[++i];\n\t\t} else if (arg === \"--max-turns\" && i + 1 < args.length) {\n\t\t\tconst n = Number.parseInt(args[++i], 10);\n\t\t\tif (Number.isInteger(n) && n > 0) {\n\t\t\t\tresult.maxTurns = n;\n\t\t\t}\n\t\t} else if (arg === \"--session\" && i + 1 < args.length) {\n\t\t\tresult.session = args[++i];\n\t\t} else if (arg === \"--fork\" && i + 1 < args.length) {\n\t\t\tresult.fork = args[++i];\n\t\t} else if (arg === \"--session-dir\" && i + 1 < args.length) {\n\t\t\tresult.sessionDir = args[++i];\n\t\t} else if (arg === \"--models\" && i + 1 < args.length) {\n\t\t\tresult.models = args[++i].split(\",\").map((s) => s.trim());\n\t\t} else if (arg === \"--no-tools\" || arg === \"-nt\") {\n\t\t\tresult.noTools = true;\n\t\t} else if (arg === \"--no-builtin-tools\" || arg === \"-nbt\") {\n\t\t\tresult.noBuiltinTools = true;\n\t\t} else if (arg === \"--enable-subagents\") {\n\t\t\tresult.subagent = true;\n\t\t} else if ((arg === \"--tools\" || arg === \"-t\") && i + 1 < args.length) {\n\t\t\tresult.tools = args[++i]\n\t\t\t\t.split(\",\")\n\t\t\t\t.map((s) => s.trim())\n\t\t\t\t.filter((name) => name.length > 0);\n\t\t} else if (arg === \"--thinking\" && i + 1 < args.length) {\n\t\t\tconst level = args[++i];\n\t\t\tif (isValidThinkingLevel(level)) {\n\t\t\t\tresult.thinking = level;\n\t\t\t} else {\n\t\t\t\tresult.diagnostics.push({\n\t\t\t\t\ttype: \"warning\",\n\t\t\t\t\tmessage: `Invalid thinking level \"${level}\". Valid values: ${VALID_THINKING_LEVELS.join(\", \")}`,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (arg === \"--print\" || arg === \"-p\") {\n\t\t\tresult.print = true;\n\t\t\tconst next = args[i + 1];\n\t\t\tif (next !== undefined && !next.startsWith(\"@\") && (!next.startsWith(\"-\") || next.startsWith(\"---\"))) {\n\t\t\t\tresult.messages.push(next);\n\t\t\t\ti++;\n\t\t\t}\n\t\t} else if (arg === \"--export\" && i + 1 < args.length) {\n\t\t\tresult.export = args[++i];\n\t\t} else if ((arg === \"--extension\" || arg === \"-e\") && i + 1 < args.length) {\n\t\t\tresult.extensions = result.extensions ?? [];\n\t\t\tresult.extensions.push(args[++i]);\n\t\t} else if (arg === \"--no-extensions\" || arg === \"-ne\") {\n\t\t\tresult.noExtensions = true;\n\t\t} else if (arg === \"--skill\" && i + 1 < args.length) {\n\t\t\tresult.skills = result.skills ?? [];\n\t\t\tresult.skills.push(args[++i]);\n\t\t} else if (arg === \"--agent\" && i + 1 < args.length) {\n\t\t\tresult.agents = result.agents ?? [];\n\t\t\tresult.agents.push(args[++i]);\n\t\t} else if (arg === \"--prompt-template\" && i + 1 < args.length) {\n\t\t\tresult.promptTemplates = result.promptTemplates ?? [];\n\t\t\tresult.promptTemplates.push(args[++i]);\n\t\t} else if (arg === \"--slash-command\" && i + 1 < args.length) {\n\t\t\tresult.slashCommands = result.slashCommands ?? [];\n\t\t\tresult.slashCommands.push(args[++i]);\n\t\t} else if (arg === \"--theme\" && i + 1 < args.length) {\n\t\t\tresult.themes = result.themes ?? [];\n\t\t\tresult.themes.push(args[++i]);\n\t\t} else if (arg === \"--mode-path\" && i + 1 < args.length) {\n\t\t\tresult.modePaths = result.modePaths ?? [];\n\t\t\tresult.modePaths.push(args[++i]);\n\t\t} else if (arg === \"--no-skills\" || arg === \"-ns\") {\n\t\t\tresult.noSkills = true;\n\t\t} else if (arg === \"--no-prompt-templates\" || arg === \"-np\") {\n\t\t\tresult.noPromptTemplates = true;\n\t\t} else if (arg === \"--no-slash-commands\" || arg === \"-nsc\") {\n\t\t\tresult.noSlashCommands = true;\n\t\t} else if (arg === \"--no-themes\") {\n\t\t\tresult.noThemes = true;\n\t\t} else if (arg === \"--no-context-files\" || arg === \"-nc\") {\n\t\t\tresult.noContextFiles = true;\n\t\t} else if (arg === \"--list-models\") {\n\t\t\t// Check if next arg is a search pattern (not a flag or file arg)\n\t\t\tif (i + 1 < args.length && !args[i + 1].startsWith(\"-\") && !args[i + 1].startsWith(\"@\")) {\n\t\t\t\tresult.listModels = args[++i];\n\t\t\t} else {\n\t\t\t\tresult.listModels = true;\n\t\t\t}\n\t\t} else if (arg === \"--verbose\") {\n\t\t\tresult.verbose = true;\n\t\t} else if (arg === \"--offline\") {\n\t\t\tresult.offline = true;\n\t\t} else if (arg.startsWith(\"@\")) {\n\t\t\tresult.fileArgs.push(arg.slice(1)); // Remove @ prefix\n\t\t} else if (arg.startsWith(\"--\")) {\n\t\t\tconst eqIndex = arg.indexOf(\"=\");\n\t\t\tif (eqIndex !== -1) {\n\t\t\t\tresult.unknownFlags.set(arg.slice(2, eqIndex), arg.slice(eqIndex + 1));\n\t\t\t} else {\n\t\t\t\tconst flagName = arg.slice(2);\n\t\t\t\tconst next = args[i + 1];\n\t\t\t\tif (next !== undefined && !next.startsWith(\"-\") && !next.startsWith(\"@\")) {\n\t\t\t\t\tresult.unknownFlags.set(flagName, next);\n\t\t\t\t\ti++;\n\t\t\t\t} else {\n\t\t\t\t\tresult.unknownFlags.set(flagName, true);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (arg.startsWith(\"-\") && !arg.startsWith(\"--\")) {\n\t\t\tresult.diagnostics.push({ type: \"error\", message: `Unknown option: ${arg}` });\n\t\t} else if (!arg.startsWith(\"-\")) {\n\t\t\tresult.messages.push(arg);\n\t\t}\n\t}\n\n\treturn result;\n}\n\nexport function printHelp(extensionFlags?: ExtensionFlag[]): void {\n\tconst extensionFlagsText =\n\t\textensionFlags && extensionFlags.length > 0\n\t\t\t? `\\n${chalk.bold(\"Extension CLI Flags:\")}\\n${extensionFlags\n\t\t\t\t\t.map((flag) => {\n\t\t\t\t\t\tconst value = flag.type === \"string\" ? \" <value>\" : \"\";\n\t\t\t\t\t\tconst description = flag.description ?? `Registered by ${flag.extensionPath}`;\n\t\t\t\t\t\treturn ` --${flag.name}${value}`.padEnd(30) + description;\n\t\t\t\t\t})\n\t\t\t\t\t.join(\"\\n\")}\\n`\n\t\t\t: \"\";\n\tconsole.log(`${chalk.bold(APP_NAME)} - AI coding assistant with read, bash, edit, write tools\n\n${chalk.bold(\"Usage:\")}\n ${APP_NAME} [options] [@files...] [messages...]\n\n${chalk.bold(\"Commands:\")}\n ${APP_NAME} install <source> [-l] Install extension source and add to settings\n ${APP_NAME} remove <source> [-l] Remove extension source from settings\n ${APP_NAME} uninstall <source> [-l] Alias for remove\n ${APP_NAME} update [source|self|pi] Update hoocode and installed extensions\n ${APP_NAME} list List installed extensions from settings\n ${APP_NAME} config Open TUI to enable/disable package resources\n ${APP_NAME} <command> --help Show help for install/remove/uninstall/update/list\n\n${chalk.bold(\"Options:\")}\n --provider <name> Provider name (default: google)\n --model <pattern> Model pattern or ID (supports \"provider/id\" and optional \":<thinking>\")\n --api-key <key> API key (defaults to env vars)\n --system-prompt <text> System prompt (default: coding assistant prompt)\n --mode <mode> Output mode: text (default), json, or rpc\n --print, -p Non-interactive mode: process prompt and exit\n --continue, -c Continue previous session\n --resume, -r Select a session to resume\n --session <path|id> Use specific session file or partial UUID\n --fork <path|id> Fork specific session file or partial UUID into a new session\n --session-dir <dir> Directory for session storage and lookup\n --no-session Don't save session (ephemeral)\n --models <patterns> Comma-separated model patterns for Ctrl+P cycling\n Supports globs (anthropic/*, *sonnet*) and fuzzy matching\n --no-tools, -nt Disable all tools by default (built-in and extension)\n --no-builtin-tools, -nbt Disable built-in tools by default but keep extension/custom tools enabled\n --tools, -t <tools> Comma-separated allowlist of tool names to enable\n Applies to built-in, extension, and custom tools\n --max-turns <n> Hard cap on assistant turns; the agent is asked to wrap up near the\n cap and stopped at it (mainly used for spawned subagents)\n --enable-subagents Enable the subagent tool (delegate tasks to isolated agent loops)\n Can also be enabled via the \"enableSubagent\" setting\n --thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh\n --extension, -e <path> Load an extension file (can be used multiple times)\n --no-extensions, -ne Disable extension discovery (explicit -e paths still work)\n --skill <path> Load a skill file or directory (can be used multiple times)\n --no-skills, -ns Disable skills discovery and loading\n --agent <path> Load an agent file or directory (can be used multiple times)\n --prompt-template <path> Load a prompt template / slash command file or directory (repeatable)\n --slash-command <path> Alias of --prompt-template (prompts and slash commands are one feature)\n --no-prompt-templates, -np Disable prompt template / slash command discovery (same as --no-slash-commands)\n --no-slash-commands, -nsc Disable prompt template / slash command discovery (same as --no-prompt-templates)\n --theme <path> Load a theme file or directory (can be used multiple times)\n --no-themes Disable theme discovery and loading\n --mode-path <dir> Add a directory to search for {name}/system.md mode files (can be used multiple times)\n --no-context-files, -nc Disable AGENTS.md and CLAUDE.md discovery and loading\n --export <file> Export session file to HTML and exit\n --list-models [search] List available models (with optional fuzzy search)\n --verbose Force verbose startup (overrides quietStartup setting)\n --offline Disable startup network operations (same as HOOCODE_OFFLINE=1)\n --help, -h Show this help\n --version, -v Show version number\n\nExtensions can register additional flags (e.g., --plan from plan-mode extension).${extensionFlagsText}\n\n${chalk.bold(\"Examples:\")}\n # Interactive mode\n ${APP_NAME}\n\n # Interactive mode with initial prompt\n ${APP_NAME} \"List all .ts files in src/\"\n\n # Include files in initial message\n ${APP_NAME} @prompt.md @image.png \"What color is the sky?\"\n\n # Non-interactive mode (process and exit)\n ${APP_NAME} -p \"List all .ts files in src/\"\n\n # Multiple messages (interactive)\n ${APP_NAME} \"Read package.json\" \"What dependencies do we have?\"\n\n # Continue previous session\n ${APP_NAME} --continue \"What did we discuss?\"\n\n # Use different model\n ${APP_NAME} --provider openai --model gpt-4o-mini \"Help me refactor this code\"\n\n # Use model with provider prefix (no --provider needed)\n ${APP_NAME} --model openai/gpt-4o \"Help me refactor this code\"\n\n # Use model with thinking level shorthand\n ${APP_NAME} --model sonnet:high \"Solve this complex problem\"\n\n # Limit model cycling to specific models\n ${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o\n\n # Limit to a specific provider with glob pattern\n ${APP_NAME} --models \"github-copilot/*\"\n\n # Cycle models with fixed thinking levels\n ${APP_NAME} --models sonnet:high,haiku:low\n\n # Start with a specific thinking level\n ${APP_NAME} --thinking high \"Solve this complex problem\"\n\n # Read-only mode (no file modifications possible)\n ${APP_NAME} --tools read,grep,find,ls -p \"Review the code in src/\"\n\n # Export a session file to HTML\n ${APP_NAME} --export ~/${CONFIG_DIR_NAME}/agent/sessions/--path--/session.jsonl\n ${APP_NAME} --export session.jsonl output.html\n\n${chalk.bold(\"Environment Variables:\")}\n ANTHROPIC_API_KEY - Anthropic Claude API key\n ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth token (alternative to API key)\n OPENAI_API_KEY - OpenAI GPT API key\n AZURE_OPENAI_API_KEY - Azure OpenAI API key\n AZURE_OPENAI_BASE_URL - Azure OpenAI/Cognitive Services base URL (e.g. https://{resource}.openai.azure.com)\n AZURE_OPENAI_RESOURCE_NAME - Azure OpenAI resource name (alternative to base URL)\n AZURE_OPENAI_API_VERSION - Azure OpenAI API version (default: v1)\n AZURE_OPENAI_DEPLOYMENT_NAME_MAP - Azure OpenAI model=deployment map (comma-separated)\n DEEPSEEK_API_KEY - DeepSeek API key\n GEMINI_API_KEY - Google Gemini API key\n GROQ_API_KEY - Groq API key\n CEREBRAS_API_KEY - Cerebras API key\n XAI_API_KEY - xAI Grok API key\n FIREWORKS_API_KEY - Fireworks API key\n TOGETHER_API_KEY - Together AI API key\n OPENROUTER_API_KEY - OpenRouter API key\n AI_GATEWAY_API_KEY - Vercel AI Gateway API key\n ZAI_API_KEY - ZAI API key\n MISTRAL_API_KEY - Mistral API key\n MINIMAX_API_KEY - MiniMax API key\n MOONSHOT_API_KEY - Moonshot AI API key\n OPENCODE_API_KEY - OpenCode Zen/OpenCode Go API key\n KIMI_API_KEY - Kimi For Coding API key\n CLOUDFLARE_API_KEY - Cloudflare API token (Workers AI and AI Gateway)\n CLOUDFLARE_ACCOUNT_ID - Cloudflare account id (required for both)\n CLOUDFLARE_GATEWAY_ID - Cloudflare AI Gateway slug (required for AI Gateway)\n XIAOMI_API_KEY - Xiaomi MiMo API key (api.xiaomimimo.com billing)\n XIAOMI_TOKEN_PLAN_CN_API_KEY - Xiaomi MiMo Token Plan API key (China region)\n XIAOMI_TOKEN_PLAN_AMS_API_KEY - Xiaomi MiMo Token Plan API key (Amsterdam region)\n XIAOMI_TOKEN_PLAN_SGP_API_KEY - Xiaomi MiMo Token Plan API key (Singapore region)\n NVIDIA_API_KEY - NVIDIA API key (integrate.api.nvidia.com)\n AWS_PROFILE - AWS profile for Amazon Bedrock\n AWS_ACCESS_KEY_ID - AWS access key for Amazon Bedrock\n AWS_SECRET_ACCESS_KEY - AWS secret key for Amazon Bedrock\n AWS_BEARER_TOKEN_BEDROCK - Bedrock API key (bearer token)\n AWS_REGION - AWS region for Amazon Bedrock (e.g., us-east-1)\n ${ENV_AGENT_DIR.padEnd(32)} - Config directory (default: ~/${CONFIG_DIR_NAME}/agent)\n ${ENV_SESSION_DIR.padEnd(32)} - Session storage directory (overridden by --session-dir)\n HOOCODE_PACKAGE_DIR - Override package directory (for Nix/Guix store paths). Legacy: PI_PACKAGE_DIR\n HOOCODE_OFFLINE - Disable startup network operations when set to 1/true/yes. Legacy: PI_OFFLINE\n HOOCODE_TELEMETRY - Override install telemetry when set to 1/true/yes or 0/false/no. Legacy: PI_TELEMETRY\n HOOCODE_SHARE_VIEWER_URL - Base URL for /share command (default: https://pi.dev/session/). Legacy: PI_SHARE_VIEWER_URL\n\n${chalk.bold(\"Built-in Tool Names:\")}\n read - Read file contents\n bash - Execute bash commands\n edit - Edit files with find/replace\n write - Write files (creates/overwrites)\n grep - Search file contents (read-only, off by default)\n find - Find files by glob pattern (read-only, off by default)\n ls - List directory contents (read-only, off by default)\n`);\n}\n"]}
@@ -15,11 +15,22 @@ export declare class SubagentLifeguard extends EventEmitter {
15
15
  private processes;
16
16
  private lastHeartbeat;
17
17
  private timeouts;
18
+ /** When each task started, used to compute the load-scaled hard timeout. */
19
+ private startedAt;
20
+ /** Per-agent base hard timeout (before load scaling), captured at monitor(). */
21
+ private baseTimeoutMs;
18
22
  private checkInterval;
23
+ /** Wall-clock time the heartbeat check last ran, to measure event-loop lag. */
24
+ private lastCheckAt;
19
25
  private disposed;
20
26
  private readonly cwd;
21
27
  private parentShutdownHandler?;
22
28
  constructor(cwd: string);
29
+ /**
30
+ * Tolerance multiplier for the current load. 1 process → 1x; each extra
31
+ * concurrent process adds LOAD_TOLERANCE_PER_PROCESS, capped at MAX_LOAD_MULTIPLIER.
32
+ */
33
+ private loadMultiplier;
23
34
  /**
24
35
  * Begin monitoring a child process. The process must emit a
25
36
  * `{"ping":true}` JSON line on stdout every 30 seconds.
@@ -1 +1 @@
1
- {"version":3,"file":"lifeguard.d.ts","sourceRoot":"","sources":["../../src/core/lifeguard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgB3C,MAAM,WAAW,gBAAgB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;CACtB;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,YAAY;IAClD,OAAO,CAAC,SAAS,CAAuC;IACxD,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,QAAQ,CAAqC;IACrD,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,qBAAqB,CAAC,CAAa;IAE3C,YAAY,GAAG,EAAE,MAAM,EAMtB;IAED;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAgBrE;IAED,+CAA+C;IAC/C,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAIrC;IAED,0DAA0D;IAC1D,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE9C;IAED,qDAAqD;IACrD,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAErC;IAED,iDAAiD;IACjD,OAAO,IAAI,IAAI,CA2Bd;IAED,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,OAAO;IAUf,OAAO,CAAC,uBAAuB;IAQ/B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,cAAc;IAyBtB,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,IAAI;CAoBZ","sourcesContent":["import type { ChildProcess } from \"node:child_process\";\nimport { EventEmitter } from \"node:events\";\nimport { existsSync, readdirSync, readFileSync, rmdirSync, statSync, unlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getDispatchRoot } from \"../config.js\";\n\nconst TIMEOUTS_MS: Record<string, number> = {\n\texplore: 5 * 60 * 1000,\n\tedit: 10 * 60 * 1000,\n\ttest: 10 * 60 * 1000,\n\treview: 8 * 60 * 1000,\n\tdoc: 5 * 60 * 1000,\n};\n\nconst HEARTBEAT_MISS_THRESHOLD_MS = 60000;\nconst PARENT_SHUTDOWN_GRACE_MS = 5000;\n\nexport interface LifeguardProcess {\n\tpid: number;\n\ttask_id: string;\n\tagent_type: string;\n\tprocess: ChildProcess;\n}\n\n/**\n * Monitors running subagent processes for heartbeats, hard timeouts,\n * and parent-exit cleanup. Emits \"stalled\" and \"timeout\" events when\n * processes are terminated.\n */\nexport class SubagentLifeguard extends EventEmitter {\n\tprivate processes = new Map<string, LifeguardProcess>();\n\tprivate lastHeartbeat = new Map<string, number>();\n\tprivate timeouts = new Map<string, NodeJS.Timeout>();\n\tprivate checkInterval: NodeJS.Timeout | null = null;\n\tprivate disposed = false;\n\tprivate readonly cwd: string;\n\tprivate parentShutdownHandler?: () => void;\n\n\tconstructor(cwd: string) {\n\t\tsuper();\n\t\tthis.cwd = cwd;\n\t\tthis.setupParentExitHandlers();\n\t\tthis.sweepOldAgents();\n\t\tthis.checkInterval = setInterval(() => this.checkHeartbeats(), 5000);\n\t}\n\n\t/**\n\t * Begin monitoring a child process. The process must emit a\n\t * `{\"ping\":true}` JSON line on stdout every 30 seconds.\n\t */\n\tmonitor(task_id: string, agent_type: string, proc: ChildProcess): void {\n\t\tif (this.disposed) return;\n\n\t\tconst pid = proc.pid ?? 0;\n\t\tthis.processes.set(task_id, { pid, task_id, agent_type, process: proc });\n\t\tthis.lastHeartbeat.set(task_id, Date.now());\n\n\t\tconst timeoutMs = TIMEOUTS_MS[agent_type] ?? TIMEOUTS_MS.explore;\n\t\tconst timeout = setTimeout(() => {\n\t\t\tthis.handleTimeout(task_id);\n\t\t}, timeoutMs);\n\t\tthis.timeouts.set(task_id, timeout);\n\n\t\tproc.once(\"exit\", () => {\n\t\t\tthis.untrack(task_id);\n\t\t});\n\t}\n\n\t/** Record a heartbeat for a monitored task. */\n\trecordHeartbeat(task_id: string): void {\n\t\tif (this.processes.has(task_id)) {\n\t\t\tthis.lastHeartbeat.set(task_id, Date.now());\n\t\t}\n\t}\n\n\t/** Get the last recorded heartbeat timestamp, or null. */\n\tlastHeartbeatAt(task_id: string): number | null {\n\t\treturn this.lastHeartbeat.get(task_id) ?? null;\n\t}\n\n\t/** True if the task is currently being monitored. */\n\tisMonitoring(task_id: string): boolean {\n\t\treturn this.processes.has(task_id);\n\t}\n\n\t/** Kill all monitored processes and clean up. */\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\n\t\tif (this.checkInterval) {\n\t\t\tclearInterval(this.checkInterval);\n\t\t\tthis.checkInterval = null;\n\t\t}\n\n\t\tfor (const timeout of this.timeouts.values()) {\n\t\t\tclearTimeout(timeout);\n\t\t}\n\t\tthis.timeouts.clear();\n\n\t\tfor (const monitored of this.processes.values()) {\n\t\t\tif (!monitored.process.killed) {\n\t\t\t\tmonitored.process.kill(\"SIGKILL\");\n\t\t\t}\n\t\t}\n\t\tthis.processes.clear();\n\t\tthis.lastHeartbeat.clear();\n\t\tthis.removeAllListeners();\n\n\t\tif (this.parentShutdownHandler) {\n\t\t\tprocess.removeListener(\"SIGINT\", this.parentShutdownHandler);\n\t\t\tprocess.removeListener(\"SIGTERM\", this.parentShutdownHandler);\n\t\t}\n\t}\n\n\tprivate checkHeartbeats(): void {\n\t\tconst now = Date.now();\n\t\tfor (const [task_id] of this.processes) {\n\t\t\tconst last = this.lastHeartbeat.get(task_id);\n\t\t\tif (last === undefined) continue;\n\t\t\tif (now - last > HEARTBEAT_MISS_THRESHOLD_MS) {\n\t\t\t\tthis.handleStalled(task_id);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handleStalled(task_id: string): void {\n\t\tconst monitored = this.processes.get(task_id);\n\t\tif (!monitored) return;\n\n\t\tif (!monitored.process.killed) {\n\t\t\tmonitored.process.kill(\"SIGKILL\");\n\t\t}\n\n\t\tthis.emit(\"stalled\", { task_id, pid: monitored.pid });\n\t\t// Process exit handler will call untrack()\n\t}\n\n\tprivate handleTimeout(task_id: string): void {\n\t\tconst monitored = this.processes.get(task_id);\n\t\tif (!monitored) return;\n\n\t\tif (!monitored.process.killed) {\n\t\t\tmonitored.process.kill(\"SIGKILL\");\n\t\t}\n\n\t\tthis.emit(\"timeout\", { task_id, pid: monitored.pid });\n\t\tthis.timeouts.delete(task_id);\n\t\t// Process exit handler will call untrack()\n\t}\n\n\tprivate untrack(task_id: string): void {\n\t\tconst timeout = this.timeouts.get(task_id);\n\t\tif (timeout) {\n\t\t\tclearTimeout(timeout);\n\t\t\tthis.timeouts.delete(task_id);\n\t\t}\n\t\tthis.processes.delete(task_id);\n\t\tthis.lastHeartbeat.delete(task_id);\n\t}\n\n\tprivate setupParentExitHandlers(): void {\n\t\tconst shutdown = () => this.gracefulShutdown();\n\t\tthis.parentShutdownHandler = shutdown;\n\t\tprocess.setMaxListeners(Math.max(process.getMaxListeners(), 20));\n\t\tprocess.once(\"SIGINT\", shutdown);\n\t\tprocess.once(\"SIGTERM\", shutdown);\n\t}\n\n\tprivate gracefulShutdown(): void {\n\t\t// SIGTERM all children\n\t\tfor (const monitored of this.processes.values()) {\n\t\t\tif (!monitored.process.killed) {\n\t\t\t\tmonitored.process.kill(\"SIGTERM\");\n\t\t\t}\n\t\t}\n\n\t\t// SIGKILL after grace period\n\t\tsetTimeout(() => {\n\t\t\tfor (const monitored of this.processes.values()) {\n\t\t\t\tif (!monitored.process.killed) {\n\t\t\t\t\tmonitored.process.kill(\"SIGKILL\");\n\t\t\t\t}\n\t\t\t}\n\t\t}, PARENT_SHUTDOWN_GRACE_MS).unref();\n\t}\n\n\tprivate sweepOldAgents(): void {\n\t\tconst dispatchDir = getDispatchRoot(this.cwd);\n\t\tif (!existsSync(dispatchDir)) return;\n\n\t\tconst now = Date.now();\n\t\tconst cutoff = 24 * 60 * 60 * 1000; // 24 hours\n\n\t\tfor (const entry of readdirSync(dispatchDir)) {\n\t\t\tconst entryPath = join(dispatchDir, entry);\n\t\t\ttry {\n\t\t\t\tconst stats = statSync(entryPath);\n\t\t\t\tif (!stats.isDirectory()) continue;\n\n\t\t\t\tif (now - stats.mtimeMs > cutoff) {\n\t\t\t\t\tconst hasRunningPid = this.hasRunningPid(entryPath);\n\t\t\t\t\tif (!hasRunningPid) {\n\t\t\t\t\t\tthis.rmrf(entryPath);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Ignore errors for individual entries\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate hasRunningPid(dir: string): boolean {\n\t\tconst pidFile = join(dir, \"pid\");\n\t\tif (!existsSync(pidFile)) return false;\n\n\t\ttry {\n\t\t\tconst pid = Number.parseInt(readFileSync(pidFile, \"utf-8\"), 10);\n\t\t\tif (Number.isNaN(pid)) return false;\n\t\t\tprocess.kill(pid, 0); // Check if process exists\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprivate rmrf(dir: string): void {\n\t\tfor (const entry of readdirSync(dir)) {\n\t\t\tconst entryPath = join(dir, entry);\n\t\t\ttry {\n\t\t\t\tconst stats = statSync(entryPath);\n\t\t\t\tif (stats.isDirectory()) {\n\t\t\t\t\tthis.rmrf(entryPath);\n\t\t\t\t} else {\n\t\t\t\t\tunlinkSync(entryPath);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Ignore\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\trmdirSync(dir);\n\t\t} catch {\n\t\t\t// Ignore\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"lifeguard.d.ts","sourceRoot":"","sources":["../../src/core/lifeguard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAoC3C,MAAM,WAAW,gBAAgB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;CACtB;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,YAAY;IAClD,OAAO,CAAC,SAAS,CAAuC;IACxD,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,QAAQ,CAAqC;IACrD,4EAA4E;IAC5E,OAAO,CAAC,SAAS,CAA6B;IAC9C,gFAAgF;IAChF,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,aAAa,CAA+B;IACpD,+EAA+E;IAC/E,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,qBAAqB,CAAC,CAAa;IAE3C,YAAY,GAAG,EAAE,MAAM,EAOtB;IAED;;;OAGG;IACH,OAAO,CAAC,cAAc;IAMtB;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAwBrE;IAED,+CAA+C;IAC/C,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAIrC;IAED,0DAA0D;IAC1D,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE9C;IAED,qDAAqD;IACrD,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAErC;IAED,iDAAiD;IACjD,OAAO,IAAI,IAAI,CA6Bd;IAED,OAAO,CAAC,eAAe;IAoBvB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,aAAa;IAkCrB,OAAO,CAAC,OAAO;IAYf,OAAO,CAAC,uBAAuB;IAQ/B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,cAAc;IAyBtB,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,IAAI;CAoBZ","sourcesContent":["import type { ChildProcess } from \"node:child_process\";\nimport { EventEmitter } from \"node:events\";\nimport { existsSync, readdirSync, readFileSync, rmdirSync, statSync, unlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getDispatchRoot } from \"../config.js\";\n\nconst TIMEOUTS_MS: Record<string, number> = {\n\texplore: 5 * 60 * 1000,\n\tedit: 10 * 60 * 1000,\n\ttest: 10 * 60 * 1000,\n\treview: 8 * 60 * 1000,\n\tdoc: 5 * 60 * 1000,\n};\n\nconst HEARTBEAT_MISS_THRESHOLD_MS = 60000;\nconst HEARTBEAT_CHECK_INTERVAL_MS = 5000;\nconst PARENT_SHUTDOWN_GRACE_MS = 5000;\n\n/**\n * Concurrency tolerance. Each *additional* concurrently-monitored subagent adds\n * this fraction to the heartbeat-miss and hard-timeout budgets.\n *\n * When several subagents run at once (plus background MCP tools), they saturate\n * the CPU and starve the parent's event loop: its `setInterval` heartbeat check\n * fires late, and it cannot read a child's `{\"ping\":true}` line in time even\n * though the child is healthy and still working. Scaling the budgets by load\n * stops that contention from false-positive SIGKILLing healthy subagents — the\n * failure the demo hit when running many agents + MCP tools in the background.\n */\nconst LOAD_TOLERANCE_PER_PROCESS = 0.5;\n\n/**\n * Hard ceiling on the load tolerance multiplier, so a genuinely stuck subagent\n * is still eventually reaped no matter how busy the pool is.\n */\nconst MAX_LOAD_MULTIPLIER = 4;\n\nexport interface LifeguardProcess {\n\tpid: number;\n\ttask_id: string;\n\tagent_type: string;\n\tprocess: ChildProcess;\n}\n\n/**\n * Monitors running subagent processes for heartbeats, hard timeouts,\n * and parent-exit cleanup. Emits \"stalled\" and \"timeout\" events when\n * processes are terminated.\n */\nexport class SubagentLifeguard extends EventEmitter {\n\tprivate processes = new Map<string, LifeguardProcess>();\n\tprivate lastHeartbeat = new Map<string, number>();\n\tprivate timeouts = new Map<string, NodeJS.Timeout>();\n\t/** When each task started, used to compute the load-scaled hard timeout. */\n\tprivate startedAt = new Map<string, number>();\n\t/** Per-agent base hard timeout (before load scaling), captured at monitor(). */\n\tprivate baseTimeoutMs = new Map<string, number>();\n\tprivate checkInterval: NodeJS.Timeout | null = null;\n\t/** Wall-clock time the heartbeat check last ran, to measure event-loop lag. */\n\tprivate lastCheckAt = Date.now();\n\tprivate disposed = false;\n\tprivate readonly cwd: string;\n\tprivate parentShutdownHandler?: () => void;\n\n\tconstructor(cwd: string) {\n\t\tsuper();\n\t\tthis.cwd = cwd;\n\t\tthis.setupParentExitHandlers();\n\t\tthis.sweepOldAgents();\n\t\tthis.lastCheckAt = Date.now();\n\t\tthis.checkInterval = setInterval(() => this.checkHeartbeats(), HEARTBEAT_CHECK_INTERVAL_MS);\n\t}\n\n\t/**\n\t * Tolerance multiplier for the current load. 1 process → 1x; each extra\n\t * concurrent process adds LOAD_TOLERANCE_PER_PROCESS, capped at MAX_LOAD_MULTIPLIER.\n\t */\n\tprivate loadMultiplier(): number {\n\t\tconst concurrent = this.processes.size;\n\t\tconst mult = 1 + Math.max(0, concurrent - 1) * LOAD_TOLERANCE_PER_PROCESS;\n\t\treturn Math.min(mult, MAX_LOAD_MULTIPLIER);\n\t}\n\n\t/**\n\t * Begin monitoring a child process. The process must emit a\n\t * `{\"ping\":true}` JSON line on stdout every 30 seconds.\n\t */\n\tmonitor(task_id: string, agent_type: string, proc: ChildProcess): void {\n\t\tif (this.disposed) return;\n\n\t\tconst pid = proc.pid ?? 0;\n\t\tthis.processes.set(task_id, { pid, task_id, agent_type, process: proc });\n\t\tthis.lastHeartbeat.set(task_id, Date.now());\n\n\t\tconst timeoutMs = TIMEOUTS_MS[agent_type] ?? TIMEOUTS_MS.explore;\n\t\tthis.startedAt.set(task_id, Date.now());\n\t\tthis.baseTimeoutMs.set(task_id, timeoutMs);\n\t\t// Arm the hard timeout scaled by current load. When it fires, handleTimeout\n\t\t// re-checks load and re-arms (up to MAX_LOAD_MULTIPLIER) if the pool is still\n\t\t// busy, so a slow-but-progressing subagent isn't killed for CPU contention.\n\t\tconst timeout = setTimeout(\n\t\t\t() => {\n\t\t\t\tthis.handleTimeout(task_id);\n\t\t\t},\n\t\t\tMath.round(timeoutMs * this.loadMultiplier()),\n\t\t);\n\t\tthis.timeouts.set(task_id, timeout);\n\n\t\tproc.once(\"exit\", () => {\n\t\t\tthis.untrack(task_id);\n\t\t});\n\t}\n\n\t/** Record a heartbeat for a monitored task. */\n\trecordHeartbeat(task_id: string): void {\n\t\tif (this.processes.has(task_id)) {\n\t\t\tthis.lastHeartbeat.set(task_id, Date.now());\n\t\t}\n\t}\n\n\t/** Get the last recorded heartbeat timestamp, or null. */\n\tlastHeartbeatAt(task_id: string): number | null {\n\t\treturn this.lastHeartbeat.get(task_id) ?? null;\n\t}\n\n\t/** True if the task is currently being monitored. */\n\tisMonitoring(task_id: string): boolean {\n\t\treturn this.processes.has(task_id);\n\t}\n\n\t/** Kill all monitored processes and clean up. */\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\n\t\tif (this.checkInterval) {\n\t\t\tclearInterval(this.checkInterval);\n\t\t\tthis.checkInterval = null;\n\t\t}\n\n\t\tfor (const timeout of this.timeouts.values()) {\n\t\t\tclearTimeout(timeout);\n\t\t}\n\t\tthis.timeouts.clear();\n\n\t\tfor (const monitored of this.processes.values()) {\n\t\t\tif (!monitored.process.killed) {\n\t\t\t\tmonitored.process.kill(\"SIGKILL\");\n\t\t\t}\n\t\t}\n\t\tthis.processes.clear();\n\t\tthis.lastHeartbeat.clear();\n\t\tthis.startedAt.clear();\n\t\tthis.baseTimeoutMs.clear();\n\t\tthis.removeAllListeners();\n\n\t\tif (this.parentShutdownHandler) {\n\t\t\tprocess.removeListener(\"SIGINT\", this.parentShutdownHandler);\n\t\t\tprocess.removeListener(\"SIGTERM\", this.parentShutdownHandler);\n\t\t}\n\t}\n\n\tprivate checkHeartbeats(): void {\n\t\tconst now = Date.now();\n\t\t// Event-loop lag: how much later than scheduled this check actually ran.\n\t\t// Heavy CPU load (many concurrent subagents + background MCP tools) starves\n\t\t// the loop, delaying both this check *and* our reading of child heartbeats.\n\t\t// Forgive that gap so the parent's own starvation isn't charged against the\n\t\t// children as a missed heartbeat.\n\t\tconst loopLag = Math.max(0, now - this.lastCheckAt - HEARTBEAT_CHECK_INTERVAL_MS);\n\t\tthis.lastCheckAt = now;\n\n\t\tconst threshold = HEARTBEAT_MISS_THRESHOLD_MS * this.loadMultiplier() + loopLag;\n\t\tfor (const [task_id] of this.processes) {\n\t\t\tconst last = this.lastHeartbeat.get(task_id);\n\t\t\tif (last === undefined) continue;\n\t\t\tif (now - last > threshold) {\n\t\t\t\tthis.handleStalled(task_id);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handleStalled(task_id: string): void {\n\t\tconst monitored = this.processes.get(task_id);\n\t\tif (!monitored) return;\n\n\t\tif (!monitored.process.killed) {\n\t\t\tmonitored.process.kill(\"SIGKILL\");\n\t\t}\n\n\t\tthis.emit(\"stalled\", { task_id, pid: monitored.pid });\n\t\t// Process exit handler will call untrack()\n\t}\n\n\tprivate handleTimeout(task_id: string): void {\n\t\tconst monitored = this.processes.get(task_id);\n\t\tif (!monitored) return;\n\n\t\t// Under load the wall-clock timer can fire while the subagent is still doing\n\t\t// real work — its turns are just slow because the CPU is shared. Re-arm rather\n\t\t// than kill, up to a hard ceiling (base * MAX_LOAD_MULTIPLIER) so a genuinely\n\t\t// stuck agent still terminates.\n\t\tconst started = this.startedAt.get(task_id) ?? Date.now();\n\t\tconst base = this.baseTimeoutMs.get(task_id) ?? TIMEOUTS_MS[monitored.agent_type] ?? TIMEOUTS_MS.explore;\n\t\tconst elapsed = Date.now() - started;\n\t\tconst ceiling = base * MAX_LOAD_MULTIPLIER;\n\t\tif (this.loadMultiplier() > 1 && elapsed < ceiling) {\n\t\t\tconst remaining = ceiling - elapsed;\n\t\t\tconst next = Math.min(\n\t\t\t\tMath.round(base * this.loadMultiplier()),\n\t\t\t\tMath.max(HEARTBEAT_CHECK_INTERVAL_MS, remaining),\n\t\t\t);\n\t\t\tthis.timeouts.set(\n\t\t\t\ttask_id,\n\t\t\t\tsetTimeout(() => this.handleTimeout(task_id), next),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!monitored.process.killed) {\n\t\t\tmonitored.process.kill(\"SIGKILL\");\n\t\t}\n\n\t\tthis.emit(\"timeout\", { task_id, pid: monitored.pid });\n\t\tthis.timeouts.delete(task_id);\n\t\t// Process exit handler will call untrack()\n\t}\n\n\tprivate untrack(task_id: string): void {\n\t\tconst timeout = this.timeouts.get(task_id);\n\t\tif (timeout) {\n\t\t\tclearTimeout(timeout);\n\t\t\tthis.timeouts.delete(task_id);\n\t\t}\n\t\tthis.processes.delete(task_id);\n\t\tthis.lastHeartbeat.delete(task_id);\n\t\tthis.startedAt.delete(task_id);\n\t\tthis.baseTimeoutMs.delete(task_id);\n\t}\n\n\tprivate setupParentExitHandlers(): void {\n\t\tconst shutdown = () => this.gracefulShutdown();\n\t\tthis.parentShutdownHandler = shutdown;\n\t\tprocess.setMaxListeners(Math.max(process.getMaxListeners(), 20));\n\t\tprocess.once(\"SIGINT\", shutdown);\n\t\tprocess.once(\"SIGTERM\", shutdown);\n\t}\n\n\tprivate gracefulShutdown(): void {\n\t\t// SIGTERM all children\n\t\tfor (const monitored of this.processes.values()) {\n\t\t\tif (!monitored.process.killed) {\n\t\t\t\tmonitored.process.kill(\"SIGTERM\");\n\t\t\t}\n\t\t}\n\n\t\t// SIGKILL after grace period\n\t\tsetTimeout(() => {\n\t\t\tfor (const monitored of this.processes.values()) {\n\t\t\t\tif (!monitored.process.killed) {\n\t\t\t\t\tmonitored.process.kill(\"SIGKILL\");\n\t\t\t\t}\n\t\t\t}\n\t\t}, PARENT_SHUTDOWN_GRACE_MS).unref();\n\t}\n\n\tprivate sweepOldAgents(): void {\n\t\tconst dispatchDir = getDispatchRoot(this.cwd);\n\t\tif (!existsSync(dispatchDir)) return;\n\n\t\tconst now = Date.now();\n\t\tconst cutoff = 24 * 60 * 60 * 1000; // 24 hours\n\n\t\tfor (const entry of readdirSync(dispatchDir)) {\n\t\t\tconst entryPath = join(dispatchDir, entry);\n\t\t\ttry {\n\t\t\t\tconst stats = statSync(entryPath);\n\t\t\t\tif (!stats.isDirectory()) continue;\n\n\t\t\t\tif (now - stats.mtimeMs > cutoff) {\n\t\t\t\t\tconst hasRunningPid = this.hasRunningPid(entryPath);\n\t\t\t\t\tif (!hasRunningPid) {\n\t\t\t\t\t\tthis.rmrf(entryPath);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Ignore errors for individual entries\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate hasRunningPid(dir: string): boolean {\n\t\tconst pidFile = join(dir, \"pid\");\n\t\tif (!existsSync(pidFile)) return false;\n\n\t\ttry {\n\t\t\tconst pid = Number.parseInt(readFileSync(pidFile, \"utf-8\"), 10);\n\t\t\tif (Number.isNaN(pid)) return false;\n\t\t\tprocess.kill(pid, 0); // Check if process exists\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprivate rmrf(dir: string): void {\n\t\tfor (const entry of readdirSync(dir)) {\n\t\t\tconst entryPath = join(dir, entry);\n\t\t\ttry {\n\t\t\t\tconst stats = statSync(entryPath);\n\t\t\t\tif (stats.isDirectory()) {\n\t\t\t\t\tthis.rmrf(entryPath);\n\t\t\t\t} else {\n\t\t\t\t\tunlinkSync(entryPath);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Ignore\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\trmdirSync(dir);\n\t\t} catch {\n\t\t\t// Ignore\n\t\t}\n\t}\n}\n"]}
@@ -10,7 +10,25 @@ const TIMEOUTS_MS = {
10
10
  doc: 5 * 60 * 1000,
11
11
  };
12
12
  const HEARTBEAT_MISS_THRESHOLD_MS = 60000;
13
+ const HEARTBEAT_CHECK_INTERVAL_MS = 5000;
13
14
  const PARENT_SHUTDOWN_GRACE_MS = 5000;
15
+ /**
16
+ * Concurrency tolerance. Each *additional* concurrently-monitored subagent adds
17
+ * this fraction to the heartbeat-miss and hard-timeout budgets.
18
+ *
19
+ * When several subagents run at once (plus background MCP tools), they saturate
20
+ * the CPU and starve the parent's event loop: its `setInterval` heartbeat check
21
+ * fires late, and it cannot read a child's `{"ping":true}` line in time even
22
+ * though the child is healthy and still working. Scaling the budgets by load
23
+ * stops that contention from false-positive SIGKILLing healthy subagents — the
24
+ * failure the demo hit when running many agents + MCP tools in the background.
25
+ */
26
+ const LOAD_TOLERANCE_PER_PROCESS = 0.5;
27
+ /**
28
+ * Hard ceiling on the load tolerance multiplier, so a genuinely stuck subagent
29
+ * is still eventually reaped no matter how busy the pool is.
30
+ */
31
+ const MAX_LOAD_MULTIPLIER = 4;
14
32
  /**
15
33
  * Monitors running subagent processes for heartbeats, hard timeouts,
16
34
  * and parent-exit cleanup. Emits "stalled" and "timeout" events when
@@ -20,7 +38,13 @@ export class SubagentLifeguard extends EventEmitter {
20
38
  processes = new Map();
21
39
  lastHeartbeat = new Map();
22
40
  timeouts = new Map();
41
+ /** When each task started, used to compute the load-scaled hard timeout. */
42
+ startedAt = new Map();
43
+ /** Per-agent base hard timeout (before load scaling), captured at monitor(). */
44
+ baseTimeoutMs = new Map();
23
45
  checkInterval = null;
46
+ /** Wall-clock time the heartbeat check last ran, to measure event-loop lag. */
47
+ lastCheckAt = Date.now();
24
48
  disposed = false;
25
49
  cwd;
26
50
  parentShutdownHandler;
@@ -29,7 +53,17 @@ export class SubagentLifeguard extends EventEmitter {
29
53
  this.cwd = cwd;
30
54
  this.setupParentExitHandlers();
31
55
  this.sweepOldAgents();
32
- this.checkInterval = setInterval(() => this.checkHeartbeats(), 5000);
56
+ this.lastCheckAt = Date.now();
57
+ this.checkInterval = setInterval(() => this.checkHeartbeats(), HEARTBEAT_CHECK_INTERVAL_MS);
58
+ }
59
+ /**
60
+ * Tolerance multiplier for the current load. 1 process → 1x; each extra
61
+ * concurrent process adds LOAD_TOLERANCE_PER_PROCESS, capped at MAX_LOAD_MULTIPLIER.
62
+ */
63
+ loadMultiplier() {
64
+ const concurrent = this.processes.size;
65
+ const mult = 1 + Math.max(0, concurrent - 1) * LOAD_TOLERANCE_PER_PROCESS;
66
+ return Math.min(mult, MAX_LOAD_MULTIPLIER);
33
67
  }
34
68
  /**
35
69
  * Begin monitoring a child process. The process must emit a
@@ -42,9 +76,14 @@ export class SubagentLifeguard extends EventEmitter {
42
76
  this.processes.set(task_id, { pid, task_id, agent_type, process: proc });
43
77
  this.lastHeartbeat.set(task_id, Date.now());
44
78
  const timeoutMs = TIMEOUTS_MS[agent_type] ?? TIMEOUTS_MS.explore;
79
+ this.startedAt.set(task_id, Date.now());
80
+ this.baseTimeoutMs.set(task_id, timeoutMs);
81
+ // Arm the hard timeout scaled by current load. When it fires, handleTimeout
82
+ // re-checks load and re-arms (up to MAX_LOAD_MULTIPLIER) if the pool is still
83
+ // busy, so a slow-but-progressing subagent isn't killed for CPU contention.
45
84
  const timeout = setTimeout(() => {
46
85
  this.handleTimeout(task_id);
47
- }, timeoutMs);
86
+ }, Math.round(timeoutMs * this.loadMultiplier()));
48
87
  this.timeouts.set(task_id, timeout);
49
88
  proc.once("exit", () => {
50
89
  this.untrack(task_id);
@@ -84,6 +123,8 @@ export class SubagentLifeguard extends EventEmitter {
84
123
  }
85
124
  this.processes.clear();
86
125
  this.lastHeartbeat.clear();
126
+ this.startedAt.clear();
127
+ this.baseTimeoutMs.clear();
87
128
  this.removeAllListeners();
88
129
  if (this.parentShutdownHandler) {
89
130
  process.removeListener("SIGINT", this.parentShutdownHandler);
@@ -92,11 +133,19 @@ export class SubagentLifeguard extends EventEmitter {
92
133
  }
93
134
  checkHeartbeats() {
94
135
  const now = Date.now();
136
+ // Event-loop lag: how much later than scheduled this check actually ran.
137
+ // Heavy CPU load (many concurrent subagents + background MCP tools) starves
138
+ // the loop, delaying both this check *and* our reading of child heartbeats.
139
+ // Forgive that gap so the parent's own starvation isn't charged against the
140
+ // children as a missed heartbeat.
141
+ const loopLag = Math.max(0, now - this.lastCheckAt - HEARTBEAT_CHECK_INTERVAL_MS);
142
+ this.lastCheckAt = now;
143
+ const threshold = HEARTBEAT_MISS_THRESHOLD_MS * this.loadMultiplier() + loopLag;
95
144
  for (const [task_id] of this.processes) {
96
145
  const last = this.lastHeartbeat.get(task_id);
97
146
  if (last === undefined)
98
147
  continue;
99
- if (now - last > HEARTBEAT_MISS_THRESHOLD_MS) {
148
+ if (now - last > threshold) {
100
149
  this.handleStalled(task_id);
101
150
  }
102
151
  }
@@ -115,6 +164,20 @@ export class SubagentLifeguard extends EventEmitter {
115
164
  const monitored = this.processes.get(task_id);
116
165
  if (!monitored)
117
166
  return;
167
+ // Under load the wall-clock timer can fire while the subagent is still doing
168
+ // real work — its turns are just slow because the CPU is shared. Re-arm rather
169
+ // than kill, up to a hard ceiling (base * MAX_LOAD_MULTIPLIER) so a genuinely
170
+ // stuck agent still terminates.
171
+ const started = this.startedAt.get(task_id) ?? Date.now();
172
+ const base = this.baseTimeoutMs.get(task_id) ?? TIMEOUTS_MS[monitored.agent_type] ?? TIMEOUTS_MS.explore;
173
+ const elapsed = Date.now() - started;
174
+ const ceiling = base * MAX_LOAD_MULTIPLIER;
175
+ if (this.loadMultiplier() > 1 && elapsed < ceiling) {
176
+ const remaining = ceiling - elapsed;
177
+ const next = Math.min(Math.round(base * this.loadMultiplier()), Math.max(HEARTBEAT_CHECK_INTERVAL_MS, remaining));
178
+ this.timeouts.set(task_id, setTimeout(() => this.handleTimeout(task_id), next));
179
+ return;
180
+ }
118
181
  if (!monitored.process.killed) {
119
182
  monitored.process.kill("SIGKILL");
120
183
  }
@@ -130,6 +193,8 @@ export class SubagentLifeguard extends EventEmitter {
130
193
  }
131
194
  this.processes.delete(task_id);
132
195
  this.lastHeartbeat.delete(task_id);
196
+ this.startedAt.delete(task_id);
197
+ this.baseTimeoutMs.delete(task_id);
133
198
  }
134
199
  setupParentExitHandlers() {
135
200
  const shutdown = () => this.gracefulShutdown();
@@ -1 +1 @@
1
- {"version":3,"file":"lifeguard.js","sourceRoot":"","sources":["../../src/core/lifeguard.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ChildProcess } from \"node:child_process\";\nimport { EventEmitter } from \"node:events\";\nimport { existsSync, readdirSync, readFileSync, rmdirSync, statSync, unlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getDispatchRoot } from \"../config.js\";\n\nconst TIMEOUTS_MS: Record<string, number> = {\n\texplore: 5 * 60 * 1000,\n\tedit: 10 * 60 * 1000,\n\ttest: 10 * 60 * 1000,\n\treview: 8 * 60 * 1000,\n\tdoc: 5 * 60 * 1000,\n};\n\nconst HEARTBEAT_MISS_THRESHOLD_MS = 60000;\nconst PARENT_SHUTDOWN_GRACE_MS = 5000;\n\nexport interface LifeguardProcess {\n\tpid: number;\n\ttask_id: string;\n\tagent_type: string;\n\tprocess: ChildProcess;\n}\n\n/**\n * Monitors running subagent processes for heartbeats, hard timeouts,\n * and parent-exit cleanup. Emits \"stalled\" and \"timeout\" events when\n * processes are terminated.\n */\nexport class SubagentLifeguard extends EventEmitter {\n\tprivate processes = new Map<string, LifeguardProcess>();\n\tprivate lastHeartbeat = new Map<string, number>();\n\tprivate timeouts = new Map<string, NodeJS.Timeout>();\n\tprivate checkInterval: NodeJS.Timeout | null = null;\n\tprivate disposed = false;\n\tprivate readonly cwd: string;\n\tprivate parentShutdownHandler?: () => void;\n\n\tconstructor(cwd: string) {\n\t\tsuper();\n\t\tthis.cwd = cwd;\n\t\tthis.setupParentExitHandlers();\n\t\tthis.sweepOldAgents();\n\t\tthis.checkInterval = setInterval(() => this.checkHeartbeats(), 5000);\n\t}\n\n\t/**\n\t * Begin monitoring a child process. The process must emit a\n\t * `{\"ping\":true}` JSON line on stdout every 30 seconds.\n\t */\n\tmonitor(task_id: string, agent_type: string, proc: ChildProcess): void {\n\t\tif (this.disposed) return;\n\n\t\tconst pid = proc.pid ?? 0;\n\t\tthis.processes.set(task_id, { pid, task_id, agent_type, process: proc });\n\t\tthis.lastHeartbeat.set(task_id, Date.now());\n\n\t\tconst timeoutMs = TIMEOUTS_MS[agent_type] ?? TIMEOUTS_MS.explore;\n\t\tconst timeout = setTimeout(() => {\n\t\t\tthis.handleTimeout(task_id);\n\t\t}, timeoutMs);\n\t\tthis.timeouts.set(task_id, timeout);\n\n\t\tproc.once(\"exit\", () => {\n\t\t\tthis.untrack(task_id);\n\t\t});\n\t}\n\n\t/** Record a heartbeat for a monitored task. */\n\trecordHeartbeat(task_id: string): void {\n\t\tif (this.processes.has(task_id)) {\n\t\t\tthis.lastHeartbeat.set(task_id, Date.now());\n\t\t}\n\t}\n\n\t/** Get the last recorded heartbeat timestamp, or null. */\n\tlastHeartbeatAt(task_id: string): number | null {\n\t\treturn this.lastHeartbeat.get(task_id) ?? null;\n\t}\n\n\t/** True if the task is currently being monitored. */\n\tisMonitoring(task_id: string): boolean {\n\t\treturn this.processes.has(task_id);\n\t}\n\n\t/** Kill all monitored processes and clean up. */\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\n\t\tif (this.checkInterval) {\n\t\t\tclearInterval(this.checkInterval);\n\t\t\tthis.checkInterval = null;\n\t\t}\n\n\t\tfor (const timeout of this.timeouts.values()) {\n\t\t\tclearTimeout(timeout);\n\t\t}\n\t\tthis.timeouts.clear();\n\n\t\tfor (const monitored of this.processes.values()) {\n\t\t\tif (!monitored.process.killed) {\n\t\t\t\tmonitored.process.kill(\"SIGKILL\");\n\t\t\t}\n\t\t}\n\t\tthis.processes.clear();\n\t\tthis.lastHeartbeat.clear();\n\t\tthis.removeAllListeners();\n\n\t\tif (this.parentShutdownHandler) {\n\t\t\tprocess.removeListener(\"SIGINT\", this.parentShutdownHandler);\n\t\t\tprocess.removeListener(\"SIGTERM\", this.parentShutdownHandler);\n\t\t}\n\t}\n\n\tprivate checkHeartbeats(): void {\n\t\tconst now = Date.now();\n\t\tfor (const [task_id] of this.processes) {\n\t\t\tconst last = this.lastHeartbeat.get(task_id);\n\t\t\tif (last === undefined) continue;\n\t\t\tif (now - last > HEARTBEAT_MISS_THRESHOLD_MS) {\n\t\t\t\tthis.handleStalled(task_id);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handleStalled(task_id: string): void {\n\t\tconst monitored = this.processes.get(task_id);\n\t\tif (!monitored) return;\n\n\t\tif (!monitored.process.killed) {\n\t\t\tmonitored.process.kill(\"SIGKILL\");\n\t\t}\n\n\t\tthis.emit(\"stalled\", { task_id, pid: monitored.pid });\n\t\t// Process exit handler will call untrack()\n\t}\n\n\tprivate handleTimeout(task_id: string): void {\n\t\tconst monitored = this.processes.get(task_id);\n\t\tif (!monitored) return;\n\n\t\tif (!monitored.process.killed) {\n\t\t\tmonitored.process.kill(\"SIGKILL\");\n\t\t}\n\n\t\tthis.emit(\"timeout\", { task_id, pid: monitored.pid });\n\t\tthis.timeouts.delete(task_id);\n\t\t// Process exit handler will call untrack()\n\t}\n\n\tprivate untrack(task_id: string): void {\n\t\tconst timeout = this.timeouts.get(task_id);\n\t\tif (timeout) {\n\t\t\tclearTimeout(timeout);\n\t\t\tthis.timeouts.delete(task_id);\n\t\t}\n\t\tthis.processes.delete(task_id);\n\t\tthis.lastHeartbeat.delete(task_id);\n\t}\n\n\tprivate setupParentExitHandlers(): void {\n\t\tconst shutdown = () => this.gracefulShutdown();\n\t\tthis.parentShutdownHandler = shutdown;\n\t\tprocess.setMaxListeners(Math.max(process.getMaxListeners(), 20));\n\t\tprocess.once(\"SIGINT\", shutdown);\n\t\tprocess.once(\"SIGTERM\", shutdown);\n\t}\n\n\tprivate gracefulShutdown(): void {\n\t\t// SIGTERM all children\n\t\tfor (const monitored of this.processes.values()) {\n\t\t\tif (!monitored.process.killed) {\n\t\t\t\tmonitored.process.kill(\"SIGTERM\");\n\t\t\t}\n\t\t}\n\n\t\t// SIGKILL after grace period\n\t\tsetTimeout(() => {\n\t\t\tfor (const monitored of this.processes.values()) {\n\t\t\t\tif (!monitored.process.killed) {\n\t\t\t\t\tmonitored.process.kill(\"SIGKILL\");\n\t\t\t\t}\n\t\t\t}\n\t\t}, PARENT_SHUTDOWN_GRACE_MS).unref();\n\t}\n\n\tprivate sweepOldAgents(): void {\n\t\tconst dispatchDir = getDispatchRoot(this.cwd);\n\t\tif (!existsSync(dispatchDir)) return;\n\n\t\tconst now = Date.now();\n\t\tconst cutoff = 24 * 60 * 60 * 1000; // 24 hours\n\n\t\tfor (const entry of readdirSync(dispatchDir)) {\n\t\t\tconst entryPath = join(dispatchDir, entry);\n\t\t\ttry {\n\t\t\t\tconst stats = statSync(entryPath);\n\t\t\t\tif (!stats.isDirectory()) continue;\n\n\t\t\t\tif (now - stats.mtimeMs > cutoff) {\n\t\t\t\t\tconst hasRunningPid = this.hasRunningPid(entryPath);\n\t\t\t\t\tif (!hasRunningPid) {\n\t\t\t\t\t\tthis.rmrf(entryPath);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Ignore errors for individual entries\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate hasRunningPid(dir: string): boolean {\n\t\tconst pidFile = join(dir, \"pid\");\n\t\tif (!existsSync(pidFile)) return false;\n\n\t\ttry {\n\t\t\tconst pid = Number.parseInt(readFileSync(pidFile, \"utf-8\"), 10);\n\t\t\tif (Number.isNaN(pid)) return false;\n\t\t\tprocess.kill(pid, 0); // Check if process exists\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprivate rmrf(dir: string): void {\n\t\tfor (const entry of readdirSync(dir)) {\n\t\t\tconst entryPath = join(dir, entry);\n\t\t\ttry {\n\t\t\t\tconst stats = statSync(entryPath);\n\t\t\t\tif (stats.isDirectory()) {\n\t\t\t\t\tthis.rmrf(entryPath);\n\t\t\t\t} else {\n\t\t\t\t\tunlinkSync(entryPath);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Ignore\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\trmdirSync(dir);\n\t\t} catch {\n\t\t\t// Ignore\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"lifeguard.js","sourceRoot":"","sources":["../../src/core/lifeguard.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ChildProcess } from \"node:child_process\";\nimport { EventEmitter } from \"node:events\";\nimport { existsSync, readdirSync, readFileSync, rmdirSync, statSync, unlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getDispatchRoot } from \"../config.js\";\n\nconst TIMEOUTS_MS: Record<string, number> = {\n\texplore: 5 * 60 * 1000,\n\tedit: 10 * 60 * 1000,\n\ttest: 10 * 60 * 1000,\n\treview: 8 * 60 * 1000,\n\tdoc: 5 * 60 * 1000,\n};\n\nconst HEARTBEAT_MISS_THRESHOLD_MS = 60000;\nconst HEARTBEAT_CHECK_INTERVAL_MS = 5000;\nconst PARENT_SHUTDOWN_GRACE_MS = 5000;\n\n/**\n * Concurrency tolerance. Each *additional* concurrently-monitored subagent adds\n * this fraction to the heartbeat-miss and hard-timeout budgets.\n *\n * When several subagents run at once (plus background MCP tools), they saturate\n * the CPU and starve the parent's event loop: its `setInterval` heartbeat check\n * fires late, and it cannot read a child's `{\"ping\":true}` line in time even\n * though the child is healthy and still working. Scaling the budgets by load\n * stops that contention from false-positive SIGKILLing healthy subagents — the\n * failure the demo hit when running many agents + MCP tools in the background.\n */\nconst LOAD_TOLERANCE_PER_PROCESS = 0.5;\n\n/**\n * Hard ceiling on the load tolerance multiplier, so a genuinely stuck subagent\n * is still eventually reaped no matter how busy the pool is.\n */\nconst MAX_LOAD_MULTIPLIER = 4;\n\nexport interface LifeguardProcess {\n\tpid: number;\n\ttask_id: string;\n\tagent_type: string;\n\tprocess: ChildProcess;\n}\n\n/**\n * Monitors running subagent processes for heartbeats, hard timeouts,\n * and parent-exit cleanup. Emits \"stalled\" and \"timeout\" events when\n * processes are terminated.\n */\nexport class SubagentLifeguard extends EventEmitter {\n\tprivate processes = new Map<string, LifeguardProcess>();\n\tprivate lastHeartbeat = new Map<string, number>();\n\tprivate timeouts = new Map<string, NodeJS.Timeout>();\n\t/** When each task started, used to compute the load-scaled hard timeout. */\n\tprivate startedAt = new Map<string, number>();\n\t/** Per-agent base hard timeout (before load scaling), captured at monitor(). */\n\tprivate baseTimeoutMs = new Map<string, number>();\n\tprivate checkInterval: NodeJS.Timeout | null = null;\n\t/** Wall-clock time the heartbeat check last ran, to measure event-loop lag. */\n\tprivate lastCheckAt = Date.now();\n\tprivate disposed = false;\n\tprivate readonly cwd: string;\n\tprivate parentShutdownHandler?: () => void;\n\n\tconstructor(cwd: string) {\n\t\tsuper();\n\t\tthis.cwd = cwd;\n\t\tthis.setupParentExitHandlers();\n\t\tthis.sweepOldAgents();\n\t\tthis.lastCheckAt = Date.now();\n\t\tthis.checkInterval = setInterval(() => this.checkHeartbeats(), HEARTBEAT_CHECK_INTERVAL_MS);\n\t}\n\n\t/**\n\t * Tolerance multiplier for the current load. 1 process → 1x; each extra\n\t * concurrent process adds LOAD_TOLERANCE_PER_PROCESS, capped at MAX_LOAD_MULTIPLIER.\n\t */\n\tprivate loadMultiplier(): number {\n\t\tconst concurrent = this.processes.size;\n\t\tconst mult = 1 + Math.max(0, concurrent - 1) * LOAD_TOLERANCE_PER_PROCESS;\n\t\treturn Math.min(mult, MAX_LOAD_MULTIPLIER);\n\t}\n\n\t/**\n\t * Begin monitoring a child process. The process must emit a\n\t * `{\"ping\":true}` JSON line on stdout every 30 seconds.\n\t */\n\tmonitor(task_id: string, agent_type: string, proc: ChildProcess): void {\n\t\tif (this.disposed) return;\n\n\t\tconst pid = proc.pid ?? 0;\n\t\tthis.processes.set(task_id, { pid, task_id, agent_type, process: proc });\n\t\tthis.lastHeartbeat.set(task_id, Date.now());\n\n\t\tconst timeoutMs = TIMEOUTS_MS[agent_type] ?? TIMEOUTS_MS.explore;\n\t\tthis.startedAt.set(task_id, Date.now());\n\t\tthis.baseTimeoutMs.set(task_id, timeoutMs);\n\t\t// Arm the hard timeout scaled by current load. When it fires, handleTimeout\n\t\t// re-checks load and re-arms (up to MAX_LOAD_MULTIPLIER) if the pool is still\n\t\t// busy, so a slow-but-progressing subagent isn't killed for CPU contention.\n\t\tconst timeout = setTimeout(\n\t\t\t() => {\n\t\t\t\tthis.handleTimeout(task_id);\n\t\t\t},\n\t\t\tMath.round(timeoutMs * this.loadMultiplier()),\n\t\t);\n\t\tthis.timeouts.set(task_id, timeout);\n\n\t\tproc.once(\"exit\", () => {\n\t\t\tthis.untrack(task_id);\n\t\t});\n\t}\n\n\t/** Record a heartbeat for a monitored task. */\n\trecordHeartbeat(task_id: string): void {\n\t\tif (this.processes.has(task_id)) {\n\t\t\tthis.lastHeartbeat.set(task_id, Date.now());\n\t\t}\n\t}\n\n\t/** Get the last recorded heartbeat timestamp, or null. */\n\tlastHeartbeatAt(task_id: string): number | null {\n\t\treturn this.lastHeartbeat.get(task_id) ?? null;\n\t}\n\n\t/** True if the task is currently being monitored. */\n\tisMonitoring(task_id: string): boolean {\n\t\treturn this.processes.has(task_id);\n\t}\n\n\t/** Kill all monitored processes and clean up. */\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\n\t\tif (this.checkInterval) {\n\t\t\tclearInterval(this.checkInterval);\n\t\t\tthis.checkInterval = null;\n\t\t}\n\n\t\tfor (const timeout of this.timeouts.values()) {\n\t\t\tclearTimeout(timeout);\n\t\t}\n\t\tthis.timeouts.clear();\n\n\t\tfor (const monitored of this.processes.values()) {\n\t\t\tif (!monitored.process.killed) {\n\t\t\t\tmonitored.process.kill(\"SIGKILL\");\n\t\t\t}\n\t\t}\n\t\tthis.processes.clear();\n\t\tthis.lastHeartbeat.clear();\n\t\tthis.startedAt.clear();\n\t\tthis.baseTimeoutMs.clear();\n\t\tthis.removeAllListeners();\n\n\t\tif (this.parentShutdownHandler) {\n\t\t\tprocess.removeListener(\"SIGINT\", this.parentShutdownHandler);\n\t\t\tprocess.removeListener(\"SIGTERM\", this.parentShutdownHandler);\n\t\t}\n\t}\n\n\tprivate checkHeartbeats(): void {\n\t\tconst now = Date.now();\n\t\t// Event-loop lag: how much later than scheduled this check actually ran.\n\t\t// Heavy CPU load (many concurrent subagents + background MCP tools) starves\n\t\t// the loop, delaying both this check *and* our reading of child heartbeats.\n\t\t// Forgive that gap so the parent's own starvation isn't charged against the\n\t\t// children as a missed heartbeat.\n\t\tconst loopLag = Math.max(0, now - this.lastCheckAt - HEARTBEAT_CHECK_INTERVAL_MS);\n\t\tthis.lastCheckAt = now;\n\n\t\tconst threshold = HEARTBEAT_MISS_THRESHOLD_MS * this.loadMultiplier() + loopLag;\n\t\tfor (const [task_id] of this.processes) {\n\t\t\tconst last = this.lastHeartbeat.get(task_id);\n\t\t\tif (last === undefined) continue;\n\t\t\tif (now - last > threshold) {\n\t\t\t\tthis.handleStalled(task_id);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handleStalled(task_id: string): void {\n\t\tconst monitored = this.processes.get(task_id);\n\t\tif (!monitored) return;\n\n\t\tif (!monitored.process.killed) {\n\t\t\tmonitored.process.kill(\"SIGKILL\");\n\t\t}\n\n\t\tthis.emit(\"stalled\", { task_id, pid: monitored.pid });\n\t\t// Process exit handler will call untrack()\n\t}\n\n\tprivate handleTimeout(task_id: string): void {\n\t\tconst monitored = this.processes.get(task_id);\n\t\tif (!monitored) return;\n\n\t\t// Under load the wall-clock timer can fire while the subagent is still doing\n\t\t// real work — its turns are just slow because the CPU is shared. Re-arm rather\n\t\t// than kill, up to a hard ceiling (base * MAX_LOAD_MULTIPLIER) so a genuinely\n\t\t// stuck agent still terminates.\n\t\tconst started = this.startedAt.get(task_id) ?? Date.now();\n\t\tconst base = this.baseTimeoutMs.get(task_id) ?? TIMEOUTS_MS[monitored.agent_type] ?? TIMEOUTS_MS.explore;\n\t\tconst elapsed = Date.now() - started;\n\t\tconst ceiling = base * MAX_LOAD_MULTIPLIER;\n\t\tif (this.loadMultiplier() > 1 && elapsed < ceiling) {\n\t\t\tconst remaining = ceiling - elapsed;\n\t\t\tconst next = Math.min(\n\t\t\t\tMath.round(base * this.loadMultiplier()),\n\t\t\t\tMath.max(HEARTBEAT_CHECK_INTERVAL_MS, remaining),\n\t\t\t);\n\t\t\tthis.timeouts.set(\n\t\t\t\ttask_id,\n\t\t\t\tsetTimeout(() => this.handleTimeout(task_id), next),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!monitored.process.killed) {\n\t\t\tmonitored.process.kill(\"SIGKILL\");\n\t\t}\n\n\t\tthis.emit(\"timeout\", { task_id, pid: monitored.pid });\n\t\tthis.timeouts.delete(task_id);\n\t\t// Process exit handler will call untrack()\n\t}\n\n\tprivate untrack(task_id: string): void {\n\t\tconst timeout = this.timeouts.get(task_id);\n\t\tif (timeout) {\n\t\t\tclearTimeout(timeout);\n\t\t\tthis.timeouts.delete(task_id);\n\t\t}\n\t\tthis.processes.delete(task_id);\n\t\tthis.lastHeartbeat.delete(task_id);\n\t\tthis.startedAt.delete(task_id);\n\t\tthis.baseTimeoutMs.delete(task_id);\n\t}\n\n\tprivate setupParentExitHandlers(): void {\n\t\tconst shutdown = () => this.gracefulShutdown();\n\t\tthis.parentShutdownHandler = shutdown;\n\t\tprocess.setMaxListeners(Math.max(process.getMaxListeners(), 20));\n\t\tprocess.once(\"SIGINT\", shutdown);\n\t\tprocess.once(\"SIGTERM\", shutdown);\n\t}\n\n\tprivate gracefulShutdown(): void {\n\t\t// SIGTERM all children\n\t\tfor (const monitored of this.processes.values()) {\n\t\t\tif (!monitored.process.killed) {\n\t\t\t\tmonitored.process.kill(\"SIGTERM\");\n\t\t\t}\n\t\t}\n\n\t\t// SIGKILL after grace period\n\t\tsetTimeout(() => {\n\t\t\tfor (const monitored of this.processes.values()) {\n\t\t\t\tif (!monitored.process.killed) {\n\t\t\t\t\tmonitored.process.kill(\"SIGKILL\");\n\t\t\t\t}\n\t\t\t}\n\t\t}, PARENT_SHUTDOWN_GRACE_MS).unref();\n\t}\n\n\tprivate sweepOldAgents(): void {\n\t\tconst dispatchDir = getDispatchRoot(this.cwd);\n\t\tif (!existsSync(dispatchDir)) return;\n\n\t\tconst now = Date.now();\n\t\tconst cutoff = 24 * 60 * 60 * 1000; // 24 hours\n\n\t\tfor (const entry of readdirSync(dispatchDir)) {\n\t\t\tconst entryPath = join(dispatchDir, entry);\n\t\t\ttry {\n\t\t\t\tconst stats = statSync(entryPath);\n\t\t\t\tif (!stats.isDirectory()) continue;\n\n\t\t\t\tif (now - stats.mtimeMs > cutoff) {\n\t\t\t\t\tconst hasRunningPid = this.hasRunningPid(entryPath);\n\t\t\t\t\tif (!hasRunningPid) {\n\t\t\t\t\t\tthis.rmrf(entryPath);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Ignore errors for individual entries\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate hasRunningPid(dir: string): boolean {\n\t\tconst pidFile = join(dir, \"pid\");\n\t\tif (!existsSync(pidFile)) return false;\n\n\t\ttry {\n\t\t\tconst pid = Number.parseInt(readFileSync(pidFile, \"utf-8\"), 10);\n\t\t\tif (Number.isNaN(pid)) return false;\n\t\t\tprocess.kill(pid, 0); // Check if process exists\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprivate rmrf(dir: string): void {\n\t\tfor (const entry of readdirSync(dir)) {\n\t\t\tconst entryPath = join(dir, entry);\n\t\t\ttry {\n\t\t\t\tconst stats = statSync(entryPath);\n\t\t\t\tif (stats.isDirectory()) {\n\t\t\t\t\tthis.rmrf(entryPath);\n\t\t\t\t} else {\n\t\t\t\t\tunlinkSync(entryPath);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Ignore\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\trmdirSync(dir);\n\t\t} catch {\n\t\t\t// Ignore\n\t\t}\n\t}\n}\n"]}