@kolisachint/hoocode-agent 0.4.113 → 0.4.114

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 (117) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/core/agent-session.d.ts.map +1 -1
  3. package/dist/core/agent-session.js +1 -1
  4. package/dist/core/agent-session.js.map +1 -1
  5. package/dist/core/extensions/plugins/authoring.d.ts +51 -0
  6. package/dist/core/extensions/plugins/authoring.d.ts.map +1 -0
  7. package/dist/core/extensions/plugins/authoring.js +91 -0
  8. package/dist/core/extensions/plugins/authoring.js.map +1 -0
  9. package/dist/core/extensions/plugins/default-marketplace/.agents-plugin/marketplace.json +12 -0
  10. package/dist/core/extensions/plugins/default-marketplace/plugins/hello-world/.agents-plugin/plugin.json +6 -0
  11. package/dist/core/extensions/plugins/default-marketplace/plugins/hello-world/commands/hello.md +5 -0
  12. package/dist/core/extensions/plugins/default-marketplace/plugins/hello-world/skills/greet/SKILL.md +7 -0
  13. package/dist/core/extensions/plugins/formats/agents.d.ts +7 -0
  14. package/dist/core/extensions/plugins/formats/agents.d.ts.map +1 -0
  15. package/dist/core/extensions/plugins/formats/agents.js +14 -0
  16. package/dist/core/extensions/plugins/formats/agents.js.map +1 -0
  17. package/dist/core/extensions/plugins/formats/claude.d.ts +10 -0
  18. package/dist/core/extensions/plugins/formats/claude.d.ts.map +1 -0
  19. package/dist/core/extensions/plugins/formats/claude.js +17 -0
  20. package/dist/core/extensions/plugins/formats/claude.js.map +1 -0
  21. package/dist/core/extensions/plugins/formats/copilot.d.ts +23 -0
  22. package/dist/core/extensions/plugins/formats/copilot.d.ts.map +1 -0
  23. package/dist/core/extensions/plugins/formats/copilot.js +130 -0
  24. package/dist/core/extensions/plugins/formats/copilot.js.map +1 -0
  25. package/dist/core/extensions/plugins/formats/index.d.ts +41 -0
  26. package/dist/core/extensions/plugins/formats/index.d.ts.map +1 -0
  27. package/dist/core/extensions/plugins/formats/index.js +76 -0
  28. package/dist/core/extensions/plugins/formats/index.js.map +1 -0
  29. package/dist/core/extensions/plugins/formats/jsonManifest.d.ts +21 -0
  30. package/dist/core/extensions/plugins/formats/jsonManifest.d.ts.map +1 -0
  31. package/dist/core/extensions/plugins/formats/jsonManifest.js +97 -0
  32. package/dist/core/extensions/plugins/formats/jsonManifest.js.map +1 -0
  33. package/dist/core/extensions/plugins/formats/shared.d.ts +44 -0
  34. package/dist/core/extensions/plugins/formats/shared.d.ts.map +1 -0
  35. package/dist/core/extensions/plugins/formats/shared.js +107 -0
  36. package/dist/core/extensions/plugins/formats/shared.js.map +1 -0
  37. package/dist/core/extensions/plugins/formats/types.d.ts +118 -0
  38. package/dist/core/extensions/plugins/formats/types.d.ts.map +1 -0
  39. package/dist/core/extensions/plugins/formats/types.js +19 -0
  40. package/dist/core/extensions/plugins/formats/types.js.map +1 -0
  41. package/dist/core/extensions/plugins/install.d.ts +73 -0
  42. package/dist/core/extensions/plugins/install.d.ts.map +1 -0
  43. package/dist/core/extensions/plugins/install.js +173 -0
  44. package/dist/core/extensions/plugins/install.js.map +1 -0
  45. package/dist/core/extensions/plugins/manifest.d.ts +25 -7
  46. package/dist/core/extensions/plugins/manifest.d.ts.map +1 -1
  47. package/dist/core/extensions/plugins/manifest.js +16 -91
  48. package/dist/core/extensions/plugins/manifest.js.map +1 -1
  49. package/dist/core/extensions/plugins/marketplace.d.ts +5 -4
  50. package/dist/core/extensions/plugins/marketplace.d.ts.map +1 -1
  51. package/dist/core/extensions/plugins/marketplace.js +8 -13
  52. package/dist/core/extensions/plugins/marketplace.js.map +1 -1
  53. package/dist/core/settings-defaults.d.ts +2 -0
  54. package/dist/core/settings-defaults.d.ts.map +1 -1
  55. package/dist/core/settings-defaults.js +2 -0
  56. package/dist/core/settings-defaults.js.map +1 -1
  57. package/dist/core/settings-manager.d.ts +4 -0
  58. package/dist/core/settings-manager.d.ts.map +1 -1
  59. package/dist/core/settings-manager.js +16 -0
  60. package/dist/core/settings-manager.js.map +1 -1
  61. package/dist/core/settings-types.d.ts +2 -0
  62. package/dist/core/settings-types.d.ts.map +1 -1
  63. package/dist/core/settings-types.js.map +1 -1
  64. package/dist/core/subagent-depth.d.ts +13 -0
  65. package/dist/core/subagent-depth.d.ts.map +1 -1
  66. package/dist/core/subagent-depth.js +15 -0
  67. package/dist/core/subagent-depth.js.map +1 -1
  68. package/dist/core/subagent-pool.d.ts.map +1 -1
  69. package/dist/core/subagent-pool.js +4 -1
  70. package/dist/core/subagent-pool.js.map +1 -1
  71. package/dist/core/tools/find.d.ts +8 -3
  72. package/dist/core/tools/find.d.ts.map +1 -1
  73. package/dist/core/tools/find.js +189 -122
  74. package/dist/core/tools/find.js.map +1 -1
  75. package/dist/core/tools/index.d.ts +0 -4
  76. package/dist/core/tools/index.d.ts.map +1 -1
  77. package/dist/core/tools/index.js +2 -5
  78. package/dist/core/tools/index.js.map +1 -1
  79. package/dist/core/tools/plugin-tool-names.d.ts +21 -0
  80. package/dist/core/tools/plugin-tool-names.d.ts.map +1 -0
  81. package/dist/core/tools/plugin-tool-names.js +31 -0
  82. package/dist/core/tools/plugin-tool-names.js.map +1 -0
  83. package/dist/core/tools/plugins.d.ts +49 -0
  84. package/dist/core/tools/plugins.d.ts.map +1 -0
  85. package/dist/core/tools/plugins.js +186 -0
  86. package/dist/core/tools/plugins.js.map +1 -0
  87. package/dist/core/tools/propose-plugin.d.ts +36 -0
  88. package/dist/core/tools/propose-plugin.d.ts.map +1 -0
  89. package/dist/core/tools/propose-plugin.js +219 -0
  90. package/dist/core/tools/propose-plugin.js.map +1 -0
  91. package/dist/core/warm-subagent-pool.d.ts.map +1 -1
  92. package/dist/core/warm-subagent-pool.js +4 -1
  93. package/dist/core/warm-subagent-pool.js.map +1 -1
  94. package/dist/extensions/core/marketplace.d.ts +7 -3
  95. package/dist/extensions/core/marketplace.d.ts.map +1 -1
  96. package/dist/extensions/core/marketplace.js +23 -67
  97. package/dist/extensions/core/marketplace.js.map +1 -1
  98. package/dist/extensions/core/mcp-deferred.d.ts +28 -0
  99. package/dist/extensions/core/mcp-deferred.d.ts.map +1 -0
  100. package/dist/extensions/core/mcp-deferred.js +53 -0
  101. package/dist/extensions/core/mcp-deferred.js.map +1 -0
  102. package/dist/extensions/core/mcp-loader.d.ts.map +1 -1
  103. package/dist/extensions/core/mcp-loader.js +132 -78
  104. package/dist/extensions/core/mcp-loader.js.map +1 -1
  105. package/dist/main.d.ts.map +1 -1
  106. package/dist/main.js +19 -1
  107. package/dist/main.js.map +1 -1
  108. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  109. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  110. package/examples/extensions/minimal-mode.ts +1 -1
  111. package/examples/extensions/sandbox/package.json +1 -1
  112. package/examples/extensions/with-deps/package.json +1 -1
  113. package/package.json +5 -5
  114. package/dist/core/tools/glob.d.ts +0 -42
  115. package/dist/core/tools/glob.d.ts.map +0 -1
  116. package/dist/core/tools/glob.js +0 -432
  117. package/dist/core/tools/glob.js.map +0 -1
@@ -20,8 +20,9 @@ import { Text } from "@kolisachint/hoocode-tui";
20
20
  import { Type } from "typebox";
21
21
  import { getHooCodeDir } from "../../config.js";
22
22
  import { getExtensionMcpServers } from "../../core/extension-mcp-servers.js";
23
- import { subagentSkipMcp } from "../../core/subagent-depth.js";
23
+ import { deferMcpSchemas, subagentSkipMcp } from "../../core/subagent-depth.js";
24
24
  import { taskStore } from "../../core/task-store.js";
25
+ import { formatDeferredCatalog, selectResolvable } from "./mcp-deferred.js";
25
26
  const HOOCODE_DIR = getHooCodeDir();
26
27
  const mcpConnections = new Map();
27
28
  /**
@@ -217,6 +218,82 @@ function loadStandardMcpFile(filePath) {
217
218
  return [];
218
219
  }
219
220
  }
221
+ /**
222
+ * Build the full {@link ToolDefinition} for one MCP tool — the complete JSON
223
+ * schema plus the connect/execute machinery. Shared by the eager path (register
224
+ * every tool up front) and the deferred path (materialize on resolve), so both
225
+ * produce identical, callable tools.
226
+ */
227
+ function buildMcpToolDefinition(serverConfig, tool) {
228
+ const toolName = `mcp_${serverConfig.name}_${tool.name}`;
229
+ const schema = buildMcpSchema(tool);
230
+ const capturedServer = serverConfig.name;
231
+ const capturedTool = tool.name;
232
+ // MCP tools default to background mode since they are external processes with potential high latency
233
+ const isBackground = serverConfig.background !== false;
234
+ return {
235
+ name: toolName,
236
+ label: `[MCP] ${serverConfig.name} › ${tool.name}`,
237
+ description: tool.description,
238
+ parameters: schema,
239
+ background: isBackground,
240
+ // Render a clean, prefixed title in chat — `MCP [server › tool] <args>` —
241
+ // parallel to the subagent `Task [type] <desc>` line. Without this the
242
+ // ToolExecutionComponent falls back to the raw `mcp_<server>_<tool>` name.
243
+ // The args summary reuses the same helper as the background start/finish
244
+ // messages so the chat title stays in sync with them.
245
+ renderCall(args, theme) {
246
+ const summary = summarizeArgs((args ?? {}));
247
+ const text = theme.fg("toolTitle", theme.bold("MCP ")) +
248
+ theme.fg("accent", `[${capturedServer} › ${capturedTool}]`) +
249
+ (summary ? theme.fg("dim", ` ${summary}`) : "");
250
+ return new Text(text, 0, 0);
251
+ },
252
+ async execute(_toolCallId, params, signal, _onUpdate) {
253
+ // Background MCP tools get a task store entry so they appear in the task pane.
254
+ // Foreground tools skip this (their result is awaited inline). The server
255
+ // name rides in subagentMode and becomes the row's `[server]` origin tag;
256
+ // the title carries just the tool.
257
+ const task = isBackground
258
+ ? taskStore.create(capturedTool, { source: "mcp", subagentMode: capturedServer })
259
+ : undefined;
260
+ if (task)
261
+ taskStore.update(task.id, { status: "in_progress" });
262
+ // Lazily (re)connect: a dropped connection (server exit, process churn
263
+ // between turns, a racing teardown) should transparently reconnect from
264
+ // the retained config rather than permanently fail with "not connected".
265
+ const activeConn = await getOrConnectMcp(capturedServer);
266
+ if (!activeConn) {
267
+ if (task)
268
+ taskStore.update(task.id, { status: "failed" });
269
+ return {
270
+ content: [
271
+ { type: "text", text: `MCP server "${capturedServer}" is not connected (reconnect attempt failed)` },
272
+ ],
273
+ details: undefined,
274
+ };
275
+ }
276
+ try {
277
+ const abortPromise = new Promise((_, reject) => {
278
+ signal.addEventListener("abort", () => reject(new Error("Aborted")));
279
+ });
280
+ const result = await Promise.race([
281
+ activeConn.rpc("tools/call", { name: capturedTool, arguments: params }),
282
+ abortPromise,
283
+ ]);
284
+ if (task)
285
+ taskStore.update(task.id, { status: "done" });
286
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], details: undefined };
287
+ }
288
+ catch (error) {
289
+ if (task)
290
+ taskStore.update(task.id, { status: "failed" });
291
+ throw error;
292
+ }
293
+ },
294
+ };
295
+ }
296
+ const RESOLVE_MCP_TOOLS_NAME = "ResolveMcpTools";
220
297
  export function setupMcpLoader(pi) {
221
298
  pi.on("session_start", async (_event, ctx) => {
222
299
  // A spawned subagent whose tool allowlist has no MCP tools is told by its
@@ -295,7 +372,17 @@ export function setupMcpLoader(pi) {
295
372
  allServerConfigs.push(serverConfig);
296
373
  }
297
374
  }
298
- // 3. Connect to all servers and register tools
375
+ // Deferral (spec §2): inject MCP tool names only and materialize each schema
376
+ // on demand via ResolveMcpTools. Opt-in and top-level only — a subagent that
377
+ // needs MCP has this env cleared, so it eager-registers its allowlisted tools
378
+ // at dispatch (the dispatch ↔ schema interaction) and they are immediately callable.
379
+ const defer = deferMcpSchemas();
380
+ const deferredCatalog = [];
381
+ // Retain each deferred tool's raw definition + config so ResolveMcpTools can
382
+ // build the full ToolDefinition on request.
383
+ const deferredByName = new Map();
384
+ const resolvedNames = new Set();
385
+ // 3. Connect to all servers and register (or defer) tools
299
386
  for (const serverConfig of allServerConfigs) {
300
387
  // Retain the config so a tool call can lazily reconnect a dropped server.
301
388
  mcpServerConfigs.set(serverConfig.name, serverConfig);
@@ -303,89 +390,56 @@ export function setupMcpLoader(pi) {
303
390
  const { tools } = await connectMcpServer(serverConfig);
304
391
  for (const tool of tools) {
305
392
  const toolName = `mcp_${serverConfig.name}_${tool.name}`;
306
- const schema = buildMcpSchema(tool);
307
- const capturedServer = serverConfig.name;
308
- const capturedTool = tool.name;
309
- // MCP tools default to background mode since they are external processes with potential high latency
310
- const isBackground = serverConfig.background !== false;
311
- pi.registerTool({
312
- name: toolName,
313
- label: `[MCP] ${serverConfig.name} › ${tool.name}`,
314
- description: tool.description,
315
- parameters: schema,
316
- background: isBackground,
317
- // Render a clean, prefixed title in chat — `MCP [server › tool] <args>` —
318
- // parallel to the subagent `Task [type] <desc>` line. Without this the
319
- // ToolExecutionComponent falls back to the raw `mcp_<server>_<tool>` name.
320
- // The args summary reuses the same helper as the background start/finish
321
- // messages so the chat title stays in sync with them.
322
- renderCall(args, theme) {
323
- const summary = summarizeArgs((args ?? {}));
324
- const text = theme.fg("toolTitle", theme.bold("MCP ")) +
325
- theme.fg("accent", `[${capturedServer} › ${capturedTool}]`) +
326
- (summary ? theme.fg("dim", ` ${summary}`) : "");
327
- return new Text(text, 0, 0);
328
- },
329
- async execute(_toolCallId, params, signal, _onUpdate) {
330
- // Background MCP tools get a task store entry so they appear in the task pane.
331
- // Foreground tools skip this (their result is awaited inline). The server
332
- // name rides in subagentMode and becomes the row's `[server]` origin tag;
333
- // the title carries just the tool.
334
- const task = isBackground
335
- ? taskStore.create(capturedTool, { source: "mcp", subagentMode: capturedServer })
336
- : undefined;
337
- if (task)
338
- taskStore.update(task.id, { status: "in_progress" });
339
- // Lazily (re)connect: a dropped connection (server exit, process churn
340
- // between turns, a racing teardown) should transparently reconnect from
341
- // the retained config rather than permanently fail with "not connected".
342
- const activeConn = await getOrConnectMcp(capturedServer);
343
- if (!activeConn) {
344
- if (task)
345
- taskStore.update(task.id, { status: "failed" });
346
- return {
347
- content: [
348
- {
349
- type: "text",
350
- text: `MCP server "${capturedServer}" is not connected (reconnect attempt failed)`,
351
- },
352
- ],
353
- details: undefined,
354
- };
355
- }
356
- try {
357
- const abortPromise = new Promise((_, reject) => {
358
- signal.addEventListener("abort", () => reject(new Error("Aborted")));
359
- });
360
- const result = await Promise.race([
361
- activeConn.rpc("tools/call", {
362
- name: capturedTool,
363
- arguments: params,
364
- }),
365
- abortPromise,
366
- ]);
367
- if (task)
368
- taskStore.update(task.id, { status: "done" });
369
- return {
370
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
371
- details: undefined,
372
- };
373
- }
374
- catch (error) {
375
- if (task)
376
- taskStore.update(task.id, { status: "failed" });
377
- throw error;
378
- }
379
- },
380
- });
393
+ if (defer) {
394
+ deferredCatalog.push({ toolName, server: serverConfig.name, description: tool.description });
395
+ deferredByName.set(toolName, { serverConfig, tool });
396
+ }
397
+ else {
398
+ pi.registerTool(buildMcpToolDefinition(serverConfig, tool));
399
+ }
381
400
  }
382
401
  const bgMode = serverConfig.background !== false ? "background" : "foreground";
383
- ctx.ui.notify(`MCP: connected "${serverConfig.name}" (${tools.length} tool${tools.length === 1 ? "" : "s"}, ${bgMode})`, "info");
402
+ const suffix = defer ? ", schemas deferred" : "";
403
+ ctx.ui.notify(`MCP: connected "${serverConfig.name}" (${tools.length} tool${tools.length === 1 ? "" : "s"}, ${bgMode}${suffix})`, "info");
384
404
  }
385
405
  catch (err) {
386
406
  ctx.ui.notify(`MCP: failed to connect "${serverConfig.name}": ${String(err)}`, "error");
387
407
  }
388
408
  }
409
+ // 4. In deferred mode, register the single resolver that materializes schemas on demand.
410
+ if (defer && deferredCatalog.length > 0) {
411
+ const resolveParams = Type.Object({
412
+ names: Type.Array(Type.String(), {
413
+ description: "MCP tool names to make callable (e.g. 'mcp_github_create_pr' or 'create_pr').",
414
+ }),
415
+ }, { additionalProperties: false });
416
+ pi.registerTool({
417
+ name: RESOLVE_MCP_TOOLS_NAME,
418
+ label: RESOLVE_MCP_TOOLS_NAME,
419
+ description: "MCP tools are connected but their schemas are loaded on demand to keep context small. Call this with the tool name(s) you need to make them callable, then call the tool(s). Available MCP tools:\n" +
420
+ formatDeferredCatalog(deferredCatalog),
421
+ promptSnippet: "Resolve deferred MCP tool schemas by name before calling them.",
422
+ parameters: resolveParams,
423
+ async execute(_toolCallId, params) {
424
+ const matched = selectResolvable(deferredCatalog, params.names ?? []);
425
+ const newlyResolved = [];
426
+ for (const entry of matched) {
427
+ if (resolvedNames.has(entry.toolName))
428
+ continue;
429
+ const raw = deferredByName.get(entry.toolName);
430
+ if (!raw)
431
+ continue;
432
+ pi.registerTool(buildMcpToolDefinition(raw.serverConfig, raw.tool));
433
+ resolvedNames.add(entry.toolName);
434
+ newlyResolved.push(entry.toolName);
435
+ }
436
+ const text = matched.length
437
+ ? `Resolved ${newlyResolved.length} MCP tool(s): ${matched.map((m) => m.toolName).join(", ")}. They are now callable.`
438
+ : `No MCP tools matched: ${(params.names ?? []).join(", ") || "(none)"}. See ResolveMcpTools for the catalog.`;
439
+ return { content: [{ type: "text", text }], details: undefined };
440
+ },
441
+ });
442
+ }
389
443
  });
390
444
  }
391
445
  //# sourceMappingURL=mcp-loader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-loader.js","sourceRoot":"","sources":["../../../src/extensions/core/mcp-loader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MCP server loader — discovers server configs (standard mcp.json locations,\n * hoocode's per-server JSON files, plugin registrations), connects via JSON-RPC\n * 2.0 over stdio, and registers each server tool as `mcp_<server>_<tool>`.\n *\n * Config sources (first-wins by server name):\n * 1. ~/.agents/mcp.json (user), ./.agents/mcp.json (project),\n * ~/.config/claude/mcp.json (Claude Desktop)\n * 2. ~/.hoocode/mcp-servers/*.json and ./.hoocode/mcp-servers/*.json\n * 3. MCP servers registered by plugins/extensions during load\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport type { AgentToolResult, AgentToolUpdateCallback } from \"@kolisachint/hoocode-agent-core\";\nimport { summarizeArgs } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { getHooCodeDir } from \"../../config.js\";\nimport { getExtensionMcpServers } from \"../../core/extension-mcp-servers.js\";\nimport type { ExtensionAPI, ExtensionContext, SessionStartEvent } from \"../../core/extensions/types.js\";\nimport { subagentSkipMcp } from \"../../core/subagent-depth.js\";\nimport { taskStore } from \"../../core/task-store.js\";\n\nconst HOOCODE_DIR = getHooCodeDir();\n\ninterface McpToolDef {\n\tname: string;\n\tdescription: string;\n\tinputSchema?: {\n\t\ttype?: string;\n\t\tproperties?: Record<string, { type?: string; description?: string }>;\n\t\trequired?: string[];\n\t};\n}\n\nexport interface McpServerConfig {\n\t/** Unique server identifier used as prefix for registered tool names */\n\tname: string;\n\t/** Executable to spawn */\n\tcommand: string;\n\t/** Optional arguments passed to the command */\n\targs?: string[];\n\t/** Optional extra environment variables for the server process */\n\tenv?: Record<string, string>;\n\t/** Run MCP tools in background by default (default: true for MCP servers) */\n\tbackground?: boolean;\n}\n\n/** Standard MCP config format used by Claude Desktop and other tools */\ninterface StandardMcpServerConfig {\n\tcommand: string;\n\targs?: string[];\n\tenv?: Record<string, string>;\n\tbackground?: boolean;\n}\n\ninterface StandardMcpConfig {\n\tmcpServers?: Record<string, StandardMcpServerConfig>;\n}\n\ninterface McpConnection {\n\trpc(method: string, params?: unknown, timeoutMs?: number): Promise<unknown>;\n\t/** Send a JSON-RPC notification (no id, no response expected). */\n\tnotify(method: string, params?: unknown): void;\n\tterminate(): void;\n}\n\nconst mcpConnections = new Map<string, McpConnection>();\n/**\n * Server configs retained by name so a tool call can transparently reconnect a\n * dropped server (process churn between turns, server exit, a racing teardown)\n * instead of permanently failing with \"not connected\".\n */\nconst mcpServerConfigs = new Map<string, McpServerConfig>();\n\n/** Timeout for the connection handshake (initialize / tools/list). Tool calls\n * themselves are left untimed since MCP tools can be long-running. */\nconst MCP_HANDSHAKE_TIMEOUT_MS = 15000;\n\nfunction spawnMcpServer(config: McpServerConfig): McpConnection {\n\tconst proc: ChildProcess = spawn(config.command, config.args ?? [], {\n\t\tenv: { ...process.env, ...(config.env ?? {}) },\n\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t});\n\n\tlet nextId = 1;\n\tconst pending = new Map<number, { resolve: (r: unknown) => void; reject: (e: Error) => void }>();\n\n\tconst rl = createInterface({ input: proc.stdout! });\n\trl.on(\"line\", (line) => {\n\t\tif (!line.trim()) return;\n\t\ttry {\n\t\t\tconst msg = JSON.parse(line) as {\n\t\t\t\tid?: number;\n\t\t\t\tresult?: unknown;\n\t\t\t\terror?: { message: string };\n\t\t\t};\n\t\t\tif (msg.id === undefined) return;\n\t\t\tconst cb = pending.get(msg.id);\n\t\t\tif (!cb) return;\n\t\t\tpending.delete(msg.id);\n\t\t\tif (msg.error) cb.reject(new Error(msg.error.message));\n\t\t\telse cb.resolve(msg.result);\n\t\t} catch {\n\t\t\t// ignore non-JSON server startup output\n\t\t}\n\t});\n\n\tproc.on(\"exit\", () => {\n\t\tfor (const cb of pending.values()) cb.reject(new Error(`MCP server \"${config.name}\" exited unexpectedly`));\n\t\tpending.clear();\n\t\tmcpConnections.delete(config.name);\n\t});\n\n\tfunction rpc(method: string, params?: unknown, timeoutMs?: number): Promise<unknown> {\n\t\tconst id = nextId++;\n\t\treturn new Promise<unknown>((resolve, reject) => {\n\t\t\tlet timer: NodeJS.Timeout | undefined;\n\t\t\tif (timeoutMs && timeoutMs > 0) {\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\tif (pending.delete(id)) {\n\t\t\t\t\t\treject(new Error(`MCP server \"${config.name}\" timed out after ${timeoutMs}ms on ${method}`));\n\t\t\t\t\t}\n\t\t\t\t}, timeoutMs);\n\t\t\t\ttimer.unref?.();\n\t\t\t}\n\t\t\tpending.set(id, {\n\t\t\t\tresolve: (r) => {\n\t\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\t\tresolve(r);\n\t\t\t\t},\n\t\t\t\treject: (e) => {\n\t\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\t\treject(e);\n\t\t\t\t},\n\t\t\t});\n\t\t\tproc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", id, method, params })}\\n`);\n\t\t});\n\t}\n\n\tfunction notify(method: string, params?: unknown): void {\n\t\tproc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", method, params })}\\n`);\n\t}\n\n\treturn {\n\t\trpc,\n\t\tnotify,\n\t\tterminate: () => {\n\t\t\trl.close();\n\t\t\tproc.kill();\n\t\t},\n\t};\n}\n\nasync function connectMcpServer(config: McpServerConfig): Promise<{ conn: McpConnection; tools: McpToolDef[] }> {\n\tmcpConnections.get(config.name)?.terminate();\n\n\tconst conn = spawnMcpServer(config);\n\tmcpConnections.set(config.name, conn);\n\n\tawait conn.rpc(\n\t\t\"initialize\",\n\t\t{\n\t\t\tprotocolVersion: \"2024-11-05\",\n\t\t\tcapabilities: { tools: {} },\n\t\t\tclientInfo: { name: \"hoocode\", version: \"1.0.0\" },\n\t\t},\n\t\tMCP_HANDSHAKE_TIMEOUT_MS,\n\t);\n\n\t// Per the MCP spec the client must acknowledge a successful initialize with the\n\t// initialized notification before issuing further requests; strict servers gate\n\t// tools/call on it.\n\tconn.notify(\"notifications/initialized\");\n\n\tconst toolsResult = (await conn.rpc(\"tools/list\", {}, MCP_HANDSHAKE_TIMEOUT_MS)) as {\n\t\ttools?: McpToolDef[];\n\t};\n\treturn { conn, tools: toolsResult.tools ?? [] };\n}\n\n/**\n * Return a live connection for a server, lazily reconnecting from the retained\n * config when the previous connection was torn down. Returns undefined only when\n * no config is known or a fresh connect attempt fails.\n */\nasync function getOrConnectMcp(name: string): Promise<McpConnection | undefined> {\n\tconst existing = mcpConnections.get(name);\n\tif (existing) return existing;\n\tconst config = mcpServerConfigs.get(name);\n\tif (!config) return undefined;\n\ttry {\n\t\tconst { conn } = await connectMcpServer(config);\n\t\treturn conn;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nlet mcpExitCleanupInstalled = false;\n/** Kill spawned MCP servers when the host process exits so they don't linger as\n * orphans (their stdin merely goes idle, which doesn't terminate them). */\nfunction installMcpExitCleanup(): void {\n\tif (mcpExitCleanupInstalled) return;\n\tmcpExitCleanupInstalled = true;\n\tprocess.once(\"exit\", () => {\n\t\tfor (const conn of mcpConnections.values()) {\n\t\t\ttry {\n\t\t\t\tconn.terminate();\n\t\t\t} catch {\n\t\t\t\t// best-effort cleanup\n\t\t\t}\n\t\t}\n\t\tmcpConnections.clear();\n\t});\n}\n\nfunction buildMcpSchema(tool: McpToolDef): ReturnType<typeof Type.Object> {\n\tconst props = tool.inputSchema?.properties ?? {};\n\tconst required = new Set(tool.inputSchema?.required ?? []);\n\tconst shape: Record<string, ReturnType<typeof Type.String>> = {};\n\n\tfor (const [key, prop] of Object.entries(props)) {\n\t\tlet field: ReturnType<typeof Type.String>;\n\t\tswitch (prop.type) {\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\t\tfield = Type.Number({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tcase \"boolean\":\n\t\t\t\tfield = Type.Boolean({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfield = Type.String({ description: prop.description });\n\t\t}\n\t\tshape[key] = required.has(key) ? field : (Type.Optional(field) as unknown as ReturnType<typeof Type.String>);\n\t}\n\n\treturn Type.Object(shape);\n}\n\n/**\n * Parse standard MCP config format (used by Claude Desktop, VS Code, etc.)\n * into hoocode's McpServerConfig format.\n */\nfunction parseStandardMcpConfig(config: StandardMcpConfig, _source: string): McpServerConfig[] {\n\tif (!config.mcpServers) return [];\n\n\tconst servers: McpServerConfig[] = [];\n\tfor (const [name, serverConfig] of Object.entries(config.mcpServers)) {\n\t\tservers.push({\n\t\t\tname,\n\t\t\tcommand: serverConfig.command,\n\t\t\targs: serverConfig.args,\n\t\t\tenv: serverConfig.env,\n\t\t\tbackground: serverConfig.background,\n\t\t});\n\t}\n\treturn servers;\n}\n\n/**\n * Load MCP servers from a standard mcp.json file.\n * Returns an array of McpServerConfig, or empty array if file doesn't exist or is invalid.\n */\nfunction loadStandardMcpFile(filePath: string): McpServerConfig[] {\n\tif (!existsSync(filePath)) return [];\n\n\ttry {\n\t\tconst content = readFileSync(filePath, \"utf8\");\n\t\tconst config = JSON.parse(content) as StandardMcpConfig;\n\t\treturn parseStandardMcpConfig(config, filePath);\n\t} catch {\n\t\treturn [];\n\t}\n}\n\nexport function setupMcpLoader(pi: ExtensionAPI): void {\n\tpi.on(\"session_start\", async (_event: SessionStartEvent, ctx: ExtensionContext) => {\n\t\t// A spawned subagent whose tool allowlist has no MCP tools is told by its\n\t\t// parent to skip server connection entirely (see SUBAGENT_SKIP_MCP_ENV).\n\t\t// Each connect is a ~15s-timeout handshake; doing it for a subagent that can\n\t\t// never call the tools is pure startup latency.\n\t\tif (subagentSkipMcp()) return;\n\n\t\tinstallMcpExitCleanup();\n\t\tconst allServerConfigs: McpServerConfig[] = [];\n\t\tconst seenNames = new Set<string>();\n\n\t\t// 1. Load from standard mcp.json locations\n\t\t// User-level: ~/.agents/mcp.json\n\t\tconst userAgentsConfig = loadStandardMcpFile(join(homedir(), \".agents\", \"mcp.json\"));\n\t\tfor (const config of userAgentsConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// Project-level: ./.agents/mcp.json\n\t\tconst projectAgentsConfig = loadStandardMcpFile(join(ctx.cwd, \".agents\", \"mcp.json\"));\n\t\tfor (const config of projectAgentsConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// Claude Desktop: ~/.config/claude/mcp.json\n\t\tconst claudeDesktopConfig = loadStandardMcpFile(join(homedir(), \".config\", \"claude\", \"mcp.json\"));\n\t\tfor (const config of claudeDesktopConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// 2. Load from hoocode's per-server format (existing behavior)\n\t\tconst searchDirs = [join(HOOCODE_DIR, \"mcp-servers\"), join(ctx.cwd, \".hoocode\", \"mcp-servers\")];\n\n\t\tfor (const dir of searchDirs) {\n\t\t\tif (!existsSync(dir)) continue;\n\n\t\t\tlet files: string[];\n\t\t\ttry {\n\t\t\t\tfiles = (await readdir(dir)).filter((f) => f.endsWith(\".json\"));\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (const file of files) {\n\t\t\t\tconst cfgPath = join(dir, file);\n\t\t\t\tlet serverConfig: McpServerConfig;\n\n\t\t\t\ttry {\n\t\t\t\t\tserverConfig = JSON.parse(readFileSync(cfgPath, \"utf8\")) as McpServerConfig;\n\t\t\t\t\tif (!serverConfig.name || !serverConfig.command) {\n\t\t\t\t\t\tctx.ui.notify(`MCP: config \"${file}\" is missing required \"name\" or \"command\"`, \"warning\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to parse \"${file}\": ${String(err)}`, \"error\");\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Skip if already loaded from standard config\n\t\t\t\tif (seenNames.has(serverConfig.name)) continue;\n\t\t\t\tseenNames.add(serverConfig.name);\n\t\t\t\tallServerConfigs.push(serverConfig);\n\t\t\t}\n\t\t}\n\n\t\t// 2b. Load from plugins/extensions that registered MCP servers during load.\n\t\tfor (const entry of getExtensionMcpServers()) {\n\t\t\tfor (const serverConfig of parseStandardMcpConfig(\n\t\t\t\t{ mcpServers: entry.mcpServers },\n\t\t\t\t`plugin:${entry.source}`,\n\t\t\t)) {\n\t\t\t\tif (seenNames.has(serverConfig.name)) continue;\n\t\t\t\tseenNames.add(serverConfig.name);\n\t\t\t\tallServerConfigs.push(serverConfig);\n\t\t\t}\n\t\t}\n\n\t\t// 3. Connect to all servers and register tools\n\t\tfor (const serverConfig of allServerConfigs) {\n\t\t\t// Retain the config so a tool call can lazily reconnect a dropped server.\n\t\t\tmcpServerConfigs.set(serverConfig.name, serverConfig);\n\t\t\ttry {\n\t\t\t\tconst { tools } = await connectMcpServer(serverConfig);\n\n\t\t\t\tfor (const tool of tools) {\n\t\t\t\t\tconst toolName = `mcp_${serverConfig.name}_${tool.name}`;\n\t\t\t\t\tconst schema = buildMcpSchema(tool);\n\t\t\t\t\tconst capturedServer = serverConfig.name;\n\t\t\t\t\tconst capturedTool = tool.name;\n\t\t\t\t\t// MCP tools default to background mode since they are external processes with potential high latency\n\t\t\t\t\tconst isBackground = serverConfig.background !== false;\n\n\t\t\t\t\tpi.registerTool({\n\t\t\t\t\t\tname: toolName,\n\t\t\t\t\t\tlabel: `[MCP] ${serverConfig.name} › ${tool.name}`,\n\t\t\t\t\t\tdescription: tool.description,\n\t\t\t\t\t\tparameters: schema,\n\t\t\t\t\t\tbackground: isBackground,\n\t\t\t\t\t\t// Render a clean, prefixed title in chat — `MCP [server › tool] <args>` —\n\t\t\t\t\t\t// parallel to the subagent `Task [type] <desc>` line. Without this the\n\t\t\t\t\t\t// ToolExecutionComponent falls back to the raw `mcp_<server>_<tool>` name.\n\t\t\t\t\t\t// The args summary reuses the same helper as the background start/finish\n\t\t\t\t\t\t// messages so the chat title stays in sync with them.\n\t\t\t\t\t\trenderCall(args, theme) {\n\t\t\t\t\t\t\tconst summary = summarizeArgs((args ?? {}) as Record<string, unknown>);\n\t\t\t\t\t\t\tconst text =\n\t\t\t\t\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"MCP \")) +\n\t\t\t\t\t\t\t\ttheme.fg(\"accent\", `[${capturedServer} › ${capturedTool}]`) +\n\t\t\t\t\t\t\t\t(summary ? theme.fg(\"dim\", ` ${summary}`) : \"\");\n\t\t\t\t\t\t\treturn new Text(text, 0, 0);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tasync execute(\n\t\t\t\t\t\t\t_toolCallId: string,\n\t\t\t\t\t\t\tparams: Static<typeof schema>,\n\t\t\t\t\t\t\tsignal: AbortSignal,\n\t\t\t\t\t\t\t_onUpdate: AgentToolUpdateCallback,\n\t\t\t\t\t\t): Promise<AgentToolResult<undefined>> {\n\t\t\t\t\t\t\t// Background MCP tools get a task store entry so they appear in the task pane.\n\t\t\t\t\t\t\t// Foreground tools skip this (their result is awaited inline). The server\n\t\t\t\t\t\t\t// name rides in subagentMode and becomes the row's `[server]` origin tag;\n\t\t\t\t\t\t\t// the title carries just the tool.\n\t\t\t\t\t\t\tconst task = isBackground\n\t\t\t\t\t\t\t\t? taskStore.create(capturedTool, { source: \"mcp\", subagentMode: capturedServer })\n\t\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\t\t\tif (task) taskStore.update(task.id, { status: \"in_progress\" });\n\n\t\t\t\t\t\t\t// Lazily (re)connect: a dropped connection (server exit, process churn\n\t\t\t\t\t\t\t// between turns, a racing teardown) should transparently reconnect from\n\t\t\t\t\t\t\t// the retained config rather than permanently fail with \"not connected\".\n\t\t\t\t\t\t\tconst activeConn = await getOrConnectMcp(capturedServer);\n\t\t\t\t\t\t\tif (!activeConn) {\n\t\t\t\t\t\t\t\tif (task) taskStore.update(task.id, { status: \"failed\" });\n\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\t\t\ttext: `MCP server \"${capturedServer}\" is not connected (reconnect attempt failed)`,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst abortPromise = new Promise<never>((_, reject) => {\n\t\t\t\t\t\t\t\t\tsignal.addEventListener(\"abort\", () => reject(new Error(\"Aborted\")));\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\tconst result = await Promise.race([\n\t\t\t\t\t\t\t\t\tactiveConn.rpc(\"tools/call\", {\n\t\t\t\t\t\t\t\t\t\tname: capturedTool,\n\t\t\t\t\t\t\t\t\t\targuments: params,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\tabortPromise,\n\t\t\t\t\t\t\t\t]);\n\n\t\t\t\t\t\t\t\tif (task) taskStore.update(task.id, { status: \"done\" });\n\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n\t\t\t\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tif (task) taskStore.update(task.id, { status: \"failed\" });\n\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tconst bgMode = serverConfig.background !== false ? \"background\" : \"foreground\";\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`MCP: connected \"${serverConfig.name}\" (${tools.length} tool${tools.length === 1 ? \"\" : \"s\"}, ${bgMode})`,\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t} catch (err) {\n\t\t\t\tctx.ui.notify(`MCP: failed to connect \"${serverConfig.name}\": ${String(err)}`, \"error\");\n\t\t\t}\n\t\t}\n\t});\n}\n"]}
1
+ {"version":3,"file":"mcp-loader.js","sourceRoot":"","sources":["../../../src/extensions/core/mcp-loader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MCP server loader — discovers server configs (standard mcp.json locations,\n * hoocode's per-server JSON files, plugin registrations), connects via JSON-RPC\n * 2.0 over stdio, and registers each server tool as `mcp_<server>_<tool>`.\n *\n * Config sources (first-wins by server name):\n * 1. ~/.agents/mcp.json (user), ./.agents/mcp.json (project),\n * ~/.config/claude/mcp.json (Claude Desktop)\n * 2. ~/.hoocode/mcp-servers/*.json and ./.hoocode/mcp-servers/*.json\n * 3. MCP servers registered by plugins/extensions during load\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport type { AgentToolResult, AgentToolUpdateCallback } from \"@kolisachint/hoocode-agent-core\";\nimport { summarizeArgs } from \"@kolisachint/hoocode-agent-core\";\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { getHooCodeDir } from \"../../config.js\";\nimport { getExtensionMcpServers } from \"../../core/extension-mcp-servers.js\";\nimport type { ExtensionAPI, ExtensionContext, SessionStartEvent, ToolDefinition } from \"../../core/extensions/types.js\";\nimport { deferMcpSchemas, subagentSkipMcp } from \"../../core/subagent-depth.js\";\nimport { taskStore } from \"../../core/task-store.js\";\nimport { type DeferredMcpToolEntry, formatDeferredCatalog, selectResolvable } from \"./mcp-deferred.js\";\n\nconst HOOCODE_DIR = getHooCodeDir();\n\ninterface McpToolDef {\n\tname: string;\n\tdescription: string;\n\tinputSchema?: {\n\t\ttype?: string;\n\t\tproperties?: Record<string, { type?: string; description?: string }>;\n\t\trequired?: string[];\n\t};\n}\n\nexport interface McpServerConfig {\n\t/** Unique server identifier used as prefix for registered tool names */\n\tname: string;\n\t/** Executable to spawn */\n\tcommand: string;\n\t/** Optional arguments passed to the command */\n\targs?: string[];\n\t/** Optional extra environment variables for the server process */\n\tenv?: Record<string, string>;\n\t/** Run MCP tools in background by default (default: true for MCP servers) */\n\tbackground?: boolean;\n}\n\n/** Standard MCP config format used by Claude Desktop and other tools */\ninterface StandardMcpServerConfig {\n\tcommand: string;\n\targs?: string[];\n\tenv?: Record<string, string>;\n\tbackground?: boolean;\n}\n\ninterface StandardMcpConfig {\n\tmcpServers?: Record<string, StandardMcpServerConfig>;\n}\n\ninterface McpConnection {\n\trpc(method: string, params?: unknown, timeoutMs?: number): Promise<unknown>;\n\t/** Send a JSON-RPC notification (no id, no response expected). */\n\tnotify(method: string, params?: unknown): void;\n\tterminate(): void;\n}\n\nconst mcpConnections = new Map<string, McpConnection>();\n/**\n * Server configs retained by name so a tool call can transparently reconnect a\n * dropped server (process churn between turns, server exit, a racing teardown)\n * instead of permanently failing with \"not connected\".\n */\nconst mcpServerConfigs = new Map<string, McpServerConfig>();\n\n/** Timeout for the connection handshake (initialize / tools/list). Tool calls\n * themselves are left untimed since MCP tools can be long-running. */\nconst MCP_HANDSHAKE_TIMEOUT_MS = 15000;\n\nfunction spawnMcpServer(config: McpServerConfig): McpConnection {\n\tconst proc: ChildProcess = spawn(config.command, config.args ?? [], {\n\t\tenv: { ...process.env, ...(config.env ?? {}) },\n\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t});\n\n\tlet nextId = 1;\n\tconst pending = new Map<number, { resolve: (r: unknown) => void; reject: (e: Error) => void }>();\n\n\tconst rl = createInterface({ input: proc.stdout! });\n\trl.on(\"line\", (line) => {\n\t\tif (!line.trim()) return;\n\t\ttry {\n\t\t\tconst msg = JSON.parse(line) as {\n\t\t\t\tid?: number;\n\t\t\t\tresult?: unknown;\n\t\t\t\terror?: { message: string };\n\t\t\t};\n\t\t\tif (msg.id === undefined) return;\n\t\t\tconst cb = pending.get(msg.id);\n\t\t\tif (!cb) return;\n\t\t\tpending.delete(msg.id);\n\t\t\tif (msg.error) cb.reject(new Error(msg.error.message));\n\t\t\telse cb.resolve(msg.result);\n\t\t} catch {\n\t\t\t// ignore non-JSON server startup output\n\t\t}\n\t});\n\n\tproc.on(\"exit\", () => {\n\t\tfor (const cb of pending.values()) cb.reject(new Error(`MCP server \"${config.name}\" exited unexpectedly`));\n\t\tpending.clear();\n\t\tmcpConnections.delete(config.name);\n\t});\n\n\tfunction rpc(method: string, params?: unknown, timeoutMs?: number): Promise<unknown> {\n\t\tconst id = nextId++;\n\t\treturn new Promise<unknown>((resolve, reject) => {\n\t\t\tlet timer: NodeJS.Timeout | undefined;\n\t\t\tif (timeoutMs && timeoutMs > 0) {\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\tif (pending.delete(id)) {\n\t\t\t\t\t\treject(new Error(`MCP server \"${config.name}\" timed out after ${timeoutMs}ms on ${method}`));\n\t\t\t\t\t}\n\t\t\t\t}, timeoutMs);\n\t\t\t\ttimer.unref?.();\n\t\t\t}\n\t\t\tpending.set(id, {\n\t\t\t\tresolve: (r) => {\n\t\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\t\tresolve(r);\n\t\t\t\t},\n\t\t\t\treject: (e) => {\n\t\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\t\treject(e);\n\t\t\t\t},\n\t\t\t});\n\t\t\tproc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", id, method, params })}\\n`);\n\t\t});\n\t}\n\n\tfunction notify(method: string, params?: unknown): void {\n\t\tproc.stdin!.write(`${JSON.stringify({ jsonrpc: \"2.0\", method, params })}\\n`);\n\t}\n\n\treturn {\n\t\trpc,\n\t\tnotify,\n\t\tterminate: () => {\n\t\t\trl.close();\n\t\t\tproc.kill();\n\t\t},\n\t};\n}\n\nasync function connectMcpServer(config: McpServerConfig): Promise<{ conn: McpConnection; tools: McpToolDef[] }> {\n\tmcpConnections.get(config.name)?.terminate();\n\n\tconst conn = spawnMcpServer(config);\n\tmcpConnections.set(config.name, conn);\n\n\tawait conn.rpc(\n\t\t\"initialize\",\n\t\t{\n\t\t\tprotocolVersion: \"2024-11-05\",\n\t\t\tcapabilities: { tools: {} },\n\t\t\tclientInfo: { name: \"hoocode\", version: \"1.0.0\" },\n\t\t},\n\t\tMCP_HANDSHAKE_TIMEOUT_MS,\n\t);\n\n\t// Per the MCP spec the client must acknowledge a successful initialize with the\n\t// initialized notification before issuing further requests; strict servers gate\n\t// tools/call on it.\n\tconn.notify(\"notifications/initialized\");\n\n\tconst toolsResult = (await conn.rpc(\"tools/list\", {}, MCP_HANDSHAKE_TIMEOUT_MS)) as {\n\t\ttools?: McpToolDef[];\n\t};\n\treturn { conn, tools: toolsResult.tools ?? [] };\n}\n\n/**\n * Return a live connection for a server, lazily reconnecting from the retained\n * config when the previous connection was torn down. Returns undefined only when\n * no config is known or a fresh connect attempt fails.\n */\nasync function getOrConnectMcp(name: string): Promise<McpConnection | undefined> {\n\tconst existing = mcpConnections.get(name);\n\tif (existing) return existing;\n\tconst config = mcpServerConfigs.get(name);\n\tif (!config) return undefined;\n\ttry {\n\t\tconst { conn } = await connectMcpServer(config);\n\t\treturn conn;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nlet mcpExitCleanupInstalled = false;\n/** Kill spawned MCP servers when the host process exits so they don't linger as\n * orphans (their stdin merely goes idle, which doesn't terminate them). */\nfunction installMcpExitCleanup(): void {\n\tif (mcpExitCleanupInstalled) return;\n\tmcpExitCleanupInstalled = true;\n\tprocess.once(\"exit\", () => {\n\t\tfor (const conn of mcpConnections.values()) {\n\t\t\ttry {\n\t\t\t\tconn.terminate();\n\t\t\t} catch {\n\t\t\t\t// best-effort cleanup\n\t\t\t}\n\t\t}\n\t\tmcpConnections.clear();\n\t});\n}\n\nfunction buildMcpSchema(tool: McpToolDef): ReturnType<typeof Type.Object> {\n\tconst props = tool.inputSchema?.properties ?? {};\n\tconst required = new Set(tool.inputSchema?.required ?? []);\n\tconst shape: Record<string, ReturnType<typeof Type.String>> = {};\n\n\tfor (const [key, prop] of Object.entries(props)) {\n\t\tlet field: ReturnType<typeof Type.String>;\n\t\tswitch (prop.type) {\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\t\tfield = Type.Number({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tcase \"boolean\":\n\t\t\t\tfield = Type.Boolean({ description: prop.description }) as unknown as ReturnType<typeof Type.String>;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfield = Type.String({ description: prop.description });\n\t\t}\n\t\tshape[key] = required.has(key) ? field : (Type.Optional(field) as unknown as ReturnType<typeof Type.String>);\n\t}\n\n\treturn Type.Object(shape);\n}\n\n/**\n * Parse standard MCP config format (used by Claude Desktop, VS Code, etc.)\n * into hoocode's McpServerConfig format.\n */\nfunction parseStandardMcpConfig(config: StandardMcpConfig, _source: string): McpServerConfig[] {\n\tif (!config.mcpServers) return [];\n\n\tconst servers: McpServerConfig[] = [];\n\tfor (const [name, serverConfig] of Object.entries(config.mcpServers)) {\n\t\tservers.push({\n\t\t\tname,\n\t\t\tcommand: serverConfig.command,\n\t\t\targs: serverConfig.args,\n\t\t\tenv: serverConfig.env,\n\t\t\tbackground: serverConfig.background,\n\t\t});\n\t}\n\treturn servers;\n}\n\n/**\n * Load MCP servers from a standard mcp.json file.\n * Returns an array of McpServerConfig, or empty array if file doesn't exist or is invalid.\n */\nfunction loadStandardMcpFile(filePath: string): McpServerConfig[] {\n\tif (!existsSync(filePath)) return [];\n\n\ttry {\n\t\tconst content = readFileSync(filePath, \"utf8\");\n\t\tconst config = JSON.parse(content) as StandardMcpConfig;\n\t\treturn parseStandardMcpConfig(config, filePath);\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n/**\n * Build the full {@link ToolDefinition} for one MCP tool — the complete JSON\n * schema plus the connect/execute machinery. Shared by the eager path (register\n * every tool up front) and the deferred path (materialize on resolve), so both\n * produce identical, callable tools.\n */\nfunction buildMcpToolDefinition(serverConfig: McpServerConfig, tool: McpToolDef): ToolDefinition {\n\tconst toolName = `mcp_${serverConfig.name}_${tool.name}`;\n\tconst schema = buildMcpSchema(tool);\n\tconst capturedServer = serverConfig.name;\n\tconst capturedTool = tool.name;\n\t// MCP tools default to background mode since they are external processes with potential high latency\n\tconst isBackground = serverConfig.background !== false;\n\n\treturn {\n\t\tname: toolName,\n\t\tlabel: `[MCP] ${serverConfig.name} › ${tool.name}`,\n\t\tdescription: tool.description,\n\t\tparameters: schema,\n\t\tbackground: isBackground,\n\t\t// Render a clean, prefixed title in chat — `MCP [server › tool] <args>` —\n\t\t// parallel to the subagent `Task [type] <desc>` line. Without this the\n\t\t// ToolExecutionComponent falls back to the raw `mcp_<server>_<tool>` name.\n\t\t// The args summary reuses the same helper as the background start/finish\n\t\t// messages so the chat title stays in sync with them.\n\t\trenderCall(args, theme) {\n\t\t\tconst summary = summarizeArgs((args ?? {}) as Record<string, unknown>);\n\t\t\tconst text =\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"MCP \")) +\n\t\t\t\ttheme.fg(\"accent\", `[${capturedServer} › ${capturedTool}]`) +\n\t\t\t\t(summary ? theme.fg(\"dim\", ` ${summary}`) : \"\");\n\t\t\treturn new Text(text, 0, 0);\n\t\t},\n\t\tasync execute(\n\t\t\t_toolCallId: string,\n\t\t\tparams: Static<typeof schema>,\n\t\t\tsignal: AbortSignal,\n\t\t\t_onUpdate: AgentToolUpdateCallback,\n\t\t): Promise<AgentToolResult<undefined>> {\n\t\t\t// Background MCP tools get a task store entry so they appear in the task pane.\n\t\t\t// Foreground tools skip this (their result is awaited inline). The server\n\t\t\t// name rides in subagentMode and becomes the row's `[server]` origin tag;\n\t\t\t// the title carries just the tool.\n\t\t\tconst task = isBackground\n\t\t\t\t? taskStore.create(capturedTool, { source: \"mcp\", subagentMode: capturedServer })\n\t\t\t\t: undefined;\n\t\t\tif (task) taskStore.update(task.id, { status: \"in_progress\" });\n\n\t\t\t// Lazily (re)connect: a dropped connection (server exit, process churn\n\t\t\t// between turns, a racing teardown) should transparently reconnect from\n\t\t\t// the retained config rather than permanently fail with \"not connected\".\n\t\t\tconst activeConn = await getOrConnectMcp(capturedServer);\n\t\t\tif (!activeConn) {\n\t\t\t\tif (task) taskStore.update(task.id, { status: \"failed\" });\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{ type: \"text\", text: `MCP server \"${capturedServer}\" is not connected (reconnect attempt failed)` },\n\t\t\t\t\t],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst abortPromise = new Promise<never>((_, reject) => {\n\t\t\t\t\tsignal.addEventListener(\"abort\", () => reject(new Error(\"Aborted\")));\n\t\t\t\t});\n\n\t\t\t\tconst result = await Promise.race([\n\t\t\t\t\tactiveConn.rpc(\"tools/call\", { name: capturedTool, arguments: params }),\n\t\t\t\t\tabortPromise,\n\t\t\t\t]);\n\n\t\t\t\tif (task) taskStore.update(task.id, { status: \"done\" });\n\t\t\t\treturn { content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }], details: undefined };\n\t\t\t} catch (error) {\n\t\t\t\tif (task) taskStore.update(task.id, { status: \"failed\" });\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t} as ToolDefinition;\n}\n\nconst RESOLVE_MCP_TOOLS_NAME = \"ResolveMcpTools\";\n\nexport function setupMcpLoader(pi: ExtensionAPI): void {\n\tpi.on(\"session_start\", async (_event: SessionStartEvent, ctx: ExtensionContext) => {\n\t\t// A spawned subagent whose tool allowlist has no MCP tools is told by its\n\t\t// parent to skip server connection entirely (see SUBAGENT_SKIP_MCP_ENV).\n\t\t// Each connect is a ~15s-timeout handshake; doing it for a subagent that can\n\t\t// never call the tools is pure startup latency.\n\t\tif (subagentSkipMcp()) return;\n\n\t\tinstallMcpExitCleanup();\n\t\tconst allServerConfigs: McpServerConfig[] = [];\n\t\tconst seenNames = new Set<string>();\n\n\t\t// 1. Load from standard mcp.json locations\n\t\t// User-level: ~/.agents/mcp.json\n\t\tconst userAgentsConfig = loadStandardMcpFile(join(homedir(), \".agents\", \"mcp.json\"));\n\t\tfor (const config of userAgentsConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// Project-level: ./.agents/mcp.json\n\t\tconst projectAgentsConfig = loadStandardMcpFile(join(ctx.cwd, \".agents\", \"mcp.json\"));\n\t\tfor (const config of projectAgentsConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// Claude Desktop: ~/.config/claude/mcp.json\n\t\tconst claudeDesktopConfig = loadStandardMcpFile(join(homedir(), \".config\", \"claude\", \"mcp.json\"));\n\t\tfor (const config of claudeDesktopConfig) {\n\t\t\tif (!seenNames.has(config.name)) {\n\t\t\t\tseenNames.add(config.name);\n\t\t\t\tallServerConfigs.push(config);\n\t\t\t}\n\t\t}\n\n\t\t// 2. Load from hoocode's per-server format (existing behavior)\n\t\tconst searchDirs = [join(HOOCODE_DIR, \"mcp-servers\"), join(ctx.cwd, \".hoocode\", \"mcp-servers\")];\n\n\t\tfor (const dir of searchDirs) {\n\t\t\tif (!existsSync(dir)) continue;\n\n\t\t\tlet files: string[];\n\t\t\ttry {\n\t\t\t\tfiles = (await readdir(dir)).filter((f) => f.endsWith(\".json\"));\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (const file of files) {\n\t\t\t\tconst cfgPath = join(dir, file);\n\t\t\t\tlet serverConfig: McpServerConfig;\n\n\t\t\t\ttry {\n\t\t\t\t\tserverConfig = JSON.parse(readFileSync(cfgPath, \"utf8\")) as McpServerConfig;\n\t\t\t\t\tif (!serverConfig.name || !serverConfig.command) {\n\t\t\t\t\t\tctx.ui.notify(`MCP: config \"${file}\" is missing required \"name\" or \"command\"`, \"warning\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tctx.ui.notify(`MCP: failed to parse \"${file}\": ${String(err)}`, \"error\");\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Skip if already loaded from standard config\n\t\t\t\tif (seenNames.has(serverConfig.name)) continue;\n\t\t\t\tseenNames.add(serverConfig.name);\n\t\t\t\tallServerConfigs.push(serverConfig);\n\t\t\t}\n\t\t}\n\n\t\t// 2b. Load from plugins/extensions that registered MCP servers during load.\n\t\tfor (const entry of getExtensionMcpServers()) {\n\t\t\tfor (const serverConfig of parseStandardMcpConfig(\n\t\t\t\t{ mcpServers: entry.mcpServers },\n\t\t\t\t`plugin:${entry.source}`,\n\t\t\t)) {\n\t\t\t\tif (seenNames.has(serverConfig.name)) continue;\n\t\t\t\tseenNames.add(serverConfig.name);\n\t\t\t\tallServerConfigs.push(serverConfig);\n\t\t\t}\n\t\t}\n\n\t\t// Deferral (spec §2): inject MCP tool names only and materialize each schema\n\t\t// on demand via ResolveMcpTools. Opt-in and top-level only — a subagent that\n\t\t// needs MCP has this env cleared, so it eager-registers its allowlisted tools\n\t\t// at dispatch (the dispatch ↔ schema interaction) and they are immediately callable.\n\t\tconst defer = deferMcpSchemas();\n\t\tconst deferredCatalog: DeferredMcpToolEntry[] = [];\n\t\t// Retain each deferred tool's raw definition + config so ResolveMcpTools can\n\t\t// build the full ToolDefinition on request.\n\t\tconst deferredByName = new Map<string, { serverConfig: McpServerConfig; tool: McpToolDef }>();\n\t\tconst resolvedNames = new Set<string>();\n\n\t\t// 3. Connect to all servers and register (or defer) tools\n\t\tfor (const serverConfig of allServerConfigs) {\n\t\t\t// Retain the config so a tool call can lazily reconnect a dropped server.\n\t\t\tmcpServerConfigs.set(serverConfig.name, serverConfig);\n\t\t\ttry {\n\t\t\t\tconst { tools } = await connectMcpServer(serverConfig);\n\n\t\t\t\tfor (const tool of tools) {\n\t\t\t\t\tconst toolName = `mcp_${serverConfig.name}_${tool.name}`;\n\t\t\t\t\tif (defer) {\n\t\t\t\t\t\tdeferredCatalog.push({ toolName, server: serverConfig.name, description: tool.description });\n\t\t\t\t\t\tdeferredByName.set(toolName, { serverConfig, tool });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tpi.registerTool(buildMcpToolDefinition(serverConfig, tool));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst bgMode = serverConfig.background !== false ? \"background\" : \"foreground\";\n\t\t\t\tconst suffix = defer ? \", schemas deferred\" : \"\";\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`MCP: connected \"${serverConfig.name}\" (${tools.length} tool${tools.length === 1 ? \"\" : \"s\"}, ${bgMode}${suffix})`,\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t} catch (err) {\n\t\t\t\tctx.ui.notify(`MCP: failed to connect \"${serverConfig.name}\": ${String(err)}`, \"error\");\n\t\t\t}\n\t\t}\n\n\t\t// 4. In deferred mode, register the single resolver that materializes schemas on demand.\n\t\tif (defer && deferredCatalog.length > 0) {\n\t\t\tconst resolveParams = Type.Object(\n\t\t\t\t{\n\t\t\t\t\tnames: Type.Array(Type.String(), {\n\t\t\t\t\t\tdescription: \"MCP tool names to make callable (e.g. 'mcp_github_create_pr' or 'create_pr').\",\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\t{ additionalProperties: false },\n\t\t\t);\n\t\t\tpi.registerTool({\n\t\t\t\tname: RESOLVE_MCP_TOOLS_NAME,\n\t\t\t\tlabel: RESOLVE_MCP_TOOLS_NAME,\n\t\t\t\tdescription:\n\t\t\t\t\t\"MCP tools are connected but their schemas are loaded on demand to keep context small. Call this with the tool name(s) you need to make them callable, then call the tool(s). Available MCP tools:\\n\" +\n\t\t\t\t\tformatDeferredCatalog(deferredCatalog),\n\t\t\t\tpromptSnippet: \"Resolve deferred MCP tool schemas by name before calling them.\",\n\t\t\t\tparameters: resolveParams,\n\t\t\t\tasync execute(_toolCallId: string, params: Static<typeof resolveParams>) {\n\t\t\t\t\tconst matched = selectResolvable(deferredCatalog, params.names ?? []);\n\t\t\t\t\tconst newlyResolved: string[] = [];\n\t\t\t\t\tfor (const entry of matched) {\n\t\t\t\t\t\tif (resolvedNames.has(entry.toolName)) continue;\n\t\t\t\t\t\tconst raw = deferredByName.get(entry.toolName);\n\t\t\t\t\t\tif (!raw) continue;\n\t\t\t\t\t\tpi.registerTool(buildMcpToolDefinition(raw.serverConfig, raw.tool));\n\t\t\t\t\t\tresolvedNames.add(entry.toolName);\n\t\t\t\t\t\tnewlyResolved.push(entry.toolName);\n\t\t\t\t\t}\n\t\t\t\t\tconst text = matched.length\n\t\t\t\t\t\t? `Resolved ${newlyResolved.length} MCP tool(s): ${matched.map((m) => m.toolName).join(\", \")}. They are now callable.`\n\t\t\t\t\t\t: `No MCP tools matched: ${(params.names ?? []).join(\", \") || \"(none)\"}. See ResolveMcpTools for the catalog.`;\n\t\t\t\t\treturn { content: [{ type: \"text\" as const, text }], details: undefined };\n\t\t\t\t},\n\t\t\t} as ToolDefinition);\n\t\t}\n\t});\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAuBH,OAAO,KAAK,EAAgB,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAmfjF,MAAM,WAAW,WAAW;IAC3B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACxC;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,iBAsZ/D","sourcesContent":["/**\n * Main entry point for the coding agent CLI.\n *\n * This file handles CLI argument parsing and translates them into\n * createAgentSession() options. The SDK does the heavy lifting.\n */\n\nimport { resolve } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { type ImageContent, modelsAreEqual } from \"@kolisachint/hoocode-ai\";\nimport { ProcessTerminal, setKeybindings, TUI } from \"@kolisachint/hoocode-tui\";\nimport chalk from \"chalk\";\nimport { type Args, type Mode, parseArgs, printHelp } from \"./cli/args.js\";\nimport { processFileArguments } from \"./cli/file-processor.js\";\nimport { buildInitialMessage } from \"./cli/initial-message.js\";\nimport { listModels } from \"./cli/list-models.js\";\nimport { selectSession } from \"./cli/session-picker.js\";\nimport { ENV_SESSION_DIR, expandTildePath, getAgentDir, VERSION } from \"./config.js\";\nimport { setAgentCliPaths } from \"./core/agent-manifest-paths.js\";\nimport { type CreateAgentSessionRuntimeFactory, createAgentSessionRuntime } from \"./core/agent-session-runtime.js\";\nimport {\n\ttype AgentSessionRuntimeDiagnostic,\n\tcreateAgentSessionFromServices,\n\tcreateAgentSessionServices,\n} from \"./core/agent-session-services.js\";\nimport { formatNoModelsAvailableMessage } from \"./core/auth-guidance.js\";\nimport { AuthStorage } from \"./core/auth-storage.js\";\nimport { exportFromFile } from \"./core/export-html/index.js\";\nimport type { ExtensionAPI, ExtensionFactory } from \"./core/extensions/types.js\";\nimport { KeybindingsManager } from \"./core/keybindings.js\";\nimport type { ModelRegistry } from \"./core/model-registry.js\";\nimport { resolveCliModel, resolveModelScope, type ScopedModel } from \"./core/model-resolver.js\";\nimport { restoreStdout, takeOverStdout } from \"./core/output-guard.js\";\nimport type { CreateAgentSessionOptions } from \"./core/sdk.js\";\nimport {\n\tformatMissingSessionCwdPrompt,\n\tgetMissingSessionCwdIssue,\n\tMissingSessionCwdError,\n\ttype SessionCwdIssue,\n} from \"./core/session-cwd.js\";\nimport { SessionManager } from \"./core/session-manager.js\";\nimport { SettingsManager } from \"./core/settings-manager.js\";\nimport {\n\tcanSpawnSubagent,\n\tDELEGATE_ALLOW_ENV,\n\tNESTED_CONCURRENCY_ENV,\n\tresolveMaxSubagentDepth,\n\tresolveNestedConcurrency,\n\tSUBAGENT_MAX_DEPTH_ENV,\n} from \"./core/subagent-depth.js\";\nimport { printTimings, resetTimings, time } from \"./core/timings.js\";\nimport {\n\tbuildTaskMainPrompt,\n\tcreateTaskOutputToolDefinition,\n\tcreateTaskToolDefinition,\n} from \"./core/tools/subagent.js\";\nimport { createTodoWriteToolDefinition } from \"./core/tools/todo.js\";\nimport { WARM_SUBAGENTS_ENV } from \"./core/warm-subagent-pool-instance.js\";\nimport { runMigrations, showDeprecationWarnings } from \"./migrations.js\";\nimport { InteractiveMode, runPrintMode, runRpcMode } from \"./modes/index.js\";\nimport { ExtensionSelectorComponent } from \"./modes/interactive/components/extension-selector.js\";\nimport { initTheme, stopThemeWatcher } from \"./modes/interactive/theme/theme.js\";\nimport { handleConfigCommand, handlePackageCommand } from \"./package-manager-cli.js\";\nimport { handleResourcesCommand } from \"./resources-cli.js\";\nimport { isLocalPath } from \"./utils/paths.js\";\n\n/**\n * Read all content from piped stdin.\n * Returns undefined if stdin is a TTY (interactive terminal).\n */\nasync function readPipedStdin(): Promise<string | undefined> {\n\t// If stdin is a TTY, we're running interactively - don't read stdin\n\tif (process.stdin.isTTY) {\n\t\treturn undefined;\n\t}\n\n\treturn new Promise((resolve) => {\n\t\tlet data = \"\";\n\t\tprocess.stdin.setEncoding(\"utf8\");\n\t\tprocess.stdin.on(\"data\", (chunk) => {\n\t\t\tdata += chunk;\n\t\t});\n\t\tprocess.stdin.on(\"end\", () => {\n\t\t\tresolve(data.trim() || undefined);\n\t\t});\n\t\tprocess.stdin.resume();\n\t});\n}\n\nfunction collectSettingsDiagnostics(\n\tsettingsManager: SettingsManager,\n\tcontext: string,\n): AgentSessionRuntimeDiagnostic[] {\n\treturn settingsManager.drainErrors().map(({ scope, error }) => ({\n\t\ttype: \"warning\",\n\t\tmessage: `(${context}, ${scope} settings) ${error.message}`,\n\t}));\n}\n\nfunction reportDiagnostics(diagnostics: readonly AgentSessionRuntimeDiagnostic[]): void {\n\tfor (const diagnostic of diagnostics) {\n\t\tconst color = diagnostic.type === \"error\" ? chalk.red : diagnostic.type === \"warning\" ? chalk.yellow : chalk.dim;\n\t\tconst prefix = diagnostic.type === \"error\" ? \"Error: \" : diagnostic.type === \"warning\" ? \"Warning: \" : \"\";\n\t\tconsole.error(color(`${prefix}${diagnostic.message}`));\n\t}\n}\n\nfunction isTruthyEnvFlag(value: string | undefined): boolean {\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ntype AppMode = \"interactive\" | \"print\" | \"json\" | \"rpc\";\n\nfunction resolveAppMode(parsed: Args, stdinIsTTY: boolean): AppMode {\n\tif (parsed.mode === \"rpc\") {\n\t\treturn \"rpc\";\n\t}\n\tif (parsed.mode === \"json\") {\n\t\treturn \"json\";\n\t}\n\tif (parsed.print || !stdinIsTTY) {\n\t\treturn \"print\";\n\t}\n\treturn \"interactive\";\n}\n\nfunction toPrintOutputMode(appMode: AppMode): Exclude<Mode, \"rpc\"> {\n\treturn appMode === \"json\" ? \"json\" : \"text\";\n}\n\nasync function prepareInitialMessage(\n\tparsed: Args,\n\tautoResizeImages: boolean,\n\tstdinContent?: string,\n): Promise<{\n\tinitialMessage?: string;\n\tinitialImages?: ImageContent[];\n}> {\n\tif (parsed.fileArgs.length === 0) {\n\t\treturn buildInitialMessage({ parsed, stdinContent });\n\t}\n\n\tconst { text, images } = await processFileArguments(parsed.fileArgs, { autoResizeImages });\n\treturn buildInitialMessage({\n\t\tparsed,\n\t\tfileText: text,\n\t\tfileImages: images,\n\t\tstdinContent,\n\t});\n}\n\n/** Result from resolving a session argument */\ntype ResolvedSession =\n\t| { type: \"path\"; path: string } // Direct file path\n\t| { type: \"local\"; path: string } // Found in current project\n\t| { type: \"global\"; path: string; cwd: string } // Found in different project\n\t| { type: \"not_found\"; arg: string }; // Not found anywhere\n\n/**\n * Resolve a session argument to a file path.\n * If it looks like a path, use as-is. Otherwise try to match as session ID prefix.\n */\nasync function resolveSessionPath(sessionArg: string, cwd: string, sessionDir?: string): Promise<ResolvedSession> {\n\t// If it looks like a file path, use as-is\n\tif (sessionArg.includes(\"/\") || sessionArg.includes(\"\\\\\") || sessionArg.endsWith(\".jsonl\")) {\n\t\treturn { type: \"path\", path: sessionArg };\n\t}\n\n\t// Try to match as session ID in current project first\n\tconst localSessions = await SessionManager.list(cwd, sessionDir);\n\tconst localMatches = localSessions.filter((s) => s.id.startsWith(sessionArg));\n\n\tif (localMatches.length >= 1) {\n\t\treturn { type: \"local\", path: localMatches[0].path };\n\t}\n\n\t// Try global search across all projects\n\tconst allSessions = await SessionManager.listAll();\n\tconst globalMatches = allSessions.filter((s) => s.id.startsWith(sessionArg));\n\n\tif (globalMatches.length >= 1) {\n\t\tconst match = globalMatches[0];\n\t\treturn { type: \"global\", path: match.path, cwd: match.cwd };\n\t}\n\n\t// Not found anywhere\n\treturn { type: \"not_found\", arg: sessionArg };\n}\n\n/** Prompt user for yes/no confirmation */\nasync function promptConfirm(message: string): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst rl = createInterface({\n\t\t\tinput: process.stdin,\n\t\t\toutput: process.stdout,\n\t\t});\n\t\trl.question(`${message} [y/N] `, (answer) => {\n\t\t\trl.close();\n\t\t\tresolve(answer.toLowerCase() === \"y\" || answer.toLowerCase() === \"yes\");\n\t\t});\n\t});\n}\n\nfunction validateForkFlags(parsed: Args): void {\n\tif (!parsed.fork) return;\n\n\tconst conflictingFlags = [\n\t\tparsed.session ? \"--session\" : undefined,\n\t\tparsed.continue ? \"--continue\" : undefined,\n\t\tparsed.resume ? \"--resume\" : undefined,\n\t\tparsed.noSession ? \"--no-session\" : undefined,\n\t].filter((flag): flag is string => flag !== undefined);\n\n\tif (conflictingFlags.length > 0) {\n\t\tconsole.error(chalk.red(`Error: --fork cannot be combined with ${conflictingFlags.join(\", \")}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nfunction forkSessionOrExit(sourcePath: string, cwd: string, sessionDir?: string): SessionManager {\n\ttry {\n\t\treturn SessionManager.forkFrom(sourcePath, cwd, sessionDir);\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nasync function createSessionManager(\n\tparsed: Args,\n\tcwd: string,\n\tsessionDir: string | undefined,\n\tsettingsManager: SettingsManager,\n): Promise<SessionManager> {\n\tif (parsed.noSession) {\n\t\treturn SessionManager.inMemory();\n\t}\n\n\tif (parsed.fork) {\n\t\tconst resolved = await resolveSessionPath(parsed.fork, cwd, sessionDir);\n\n\t\tswitch (resolved.type) {\n\t\t\tcase \"path\":\n\t\t\tcase \"local\":\n\t\t\tcase \"global\":\n\t\t\t\treturn forkSessionOrExit(resolved.path, cwd, sessionDir);\n\n\t\t\tcase \"not_found\":\n\t\t\t\tconsole.error(chalk.red(`No session found matching '${resolved.arg}'`));\n\t\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tif (parsed.session) {\n\t\tconst resolved = await resolveSessionPath(parsed.session, cwd, sessionDir);\n\n\t\tswitch (resolved.type) {\n\t\t\tcase \"path\":\n\t\t\tcase \"local\":\n\t\t\t\treturn SessionManager.open(resolved.path, sessionDir);\n\n\t\t\tcase \"global\": {\n\t\t\t\tconsole.log(chalk.yellow(`Session found in different project: ${resolved.cwd}`));\n\t\t\t\tconst shouldFork = await promptConfirm(\"Fork this session into current directory?\");\n\t\t\t\tif (!shouldFork) {\n\t\t\t\t\tconsole.log(chalk.dim(\"Aborted.\"));\n\t\t\t\t\tprocess.exit(0);\n\t\t\t\t}\n\t\t\t\treturn forkSessionOrExit(resolved.path, cwd, sessionDir);\n\t\t\t}\n\n\t\t\tcase \"not_found\":\n\t\t\t\tconsole.error(chalk.red(`No session found matching '${resolved.arg}'`));\n\t\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tif (parsed.resume) {\n\t\tinitTheme(settingsManager.getTheme(), true);\n\t\ttry {\n\t\t\tconst selectedPath = await selectSession(\n\t\t\t\t(onProgress) => SessionManager.list(cwd, sessionDir, onProgress),\n\t\t\t\tSessionManager.listAll,\n\t\t\t);\n\t\t\tif (!selectedPath) {\n\t\t\t\tconsole.log(chalk.dim(\"No session selected\"));\n\t\t\t\tprocess.exit(0);\n\t\t\t}\n\t\t\treturn SessionManager.open(selectedPath, sessionDir);\n\t\t} finally {\n\t\t\tstopThemeWatcher();\n\t\t}\n\t}\n\n\tif (parsed.continue) {\n\t\treturn SessionManager.continueRecent(cwd, sessionDir);\n\t}\n\n\treturn SessionManager.create(cwd, sessionDir);\n}\n\nfunction buildSessionOptions(\n\tparsed: Args,\n\tscopedModels: ScopedModel[],\n\thasExistingSession: boolean,\n\tmodelRegistry: ModelRegistry,\n\tsettingsManager: SettingsManager,\n): {\n\toptions: CreateAgentSessionOptions;\n\tcliThinkingFromModel: boolean;\n\tdiagnostics: AgentSessionRuntimeDiagnostic[];\n} {\n\tconst options: CreateAgentSessionOptions = {};\n\tconst diagnostics: AgentSessionRuntimeDiagnostic[] = [];\n\tlet cliThinkingFromModel = false;\n\n\t// Model from CLI\n\t// - supports --provider <name> --model <pattern>\n\t// - supports --model <provider>/<pattern>\n\tif (parsed.model) {\n\t\tconst resolved = resolveCliModel({\n\t\t\tcliProvider: parsed.provider,\n\t\t\tcliModel: parsed.model,\n\t\t\tmodelRegistry,\n\t\t});\n\t\tif (resolved.warning) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: resolved.warning });\n\t\t}\n\t\tif (resolved.error) {\n\t\t\tdiagnostics.push({ type: \"error\", message: resolved.error });\n\t\t}\n\t\tif (resolved.model) {\n\t\t\toptions.model = resolved.model;\n\t\t\t// Allow \"--model <pattern>:<thinking>\" as a shorthand.\n\t\t\t// Explicit --thinking still takes precedence (applied later).\n\t\t\tif (!parsed.thinking && resolved.thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = resolved.thinkingLevel;\n\t\t\t\tcliThinkingFromModel = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!options.model && scopedModels.length > 0 && !hasExistingSession) {\n\t\t// Check if saved default is in scoped models - use it if so, otherwise first scoped model\n\t\tconst savedProvider = settingsManager.getDefaultProvider();\n\t\tconst savedModelId = settingsManager.getDefaultModel();\n\t\tconst savedModel = savedProvider && savedModelId ? modelRegistry.find(savedProvider, savedModelId) : undefined;\n\t\tconst savedInScope = savedModel ? scopedModels.find((sm) => modelsAreEqual(sm.model, savedModel)) : undefined;\n\n\t\tif (savedInScope) {\n\t\t\toptions.model = savedInScope.model;\n\t\t\t// Use thinking level from scoped model config if explicitly set\n\t\t\tif (!parsed.thinking && savedInScope.thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = savedInScope.thinkingLevel;\n\t\t\t}\n\t\t} else {\n\t\t\toptions.model = scopedModels[0].model;\n\t\t\t// Use thinking level from first scoped model if explicitly set\n\t\t\tif (!parsed.thinking && scopedModels[0].thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = scopedModels[0].thinkingLevel;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Thinking level from CLI (takes precedence over scoped model thinking levels set above)\n\tif (parsed.thinking) {\n\t\toptions.thinkingLevel = parsed.thinking;\n\t}\n\n\t// Scoped models for Ctrl+P cycling\n\t// Keep thinking level undefined when not explicitly set in the model pattern.\n\t// Undefined means \"inherit current session thinking level\" during cycling.\n\tif (scopedModels.length > 0) {\n\t\toptions.scopedModels = scopedModels.map((sm) => ({\n\t\t\tmodel: sm.model,\n\t\t\tthinkingLevel: sm.thinkingLevel,\n\t\t}));\n\t}\n\n\t// API key from CLI - set in authStorage\n\t// (handled by caller before createAgentSession)\n\n\t// Tools\n\tif (parsed.noTools) {\n\t\toptions.noTools = \"all\";\n\t} else if (parsed.noBuiltinTools) {\n\t\toptions.noTools = \"builtin\";\n\t}\n\tif (parsed.tools) {\n\t\toptions.tools = [...parsed.tools];\n\t}\n\tif (parsed.disallowedTools) {\n\t\toptions.disallowedTools = [...parsed.disallowedTools];\n\t}\n\t// Web tools (webfetch + websearch): opt-in via --enable-webtools flag or the\n\t// enableWebTools setting. They are registered as base tools but inactive by\n\t// default; this adds them to the default active set.\n\tif (parsed.enableWebTools ?? settingsManager.getEnableWebTools()) {\n\t\toptions.enableWebTools = true;\n\t}\n\t// Browser tools (browser_run + browser_continue): opt-in via --enable-browsertools\n\t// flag or the enableBrowserTools setting. Registered as base tools but inactive by\n\t// default; this adds them to the default active set.\n\tif (parsed.enableBrowserTools ?? settingsManager.getEnableBrowserTools()) {\n\t\toptions.enableBrowserTools = true;\n\t}\n\t// Live preview for browser_run: defaults the streamed viewer on and auto-opens\n\t// it. Applied as a runtime settings override so the session's tool factory reads\n\t// it via the shared settingsManager (no extra option plumbing).\n\tif (parsed.enableBrowserLivePreview) {\n\t\tsettingsManager.applyOverrides({ enableBrowserLivePreview: true });\n\t}\n\t// Document tools (DocRead/DocEdit/DocWrite + the DocScan/DocGrep/DocPeek\n\t// discovery loop): opt-in via --enable-filetools flag or the enableFileTools\n\t// setting. Registered as base tools but inactive by default; this adds them to\n\t// the default active set.\n\tif (parsed.enableFileTools ?? settingsManager.getEnableFileTools()) {\n\t\toptions.enableFileTools = true;\n\t\t// DocRead/DocEdit/DocWrite drive a lossless id-based extract -> patch ->\n\t\t// reconstruct flow that depends on precise, well-formed tool calls. Models\n\t\t// that are weak at tool calling tend to mangle the id-based patches and\n\t\t// corrupt documents, so surface a one-time heads-up when these are enabled.\n\t\tdiagnostics.push({\n\t\t\ttype: \"warning\",\n\t\t\tmessage:\n\t\t\t\t\"Document tools (DocRead/DocEdit/DocWrite) are enabled. They require precise, id-based patches; \" +\n\t\t\t\t\"use a model that is strong at tool calling, or these edits can corrupt files.\",\n\t\t});\n\t}\n\n\t// Optional Task (subagent) tool: opt-in via --enable-subagents flag or the enableSubagent setting.\n\t// Registered as a custom tool; respects --tools/--no-tools allowlists like any other tool.\n\t//\n\t// Nesting is bounded by the tree-wide cap (maxSubagentDepth, default 1). The root\n\t// seeds the cap into the environment so every descendant agrees on one value; the\n\t// Task tool is registered only while this process's depth is below that cap. At the\n\t// default cap this reproduces the original guard exactly: subagents (depth >= 1) get\n\t// no Task tool and cannot recursively dispatch.\n\tconst isSubagentChild = parsed.taskId !== undefined;\n\tif (process.env[SUBAGENT_MAX_DEPTH_ENV] === undefined) {\n\t\t// The root seeds the tree-wide cap; the --max-subagent-depth flag overrides the\n\t\t// setting. resolveMaxSubagentDepth clamps it to the supported range so the seeded\n\t\t// env (and everything that inherits it) carries a sane value. Descendants inherit\n\t\t// it via the environment (env already set => keep it).\n\t\tprocess.env[SUBAGENT_MAX_DEPTH_ENV] = String(\n\t\t\tresolveMaxSubagentDepth(parsed.maxSubagentDepth ?? settingsManager.getMaxSubagentDepth()),\n\t\t);\n\t}\n\tif (process.env[NESTED_CONCURRENCY_ENV] === undefined) {\n\t\t// Seed the nested-pool concurrency from settings so descendants agree on one value.\n\t\tprocess.env[NESTED_CONCURRENCY_ENV] = String(\n\t\t\tresolveNestedConcurrency(settingsManager.getNestedSubagentConcurrency()),\n\t\t);\n\t}\n\t// Scoped delegation: --delegate-allow is the authoritative restriction for this\n\t// process. Set it from the flag, or clear any inherited value so a restricted\n\t// parent's scope never leaks into a child that wasn't given its own.\n\tif (parsed.delegateAllow && parsed.delegateAllow.length > 0) {\n\t\tprocess.env[DELEGATE_ALLOW_ENV] = parsed.delegateAllow.join(\",\");\n\t} else {\n\t\tdelete process.env[DELEGATE_ALLOW_ENV];\n\t}\n\tif (canSpawnSubagent() && (parsed.subagent ?? settingsManager.getEnableSubagent())) {\n\t\toptions.customTools = [\n\t\t\t...(options.customTools ?? []),\n\t\t\tcreateTaskToolDefinition(),\n\t\t\tcreateTaskOutputToolDefinition(),\n\t\t];\n\t\t// Warm subagents (experimental): dispatch eligible foreground subagents on\n\t\t// reused RPC workers to skip the cold-boot. Root-only — the Task tool exists\n\t\t// only where canSpawnSubagent holds and never inside a spawned child — and\n\t\t// carried via env so the Task tool reads it without threading a setting.\n\t\tif (!isSubagentChild && (parsed.warmSubagents ?? settingsManager.getWarmSubagents())) {\n\t\t\tprocess.env[WARM_SUBAGENTS_ENV] = \"1\";\n\t\t}\n\t}\n\n\t// Optional TodoWrite tool: opt-in via --enable-todowrite flag or the\n\t// enableTodoWrite setting. Never registered inside a spawned subagent child —\n\t// its todos would otherwise leak into the parent's \"main\" task group in the pane.\n\tif (!isSubagentChild && (parsed.todoWrite ?? settingsManager.getEnableTodoWrite())) {\n\t\toptions.customTools = [...(options.customTools ?? []), createTodoWriteToolDefinition()];\n\t}\n\n\treturn { options, cliThinkingFromModel, diagnostics };\n}\n\nfunction resolveCliPaths(cwd: string, paths: string[] | undefined): string[] | undefined {\n\treturn paths?.map((value) => (isLocalPath(value) ? resolve(cwd, value) : value));\n}\n\nasync function promptForMissingSessionCwd(\n\tissue: SessionCwdIssue,\n\tsettingsManager: SettingsManager,\n): Promise<string | undefined> {\n\tinitTheme(settingsManager.getTheme());\n\tsetKeybindings(KeybindingsManager.create());\n\n\treturn new Promise((resolve) => {\n\t\tconst ui = new TUI(new ProcessTerminal(), settingsManager.getShowHardwareCursor());\n\t\tui.setClearOnShrink(settingsManager.getClearOnShrink());\n\n\t\tlet settled = false;\n\t\tconst finish = (result: string | undefined) => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tui.stop();\n\t\t\tresolve(result);\n\t\t};\n\n\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\tformatMissingSessionCwdPrompt(issue),\n\t\t\t[\"Continue\", \"Cancel\"],\n\t\t\t(option) => finish(option === \"Continue\" ? issue.fallbackCwd : undefined),\n\t\t\t() => finish(undefined),\n\t\t\t{ tui: ui },\n\t\t);\n\t\tui.addChild(selector);\n\t\tui.setFocus(selector);\n\t\tui.start();\n\t});\n}\n\nexport interface MainOptions {\n\textensionFactories?: ExtensionFactory[];\n}\n\nexport async function main(args: string[], options?: MainOptions) {\n\tresetTimings();\n\tconst offlineMode = args.includes(\"--offline\") || isTruthyEnvFlag(process.env.HOOCODE_OFFLINE);\n\tif (offlineMode) {\n\t\tprocess.env.HOOCODE_OFFLINE = \"1\";\n\t\tprocess.env.HOOCODE_SKIP_VERSION_CHECK = \"1\";\n\t}\n\n\tif (await handlePackageCommand(args)) {\n\t\treturn;\n\t}\n\n\tif (await handleConfigCommand(args)) {\n\t\treturn;\n\t}\n\n\tif (await handleResourcesCommand(args)) {\n\t\treturn;\n\t}\n\n\tconst parsed = parseArgs(args);\n\tif (parsed.diagnostics.length > 0) {\n\t\tfor (const d of parsed.diagnostics) {\n\t\t\tconst color = d.type === \"error\" ? chalk.red : chalk.yellow;\n\t\t\tconsole.error(color(`${d.type === \"error\" ? \"Error\" : \"Warning\"}: ${d.message}`));\n\t\t}\n\t\tif (parsed.diagnostics.some((d) => d.type === \"error\")) {\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\ttime(\"parseArgs\");\n\tlet appMode = resolveAppMode(parsed, process.stdin.isTTY);\n\tconst shouldTakeOverStdout = appMode !== \"interactive\";\n\tif (shouldTakeOverStdout) {\n\t\ttakeOverStdout();\n\t}\n\n\tif (parsed.version) {\n\t\tconsole.log(VERSION);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.export) {\n\t\tlet result: string;\n\t\ttry {\n\t\t\tconst outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;\n\t\t\tresult = await exportFromFile(parsed.export, outputPath);\n\t\t} catch (error: unknown) {\n\t\t\tconst message = error instanceof Error ? error.message : \"Failed to export session\";\n\t\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tconsole.log(`Exported to: ${result}`);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.mode === \"rpc\" && parsed.fileArgs.length > 0) {\n\t\tconsole.error(chalk.red(\"Error: @file arguments are not supported in RPC mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\tvalidateForkFlags(parsed);\n\n\t// Run migrations (pass cwd for project-local migrations)\n\tconst { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(process.cwd());\n\ttime(\"runMigrations\");\n\n\tconst cwd = process.cwd();\n\tconst agentDir = getAgentDir();\n\tconst startupSettingsManager = SettingsManager.create(cwd, agentDir);\n\treportDiagnostics(collectSettingsDiagnostics(startupSettingsManager, \"startup session lookup\"));\n\n\t// Decide the final runtime cwd before creating cwd-bound runtime services.\n\t// --session and --resume may select a session from another project, so project-local\n\t// settings, resources, provider registrations, and models must be resolved only after\n\t// the target session cwd is known. The startup-cwd settings manager is used only for\n\t// sessionDir lookup during session selection.\n\tconst envSessionDir = process.env[ENV_SESSION_DIR];\n\tconst sessionDir =\n\t\tparsed.sessionDir ??\n\t\t(envSessionDir ? expandTildePath(envSessionDir) : undefined) ??\n\t\tstartupSettingsManager.getSessionDir();\n\tlet sessionManager = await createSessionManager(parsed, cwd, sessionDir, startupSettingsManager);\n\tconst missingSessionCwdIssue = getMissingSessionCwdIssue(sessionManager, cwd);\n\tif (missingSessionCwdIssue) {\n\t\tif (appMode === \"interactive\") {\n\t\t\tconst selectedCwd = await promptForMissingSessionCwd(missingSessionCwdIssue, startupSettingsManager);\n\t\t\tif (!selectedCwd) {\n\t\t\t\tprocess.exit(0);\n\t\t\t}\n\t\t\tsessionManager = SessionManager.open(missingSessionCwdIssue.sessionFile!, sessionDir, selectedCwd);\n\t\t} else {\n\t\t\tconsole.error(chalk.red(new MissingSessionCwdError(missingSessionCwdIssue).message));\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\ttime(\"createSessionManager\");\n\n\tconst resolvedExtensionPaths = resolveCliPaths(cwd, parsed.extensions);\n\tconst resolvedSkillPaths = resolveCliPaths(cwd, parsed.skills);\n\tconst resolvedAgentPaths = resolveCliPaths(cwd, parsed.agents);\n\tconst resolvedPromptTemplatePaths = resolveCliPaths(cwd, parsed.promptTemplates);\n\tconst resolvedSlashCommandPaths = resolveCliPaths(cwd, parsed.slashCommands);\n\tconst resolvedThemePaths = resolveCliPaths(cwd, parsed.themes);\n\n\t// Populate the module-level CLI agent store before any session is created.\n\tsetAgentCliPaths(resolvedAgentPaths ?? []);\n\n\t// Synthetic factory: feed CLI --mode-path values into the extension runtime\n\t// so hoo-core (and any other extension that reads pi.getModeSearchPaths)\n\t// sees them alongside extension-registered dirs.\n\tconst cliModePaths = parsed.modePaths ?? [];\n\tconst cliResourcePathFactories: ExtensionFactory[] =\n\t\tcliModePaths.length === 0\n\t\t\t? []\n\t\t\t: [\n\t\t\t\t\tObject.assign(\n\t\t\t\t\t\t(pi: ExtensionAPI) => {\n\t\t\t\t\t\t\tfor (const p of cliModePaths) pi.addModeSearchPath(p);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{ internal: true },\n\t\t\t\t\t),\n\t\t\t\t];\n\tconst allExtensionFactories: ExtensionFactory[] = [\n\t\t...cliResourcePathFactories,\n\t\t...(options?.extensionFactories ?? []),\n\t];\n\tconst authStorage = AuthStorage.create();\n\t// A spawned subagent (run with a task id) is a single-shot, non-interactive\n\t// process: it renders no TUI and its prompt is a plain task, never a slash\n\t// command. Themes, slash commands, and prompt templates are dead weight in that\n\t// path, so skip loading them to trim the child's cold-boot cost. Skills, context\n\t// files, and extensions (which carry the core tools) are kept — they affect the\n\t// subagent's actual work.\n\tconst isSubagentBoot = parsed.taskId !== undefined;\n\tconst createRuntime: CreateAgentSessionRuntimeFactory = async ({\n\t\tcwd,\n\t\tagentDir,\n\t\tsessionManager,\n\t\tsessionStartEvent,\n\t}) => {\n\t\tconst services = await createAgentSessionServices({\n\t\t\tcwd,\n\t\t\tagentDir,\n\t\t\tauthStorage,\n\t\t\textensionFlagValues: parsed.unknownFlags,\n\t\t\tresourceLoaderOptions: {\n\t\t\t\tadditionalExtensionPaths: resolvedExtensionPaths,\n\t\t\t\tadditionalSkillPaths: resolvedSkillPaths,\n\t\t\t\tadditionalPromptTemplatePaths: resolvedPromptTemplatePaths,\n\t\t\t\tadditionalSlashCommandPaths: resolvedSlashCommandPaths,\n\t\t\t\tadditionalThemePaths: resolvedThemePaths,\n\t\t\t\tnoExtensions: parsed.noExtensions,\n\t\t\t\tnoSkills: parsed.noSkills,\n\t\t\t\tnoPromptTemplates: parsed.noPromptTemplates || isSubagentBoot,\n\t\t\t\tnoSlashCommands: parsed.noSlashCommands || isSubagentBoot,\n\t\t\t\tnoThemes: parsed.noThemes || isSubagentBoot,\n\t\t\t\tnoContextFiles: parsed.noContextFiles,\n\t\t\t\tsystemPrompt: parsed.systemPrompt,\n\t\t\t\textensionFactories: allExtensionFactories,\n\t\t\t},\n\t\t});\n\t\tconst { settingsManager, modelRegistry, resourceLoader } = services;\n\t\tconst diagnostics: AgentSessionRuntimeDiagnostic[] = [\n\t\t\t...services.diagnostics,\n\t\t\t...collectSettingsDiagnostics(settingsManager, \"runtime creation\"),\n\t\t\t...resourceLoader.getExtensions().errors.map(({ path, error }) => ({\n\t\t\t\ttype: \"error\" as const,\n\t\t\t\tmessage: `Failed to load extension \"${path}\": ${error}`,\n\t\t\t})),\n\t\t];\n\n\t\t// When subagent tooling is enabled, append the main session subagent instructions.\n\t\tif (parsed.subagent ?? settingsManager.getEnableSubagent()) {\n\t\t\tresourceLoader.addAppendSystemPrompt(buildTaskMainPrompt());\n\t\t}\n\n\t\tconst modelPatterns = parsed.models ?? settingsManager.getEnabledModels();\n\t\tconst scopedModels =\n\t\t\tmodelPatterns && modelPatterns.length > 0 ? await resolveModelScope(modelPatterns, modelRegistry) : [];\n\t\tconst {\n\t\t\toptions: sessionOptions,\n\t\t\tcliThinkingFromModel,\n\t\t\tdiagnostics: sessionOptionDiagnostics,\n\t\t} = buildSessionOptions(\n\t\t\tparsed,\n\t\t\tscopedModels,\n\t\t\tsessionManager.buildSessionContext().messages.length > 0,\n\t\t\tmodelRegistry,\n\t\t\tsettingsManager,\n\t\t);\n\t\tdiagnostics.push(...sessionOptionDiagnostics);\n\n\t\tif (parsed.apiKey) {\n\t\t\tif (!sessionOptions.model) {\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\ttype: \"error\",\n\t\t\t\t\tmessage: \"--api-key requires a model to be specified via --model, --provider/--model, or --models\",\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tauthStorage.setRuntimeApiKey(sessionOptions.model.provider, parsed.apiKey);\n\t\t\t}\n\t\t}\n\n\t\tconst created = await createAgentSessionFromServices({\n\t\t\tservices,\n\t\t\tsessionManager,\n\t\t\tsessionStartEvent,\n\t\t\tmodel: sessionOptions.model,\n\t\t\tthinkingLevel: sessionOptions.thinkingLevel,\n\t\t\tscopedModels: sessionOptions.scopedModels,\n\t\t\ttools: sessionOptions.tools,\n\t\t\tnoTools: sessionOptions.noTools,\n\t\t\tcustomTools: sessionOptions.customTools,\n\t\t\tenableWebTools: sessionOptions.enableWebTools,\n\t\t\tenableBrowserTools: sessionOptions.enableBrowserTools,\n\t\t\tenableFileTools: sessionOptions.enableFileTools,\n\t\t});\n\t\tconst cliThinkingOverride = parsed.thinking !== undefined || cliThinkingFromModel;\n\t\tif (created.session.model && cliThinkingOverride) {\n\t\t\tcreated.session.setThinkingLevel(created.session.thinkingLevel);\n\t\t}\n\n\t\treturn {\n\t\t\t...created,\n\t\t\tservices,\n\t\t\tdiagnostics,\n\t\t};\n\t};\n\ttime(\"createRuntime\");\n\tconst runtime = await createAgentSessionRuntime(createRuntime, {\n\t\tcwd: sessionManager.getCwd(),\n\t\tagentDir,\n\t\tsessionManager,\n\t});\n\tconst { services, session, modelFallbackMessage } = runtime;\n\tconst { settingsManager, modelRegistry, resourceLoader } = services;\n\n\tif (parsed.help) {\n\t\tconst extensionFlags = resourceLoader\n\t\t\t.getExtensions()\n\t\t\t.extensions.flatMap((extension) => Array.from(extension.flags.values()));\n\t\tprintHelp(extensionFlags);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.listModels !== undefined) {\n\t\tconst searchPattern = typeof parsed.listModels === \"string\" ? parsed.listModels : undefined;\n\t\tawait listModels(modelRegistry, searchPattern);\n\t\tprocess.exit(0);\n\t}\n\n\t// Read piped stdin content (if any) - skip for RPC mode which uses stdin for JSON-RPC\n\tlet stdinContent: string | undefined;\n\tif (appMode !== \"rpc\") {\n\t\tstdinContent = await readPipedStdin();\n\t\tif (stdinContent !== undefined && appMode === \"interactive\") {\n\t\t\tappMode = \"print\";\n\t\t}\n\t}\n\ttime(\"readPipedStdin\");\n\n\tconst { initialMessage, initialImages } = await prepareInitialMessage(\n\t\tparsed,\n\t\tsettingsManager.getImageAutoResize(),\n\t\tstdinContent,\n\t);\n\ttime(\"prepareInitialMessage\");\n\tinitTheme(settingsManager.getTheme(), appMode === \"interactive\");\n\ttime(\"initTheme\");\n\n\t// Show deprecation warnings in interactive mode\n\tif (appMode === \"interactive\" && deprecationWarnings.length > 0) {\n\t\tawait showDeprecationWarnings(deprecationWarnings);\n\t}\n\n\tconst scopedModels = [...session.scopedModels];\n\ttime(\"resolveModelScope\");\n\treportDiagnostics(runtime.diagnostics);\n\tif (runtime.diagnostics.some((diagnostic) => diagnostic.type === \"error\")) {\n\t\tprocess.exit(1);\n\t}\n\ttime(\"createAgentSession\");\n\n\tif (appMode !== \"interactive\" && !session.model) {\n\t\tconsole.error(chalk.red(formatNoModelsAvailableMessage()));\n\t\tprocess.exit(1);\n\t}\n\n\tconst startupBenchmark = isTruthyEnvFlag(process.env.HOOCODE_STARTUP_BENCHMARK);\n\tif (startupBenchmark && appMode !== \"interactive\") {\n\t\tconsole.error(chalk.red(\"Error: HOOCODE_STARTUP_BENCHMARK only supports interactive mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\tif (appMode === \"rpc\") {\n\t\tprintTimings();\n\t\tawait runRpcMode(runtime);\n\t} else if (appMode === \"interactive\") {\n\t\tif (scopedModels.length > 0 && (parsed.verbose || !settingsManager.getQuietStartup())) {\n\t\t\tconst modelList = scopedModels\n\t\t\t\t.map((sm) => {\n\t\t\t\t\tconst thinkingStr = sm.thinkingLevel ? `:${sm.thinkingLevel}` : \"\";\n\t\t\t\t\treturn `${sm.model.id}${thinkingStr}`;\n\t\t\t\t})\n\t\t\t\t.join(\", \");\n\t\t\tconsole.log(chalk.dim(`Model scope: ${modelList} ${chalk.gray(\"(Ctrl+P to cycle)\")}`));\n\t\t}\n\n\t\tconst interactiveMode = new InteractiveMode(runtime, {\n\t\t\tmigratedProviders,\n\t\t\tmodelFallbackMessage,\n\t\t\tinitialMessage,\n\t\t\tinitialImages,\n\t\t\tinitialMessages: parsed.messages,\n\t\t\tverbose: parsed.verbose,\n\t\t});\n\n\t\t// Optional hooteams bridge. `--team auto` discovers a config, spawns a\n\t\t// local hooteams child on a free port, and proceeds as if its URL had\n\t\t// been passed; the child is reaped on exit (clean or signal) via a\n\t\t// process \"exit\" hook plus the explicit stop below.\n\t\tlet autoTeam: { url: string; stop(): Promise<void> } | undefined;\n\t\tlet teamUrl = parsed.team;\n\t\tif (teamUrl === \"auto\") {\n\t\t\tconst { startAutoTeam } = await import(\"./core/team-auto.js\");\n\t\t\ttry {\n\t\t\t\tautoTeam = await startAutoTeam(process.cwd(), { log: (message) => console.log(chalk.dim(message)) });\n\t\t\t\tteamUrl = autoTeam.url;\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(chalk.red(error instanceof Error ? error.message : String(error)));\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t}\n\n\t\t// Team mirror + client. Fire-and-forget: connect failures and drops warn\n\t\t// in the background and never block the main agent. Warnings go through\n\t\t// the chat, not console.error: a raw stderr write while the TUI owns the\n\t\t// screen scribbles over the render and can leave the editor looking\n\t\t// frozen. The same connection powers team focus / nudge / attach.\n\t\tlet teamView: { stop(): void } | undefined;\n\t\tif (teamUrl) {\n\t\t\tconst { connectTeamView } = await import(\"./core/team-view.js\");\n\t\t\tconst teamClient = connectTeamView(teamUrl, {\n\t\t\t\twarn: (message) => interactiveMode.showWarning(message),\n\t\t\t});\n\t\t\tteamView = teamClient;\n\t\t\tinteractiveMode.attachTeamClient(teamClient);\n\t\t}\n\t\tif (startupBenchmark) {\n\t\t\tawait interactiveMode.init();\n\t\t\ttime(\"interactiveMode.init\");\n\t\t\tprintTimings();\n\t\t\tteamView?.stop();\n\t\t\tawait autoTeam?.stop();\n\t\t\tinteractiveMode.stop();\n\t\t\tstopThemeWatcher();\n\t\t\tif (process.stdout.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stdout.once(\"drain\", resolve));\n\t\t\t}\n\t\t\tif (process.stderr.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stderr.once(\"drain\", resolve));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tprintTimings();\n\t\ttry {\n\t\t\tawait interactiveMode.run();\n\t\t} finally {\n\t\t\tteamView?.stop();\n\t\t\tawait autoTeam?.stop();\n\t\t}\n\t} else {\n\t\tprintTimings();\n\t\tconst exitCode = await runPrintMode(runtime, {\n\t\t\tmode: toPrintOutputMode(appMode),\n\t\t\tmessages: parsed.messages,\n\t\t\tinitialMessage,\n\t\t\tinitialImages,\n\t\t\ttaskId: parsed.taskId,\n\t\t\tmaxTurns: parsed.maxTurns,\n\t\t});\n\t\tstopThemeWatcher();\n\t\trestoreStdout();\n\t\tif (exitCode !== 0) {\n\t\t\tprocess.exitCode = exitCode;\n\t\t}\n\t\t// Spawned subagents (run with a task id) must exit promptly once their work is\n\t\t// done and result.json is written. The child's runtime can leave handles open\n\t\t// (e.g. MCP client connections) that keep the event loop alive, so a natural\n\t\t// exit may never happen. When that occurs the parent lifeguard SIGKILLs the idle\n\t\t// child at the 60s heartbeat threshold and misreports an already-completed task\n\t\t// as \"stalled\". Force a clean exit after draining output to avoid that false stall.\n\t\tconst ranAsSubagent = typeof parsed.taskId === \"string\" && parsed.taskId.length > 0;\n\t\tif (ranAsSubagent) {\n\t\t\tif (process.stdout.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stdout.once(\"drain\", resolve));\n\t\t\t}\n\t\t\tif (process.stderr.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stderr.once(\"drain\", resolve));\n\t\t\t}\n\t\t\tprocess.exit(exitCode);\n\t\t}\n\t\treturn;\n\t}\n}\n"]}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAuBH,OAAO,KAAK,EAAgB,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAwgBjF,MAAM,WAAW,WAAW;IAC3B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACxC;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,iBAsZ/D","sourcesContent":["/**\n * Main entry point for the coding agent CLI.\n *\n * This file handles CLI argument parsing and translates them into\n * createAgentSession() options. The SDK does the heavy lifting.\n */\n\nimport { resolve } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { type ImageContent, modelsAreEqual } from \"@kolisachint/hoocode-ai\";\nimport { ProcessTerminal, setKeybindings, TUI } from \"@kolisachint/hoocode-tui\";\nimport chalk from \"chalk\";\nimport { type Args, type Mode, parseArgs, printHelp } from \"./cli/args.js\";\nimport { processFileArguments } from \"./cli/file-processor.js\";\nimport { buildInitialMessage } from \"./cli/initial-message.js\";\nimport { listModels } from \"./cli/list-models.js\";\nimport { selectSession } from \"./cli/session-picker.js\";\nimport { ENV_SESSION_DIR, expandTildePath, getAgentDir, VERSION } from \"./config.js\";\nimport { setAgentCliPaths } from \"./core/agent-manifest-paths.js\";\nimport { type CreateAgentSessionRuntimeFactory, createAgentSessionRuntime } from \"./core/agent-session-runtime.js\";\nimport {\n\ttype AgentSessionRuntimeDiagnostic,\n\tcreateAgentSessionFromServices,\n\tcreateAgentSessionServices,\n} from \"./core/agent-session-services.js\";\nimport { formatNoModelsAvailableMessage } from \"./core/auth-guidance.js\";\nimport { AuthStorage } from \"./core/auth-storage.js\";\nimport { exportFromFile } from \"./core/export-html/index.js\";\nimport type { ExtensionAPI, ExtensionFactory } from \"./core/extensions/types.js\";\nimport { KeybindingsManager } from \"./core/keybindings.js\";\nimport type { ModelRegistry } from \"./core/model-registry.js\";\nimport { resolveCliModel, resolveModelScope, type ScopedModel } from \"./core/model-resolver.js\";\nimport { restoreStdout, takeOverStdout } from \"./core/output-guard.js\";\nimport type { CreateAgentSessionOptions } from \"./core/sdk.js\";\nimport {\n\tformatMissingSessionCwdPrompt,\n\tgetMissingSessionCwdIssue,\n\tMissingSessionCwdError,\n\ttype SessionCwdIssue,\n} from \"./core/session-cwd.js\";\nimport { SessionManager } from \"./core/session-manager.js\";\nimport { SettingsManager } from \"./core/settings-manager.js\";\nimport {\n\tcanSpawnSubagent,\n\tDEFER_MCP_SCHEMAS_ENV,\n\tDELEGATE_ALLOW_ENV,\n\tNESTED_CONCURRENCY_ENV,\n\tresolveMaxSubagentDepth,\n\tresolveNestedConcurrency,\n\tSUBAGENT_MAX_DEPTH_ENV,\n} from \"./core/subagent-depth.js\";\nimport { printTimings, resetTimings, time } from \"./core/timings.js\";\nimport { createPluginLifecycleToolDefinitions } from \"./core/tools/plugins.js\";\nimport { createProposePluginToolDefinitions } from \"./core/tools/propose-plugin.js\";\nimport {\n\tbuildTaskMainPrompt,\n\tcreateTaskOutputToolDefinition,\n\tcreateTaskToolDefinition,\n} from \"./core/tools/subagent.js\";\nimport { createTodoWriteToolDefinition } from \"./core/tools/todo.js\";\nimport { WARM_SUBAGENTS_ENV } from \"./core/warm-subagent-pool-instance.js\";\nimport { runMigrations, showDeprecationWarnings } from \"./migrations.js\";\nimport { InteractiveMode, runPrintMode, runRpcMode } from \"./modes/index.js\";\nimport { ExtensionSelectorComponent } from \"./modes/interactive/components/extension-selector.js\";\nimport { initTheme, stopThemeWatcher } from \"./modes/interactive/theme/theme.js\";\nimport { handleConfigCommand, handlePackageCommand } from \"./package-manager-cli.js\";\nimport { handleResourcesCommand } from \"./resources-cli.js\";\nimport { isLocalPath } from \"./utils/paths.js\";\n\n/**\n * Read all content from piped stdin.\n * Returns undefined if stdin is a TTY (interactive terminal).\n */\nasync function readPipedStdin(): Promise<string | undefined> {\n\t// If stdin is a TTY, we're running interactively - don't read stdin\n\tif (process.stdin.isTTY) {\n\t\treturn undefined;\n\t}\n\n\treturn new Promise((resolve) => {\n\t\tlet data = \"\";\n\t\tprocess.stdin.setEncoding(\"utf8\");\n\t\tprocess.stdin.on(\"data\", (chunk) => {\n\t\t\tdata += chunk;\n\t\t});\n\t\tprocess.stdin.on(\"end\", () => {\n\t\t\tresolve(data.trim() || undefined);\n\t\t});\n\t\tprocess.stdin.resume();\n\t});\n}\n\nfunction collectSettingsDiagnostics(\n\tsettingsManager: SettingsManager,\n\tcontext: string,\n): AgentSessionRuntimeDiagnostic[] {\n\treturn settingsManager.drainErrors().map(({ scope, error }) => ({\n\t\ttype: \"warning\",\n\t\tmessage: `(${context}, ${scope} settings) ${error.message}`,\n\t}));\n}\n\nfunction reportDiagnostics(diagnostics: readonly AgentSessionRuntimeDiagnostic[]): void {\n\tfor (const diagnostic of diagnostics) {\n\t\tconst color = diagnostic.type === \"error\" ? chalk.red : diagnostic.type === \"warning\" ? chalk.yellow : chalk.dim;\n\t\tconst prefix = diagnostic.type === \"error\" ? \"Error: \" : diagnostic.type === \"warning\" ? \"Warning: \" : \"\";\n\t\tconsole.error(color(`${prefix}${diagnostic.message}`));\n\t}\n}\n\nfunction isTruthyEnvFlag(value: string | undefined): boolean {\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ntype AppMode = \"interactive\" | \"print\" | \"json\" | \"rpc\";\n\nfunction resolveAppMode(parsed: Args, stdinIsTTY: boolean): AppMode {\n\tif (parsed.mode === \"rpc\") {\n\t\treturn \"rpc\";\n\t}\n\tif (parsed.mode === \"json\") {\n\t\treturn \"json\";\n\t}\n\tif (parsed.print || !stdinIsTTY) {\n\t\treturn \"print\";\n\t}\n\treturn \"interactive\";\n}\n\nfunction toPrintOutputMode(appMode: AppMode): Exclude<Mode, \"rpc\"> {\n\treturn appMode === \"json\" ? \"json\" : \"text\";\n}\n\nasync function prepareInitialMessage(\n\tparsed: Args,\n\tautoResizeImages: boolean,\n\tstdinContent?: string,\n): Promise<{\n\tinitialMessage?: string;\n\tinitialImages?: ImageContent[];\n}> {\n\tif (parsed.fileArgs.length === 0) {\n\t\treturn buildInitialMessage({ parsed, stdinContent });\n\t}\n\n\tconst { text, images } = await processFileArguments(parsed.fileArgs, { autoResizeImages });\n\treturn buildInitialMessage({\n\t\tparsed,\n\t\tfileText: text,\n\t\tfileImages: images,\n\t\tstdinContent,\n\t});\n}\n\n/** Result from resolving a session argument */\ntype ResolvedSession =\n\t| { type: \"path\"; path: string } // Direct file path\n\t| { type: \"local\"; path: string } // Found in current project\n\t| { type: \"global\"; path: string; cwd: string } // Found in different project\n\t| { type: \"not_found\"; arg: string }; // Not found anywhere\n\n/**\n * Resolve a session argument to a file path.\n * If it looks like a path, use as-is. Otherwise try to match as session ID prefix.\n */\nasync function resolveSessionPath(sessionArg: string, cwd: string, sessionDir?: string): Promise<ResolvedSession> {\n\t// If it looks like a file path, use as-is\n\tif (sessionArg.includes(\"/\") || sessionArg.includes(\"\\\\\") || sessionArg.endsWith(\".jsonl\")) {\n\t\treturn { type: \"path\", path: sessionArg };\n\t}\n\n\t// Try to match as session ID in current project first\n\tconst localSessions = await SessionManager.list(cwd, sessionDir);\n\tconst localMatches = localSessions.filter((s) => s.id.startsWith(sessionArg));\n\n\tif (localMatches.length >= 1) {\n\t\treturn { type: \"local\", path: localMatches[0].path };\n\t}\n\n\t// Try global search across all projects\n\tconst allSessions = await SessionManager.listAll();\n\tconst globalMatches = allSessions.filter((s) => s.id.startsWith(sessionArg));\n\n\tif (globalMatches.length >= 1) {\n\t\tconst match = globalMatches[0];\n\t\treturn { type: \"global\", path: match.path, cwd: match.cwd };\n\t}\n\n\t// Not found anywhere\n\treturn { type: \"not_found\", arg: sessionArg };\n}\n\n/** Prompt user for yes/no confirmation */\nasync function promptConfirm(message: string): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst rl = createInterface({\n\t\t\tinput: process.stdin,\n\t\t\toutput: process.stdout,\n\t\t});\n\t\trl.question(`${message} [y/N] `, (answer) => {\n\t\t\trl.close();\n\t\t\tresolve(answer.toLowerCase() === \"y\" || answer.toLowerCase() === \"yes\");\n\t\t});\n\t});\n}\n\nfunction validateForkFlags(parsed: Args): void {\n\tif (!parsed.fork) return;\n\n\tconst conflictingFlags = [\n\t\tparsed.session ? \"--session\" : undefined,\n\t\tparsed.continue ? \"--continue\" : undefined,\n\t\tparsed.resume ? \"--resume\" : undefined,\n\t\tparsed.noSession ? \"--no-session\" : undefined,\n\t].filter((flag): flag is string => flag !== undefined);\n\n\tif (conflictingFlags.length > 0) {\n\t\tconsole.error(chalk.red(`Error: --fork cannot be combined with ${conflictingFlags.join(\", \")}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nfunction forkSessionOrExit(sourcePath: string, cwd: string, sessionDir?: string): SessionManager {\n\ttry {\n\t\treturn SessionManager.forkFrom(sourcePath, cwd, sessionDir);\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nasync function createSessionManager(\n\tparsed: Args,\n\tcwd: string,\n\tsessionDir: string | undefined,\n\tsettingsManager: SettingsManager,\n): Promise<SessionManager> {\n\tif (parsed.noSession) {\n\t\treturn SessionManager.inMemory();\n\t}\n\n\tif (parsed.fork) {\n\t\tconst resolved = await resolveSessionPath(parsed.fork, cwd, sessionDir);\n\n\t\tswitch (resolved.type) {\n\t\t\tcase \"path\":\n\t\t\tcase \"local\":\n\t\t\tcase \"global\":\n\t\t\t\treturn forkSessionOrExit(resolved.path, cwd, sessionDir);\n\n\t\t\tcase \"not_found\":\n\t\t\t\tconsole.error(chalk.red(`No session found matching '${resolved.arg}'`));\n\t\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tif (parsed.session) {\n\t\tconst resolved = await resolveSessionPath(parsed.session, cwd, sessionDir);\n\n\t\tswitch (resolved.type) {\n\t\t\tcase \"path\":\n\t\t\tcase \"local\":\n\t\t\t\treturn SessionManager.open(resolved.path, sessionDir);\n\n\t\t\tcase \"global\": {\n\t\t\t\tconsole.log(chalk.yellow(`Session found in different project: ${resolved.cwd}`));\n\t\t\t\tconst shouldFork = await promptConfirm(\"Fork this session into current directory?\");\n\t\t\t\tif (!shouldFork) {\n\t\t\t\t\tconsole.log(chalk.dim(\"Aborted.\"));\n\t\t\t\t\tprocess.exit(0);\n\t\t\t\t}\n\t\t\t\treturn forkSessionOrExit(resolved.path, cwd, sessionDir);\n\t\t\t}\n\n\t\t\tcase \"not_found\":\n\t\t\t\tconsole.error(chalk.red(`No session found matching '${resolved.arg}'`));\n\t\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tif (parsed.resume) {\n\t\tinitTheme(settingsManager.getTheme(), true);\n\t\ttry {\n\t\t\tconst selectedPath = await selectSession(\n\t\t\t\t(onProgress) => SessionManager.list(cwd, sessionDir, onProgress),\n\t\t\t\tSessionManager.listAll,\n\t\t\t);\n\t\t\tif (!selectedPath) {\n\t\t\t\tconsole.log(chalk.dim(\"No session selected\"));\n\t\t\t\tprocess.exit(0);\n\t\t\t}\n\t\t\treturn SessionManager.open(selectedPath, sessionDir);\n\t\t} finally {\n\t\t\tstopThemeWatcher();\n\t\t}\n\t}\n\n\tif (parsed.continue) {\n\t\treturn SessionManager.continueRecent(cwd, sessionDir);\n\t}\n\n\treturn SessionManager.create(cwd, sessionDir);\n}\n\nfunction buildSessionOptions(\n\tparsed: Args,\n\tscopedModels: ScopedModel[],\n\thasExistingSession: boolean,\n\tmodelRegistry: ModelRegistry,\n\tsettingsManager: SettingsManager,\n): {\n\toptions: CreateAgentSessionOptions;\n\tcliThinkingFromModel: boolean;\n\tdiagnostics: AgentSessionRuntimeDiagnostic[];\n} {\n\tconst options: CreateAgentSessionOptions = {};\n\tconst diagnostics: AgentSessionRuntimeDiagnostic[] = [];\n\tlet cliThinkingFromModel = false;\n\n\t// Model from CLI\n\t// - supports --provider <name> --model <pattern>\n\t// - supports --model <provider>/<pattern>\n\tif (parsed.model) {\n\t\tconst resolved = resolveCliModel({\n\t\t\tcliProvider: parsed.provider,\n\t\t\tcliModel: parsed.model,\n\t\t\tmodelRegistry,\n\t\t});\n\t\tif (resolved.warning) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: resolved.warning });\n\t\t}\n\t\tif (resolved.error) {\n\t\t\tdiagnostics.push({ type: \"error\", message: resolved.error });\n\t\t}\n\t\tif (resolved.model) {\n\t\t\toptions.model = resolved.model;\n\t\t\t// Allow \"--model <pattern>:<thinking>\" as a shorthand.\n\t\t\t// Explicit --thinking still takes precedence (applied later).\n\t\t\tif (!parsed.thinking && resolved.thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = resolved.thinkingLevel;\n\t\t\t\tcliThinkingFromModel = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!options.model && scopedModels.length > 0 && !hasExistingSession) {\n\t\t// Check if saved default is in scoped models - use it if so, otherwise first scoped model\n\t\tconst savedProvider = settingsManager.getDefaultProvider();\n\t\tconst savedModelId = settingsManager.getDefaultModel();\n\t\tconst savedModel = savedProvider && savedModelId ? modelRegistry.find(savedProvider, savedModelId) : undefined;\n\t\tconst savedInScope = savedModel ? scopedModels.find((sm) => modelsAreEqual(sm.model, savedModel)) : undefined;\n\n\t\tif (savedInScope) {\n\t\t\toptions.model = savedInScope.model;\n\t\t\t// Use thinking level from scoped model config if explicitly set\n\t\t\tif (!parsed.thinking && savedInScope.thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = savedInScope.thinkingLevel;\n\t\t\t}\n\t\t} else {\n\t\t\toptions.model = scopedModels[0].model;\n\t\t\t// Use thinking level from first scoped model if explicitly set\n\t\t\tif (!parsed.thinking && scopedModels[0].thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = scopedModels[0].thinkingLevel;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Thinking level from CLI (takes precedence over scoped model thinking levels set above)\n\tif (parsed.thinking) {\n\t\toptions.thinkingLevel = parsed.thinking;\n\t}\n\n\t// Scoped models for Ctrl+P cycling\n\t// Keep thinking level undefined when not explicitly set in the model pattern.\n\t// Undefined means \"inherit current session thinking level\" during cycling.\n\tif (scopedModels.length > 0) {\n\t\toptions.scopedModels = scopedModels.map((sm) => ({\n\t\t\tmodel: sm.model,\n\t\t\tthinkingLevel: sm.thinkingLevel,\n\t\t}));\n\t}\n\n\t// API key from CLI - set in authStorage\n\t// (handled by caller before createAgentSession)\n\n\t// Tools\n\tif (parsed.noTools) {\n\t\toptions.noTools = \"all\";\n\t} else if (parsed.noBuiltinTools) {\n\t\toptions.noTools = \"builtin\";\n\t}\n\tif (parsed.tools) {\n\t\toptions.tools = [...parsed.tools];\n\t}\n\tif (parsed.disallowedTools) {\n\t\toptions.disallowedTools = [...parsed.disallowedTools];\n\t}\n\t// Web tools (webfetch + websearch): opt-in via --enable-webtools flag or the\n\t// enableWebTools setting. They are registered as base tools but inactive by\n\t// default; this adds them to the default active set.\n\tif (parsed.enableWebTools ?? settingsManager.getEnableWebTools()) {\n\t\toptions.enableWebTools = true;\n\t}\n\t// Browser tools (browser_run + browser_continue): opt-in via --enable-browsertools\n\t// flag or the enableBrowserTools setting. Registered as base tools but inactive by\n\t// default; this adds them to the default active set.\n\tif (parsed.enableBrowserTools ?? settingsManager.getEnableBrowserTools()) {\n\t\toptions.enableBrowserTools = true;\n\t}\n\t// Live preview for browser_run: defaults the streamed viewer on and auto-opens\n\t// it. Applied as a runtime settings override so the session's tool factory reads\n\t// it via the shared settingsManager (no extra option plumbing).\n\tif (parsed.enableBrowserLivePreview) {\n\t\tsettingsManager.applyOverrides({ enableBrowserLivePreview: true });\n\t}\n\t// Document tools (DocRead/DocEdit/DocWrite + the DocScan/DocGrep/DocPeek\n\t// discovery loop): opt-in via --enable-filetools flag or the enableFileTools\n\t// setting. Registered as base tools but inactive by default; this adds them to\n\t// the default active set.\n\tif (parsed.enableFileTools ?? settingsManager.getEnableFileTools()) {\n\t\toptions.enableFileTools = true;\n\t\t// DocRead/DocEdit/DocWrite drive a lossless id-based extract -> patch ->\n\t\t// reconstruct flow that depends on precise, well-formed tool calls. Models\n\t\t// that are weak at tool calling tend to mangle the id-based patches and\n\t\t// corrupt documents, so surface a one-time heads-up when these are enabled.\n\t\tdiagnostics.push({\n\t\t\ttype: \"warning\",\n\t\t\tmessage:\n\t\t\t\t\"Document tools (DocRead/DocEdit/DocWrite) are enabled. They require precise, id-based patches; \" +\n\t\t\t\t\"use a model that is strong at tool calling, or these edits can corrupt files.\",\n\t\t});\n\t}\n\n\t// Optional Task (subagent) tool: opt-in via --enable-subagents flag or the enableSubagent setting.\n\t// Registered as a custom tool; respects --tools/--no-tools allowlists like any other tool.\n\t//\n\t// Nesting is bounded by the tree-wide cap (maxSubagentDepth, default 1). The root\n\t// seeds the cap into the environment so every descendant agrees on one value; the\n\t// Task tool is registered only while this process's depth is below that cap. At the\n\t// default cap this reproduces the original guard exactly: subagents (depth >= 1) get\n\t// no Task tool and cannot recursively dispatch.\n\tconst isSubagentChild = parsed.taskId !== undefined;\n\tif (process.env[SUBAGENT_MAX_DEPTH_ENV] === undefined) {\n\t\t// The root seeds the tree-wide cap; the --max-subagent-depth flag overrides the\n\t\t// setting. resolveMaxSubagentDepth clamps it to the supported range so the seeded\n\t\t// env (and everything that inherits it) carries a sane value. Descendants inherit\n\t\t// it via the environment (env already set => keep it).\n\t\tprocess.env[SUBAGENT_MAX_DEPTH_ENV] = String(\n\t\t\tresolveMaxSubagentDepth(parsed.maxSubagentDepth ?? settingsManager.getMaxSubagentDepth()),\n\t\t);\n\t}\n\tif (process.env[NESTED_CONCURRENCY_ENV] === undefined) {\n\t\t// Seed the nested-pool concurrency from settings so descendants agree on one value.\n\t\tprocess.env[NESTED_CONCURRENCY_ENV] = String(\n\t\t\tresolveNestedConcurrency(settingsManager.getNestedSubagentConcurrency()),\n\t\t);\n\t}\n\t// Scoped delegation: --delegate-allow is the authoritative restriction for this\n\t// process. Set it from the flag, or clear any inherited value so a restricted\n\t// parent's scope never leaks into a child that wasn't given its own.\n\tif (parsed.delegateAllow && parsed.delegateAllow.length > 0) {\n\t\tprocess.env[DELEGATE_ALLOW_ENV] = parsed.delegateAllow.join(\",\");\n\t} else {\n\t\tdelete process.env[DELEGATE_ALLOW_ENV];\n\t}\n\tif (canSpawnSubagent() && (parsed.subagent ?? settingsManager.getEnableSubagent())) {\n\t\toptions.customTools = [\n\t\t\t...(options.customTools ?? []),\n\t\t\tcreateTaskToolDefinition(),\n\t\t\tcreateTaskOutputToolDefinition(),\n\t\t];\n\t\t// Warm subagents (experimental): dispatch eligible foreground subagents on\n\t\t// reused RPC workers to skip the cold-boot. Root-only — the Task tool exists\n\t\t// only where canSpawnSubagent holds and never inside a spawned child — and\n\t\t// carried via env so the Task tool reads it without threading a setting.\n\t\tif (!isSubagentChild && (parsed.warmSubagents ?? settingsManager.getWarmSubagents())) {\n\t\t\tprocess.env[WARM_SUBAGENTS_ENV] = \"1\";\n\t\t}\n\t}\n\n\t// Optional TodoWrite tool: opt-in via --enable-todowrite flag or the\n\t// enableTodoWrite setting. Never registered inside a spawned subagent child —\n\t// its todos would otherwise leak into the parent's \"main\" task group in the pane.\n\tif (!isSubagentChild && (parsed.todoWrite ?? settingsManager.getEnableTodoWrite())) {\n\t\toptions.customTools = [...(options.customTools ?? []), createTodoWriteToolDefinition()];\n\t}\n\n\t// Deferred MCP tool schemas (opt-in): set for the top-level agent only. Subagent\n\t// children clear this env (see subagent-pool) so a child that needs MCP resolves\n\t// its allowlisted tools eagerly at dispatch.\n\tif (!isSubagentChild && settingsManager.getDeferMcpSchemas()) {\n\t\tprocess.env[DEFER_MCP_SCHEMAS_ENV] = \"1\";\n\t}\n\n\t// Plugin lifecycle tools (SearchPlugins, InstallPlugin, ...). Top-level agent\n\t// only: these are capability-acquisition tools and must never be available to\n\t// a spawned subagent child (privilege-amplification guardrail, spec §3).\n\tif (!isSubagentChild && settingsManager.getEnablePluginTools()) {\n\t\toptions.customTools = [\n\t\t\t...(options.customTools ?? []),\n\t\t\t...createPluginLifecycleToolDefinitions(),\n\t\t\t...createProposePluginToolDefinitions(),\n\t\t];\n\t}\n\n\treturn { options, cliThinkingFromModel, diagnostics };\n}\n\nfunction resolveCliPaths(cwd: string, paths: string[] | undefined): string[] | undefined {\n\treturn paths?.map((value) => (isLocalPath(value) ? resolve(cwd, value) : value));\n}\n\nasync function promptForMissingSessionCwd(\n\tissue: SessionCwdIssue,\n\tsettingsManager: SettingsManager,\n): Promise<string | undefined> {\n\tinitTheme(settingsManager.getTheme());\n\tsetKeybindings(KeybindingsManager.create());\n\n\treturn new Promise((resolve) => {\n\t\tconst ui = new TUI(new ProcessTerminal(), settingsManager.getShowHardwareCursor());\n\t\tui.setClearOnShrink(settingsManager.getClearOnShrink());\n\n\t\tlet settled = false;\n\t\tconst finish = (result: string | undefined) => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tui.stop();\n\t\t\tresolve(result);\n\t\t};\n\n\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\tformatMissingSessionCwdPrompt(issue),\n\t\t\t[\"Continue\", \"Cancel\"],\n\t\t\t(option) => finish(option === \"Continue\" ? issue.fallbackCwd : undefined),\n\t\t\t() => finish(undefined),\n\t\t\t{ tui: ui },\n\t\t);\n\t\tui.addChild(selector);\n\t\tui.setFocus(selector);\n\t\tui.start();\n\t});\n}\n\nexport interface MainOptions {\n\textensionFactories?: ExtensionFactory[];\n}\n\nexport async function main(args: string[], options?: MainOptions) {\n\tresetTimings();\n\tconst offlineMode = args.includes(\"--offline\") || isTruthyEnvFlag(process.env.HOOCODE_OFFLINE);\n\tif (offlineMode) {\n\t\tprocess.env.HOOCODE_OFFLINE = \"1\";\n\t\tprocess.env.HOOCODE_SKIP_VERSION_CHECK = \"1\";\n\t}\n\n\tif (await handlePackageCommand(args)) {\n\t\treturn;\n\t}\n\n\tif (await handleConfigCommand(args)) {\n\t\treturn;\n\t}\n\n\tif (await handleResourcesCommand(args)) {\n\t\treturn;\n\t}\n\n\tconst parsed = parseArgs(args);\n\tif (parsed.diagnostics.length > 0) {\n\t\tfor (const d of parsed.diagnostics) {\n\t\t\tconst color = d.type === \"error\" ? chalk.red : chalk.yellow;\n\t\t\tconsole.error(color(`${d.type === \"error\" ? \"Error\" : \"Warning\"}: ${d.message}`));\n\t\t}\n\t\tif (parsed.diagnostics.some((d) => d.type === \"error\")) {\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\ttime(\"parseArgs\");\n\tlet appMode = resolveAppMode(parsed, process.stdin.isTTY);\n\tconst shouldTakeOverStdout = appMode !== \"interactive\";\n\tif (shouldTakeOverStdout) {\n\t\ttakeOverStdout();\n\t}\n\n\tif (parsed.version) {\n\t\tconsole.log(VERSION);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.export) {\n\t\tlet result: string;\n\t\ttry {\n\t\t\tconst outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;\n\t\t\tresult = await exportFromFile(parsed.export, outputPath);\n\t\t} catch (error: unknown) {\n\t\t\tconst message = error instanceof Error ? error.message : \"Failed to export session\";\n\t\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tconsole.log(`Exported to: ${result}`);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.mode === \"rpc\" && parsed.fileArgs.length > 0) {\n\t\tconsole.error(chalk.red(\"Error: @file arguments are not supported in RPC mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\tvalidateForkFlags(parsed);\n\n\t// Run migrations (pass cwd for project-local migrations)\n\tconst { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(process.cwd());\n\ttime(\"runMigrations\");\n\n\tconst cwd = process.cwd();\n\tconst agentDir = getAgentDir();\n\tconst startupSettingsManager = SettingsManager.create(cwd, agentDir);\n\treportDiagnostics(collectSettingsDiagnostics(startupSettingsManager, \"startup session lookup\"));\n\n\t// Decide the final runtime cwd before creating cwd-bound runtime services.\n\t// --session and --resume may select a session from another project, so project-local\n\t// settings, resources, provider registrations, and models must be resolved only after\n\t// the target session cwd is known. The startup-cwd settings manager is used only for\n\t// sessionDir lookup during session selection.\n\tconst envSessionDir = process.env[ENV_SESSION_DIR];\n\tconst sessionDir =\n\t\tparsed.sessionDir ??\n\t\t(envSessionDir ? expandTildePath(envSessionDir) : undefined) ??\n\t\tstartupSettingsManager.getSessionDir();\n\tlet sessionManager = await createSessionManager(parsed, cwd, sessionDir, startupSettingsManager);\n\tconst missingSessionCwdIssue = getMissingSessionCwdIssue(sessionManager, cwd);\n\tif (missingSessionCwdIssue) {\n\t\tif (appMode === \"interactive\") {\n\t\t\tconst selectedCwd = await promptForMissingSessionCwd(missingSessionCwdIssue, startupSettingsManager);\n\t\t\tif (!selectedCwd) {\n\t\t\t\tprocess.exit(0);\n\t\t\t}\n\t\t\tsessionManager = SessionManager.open(missingSessionCwdIssue.sessionFile!, sessionDir, selectedCwd);\n\t\t} else {\n\t\t\tconsole.error(chalk.red(new MissingSessionCwdError(missingSessionCwdIssue).message));\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\ttime(\"createSessionManager\");\n\n\tconst resolvedExtensionPaths = resolveCliPaths(cwd, parsed.extensions);\n\tconst resolvedSkillPaths = resolveCliPaths(cwd, parsed.skills);\n\tconst resolvedAgentPaths = resolveCliPaths(cwd, parsed.agents);\n\tconst resolvedPromptTemplatePaths = resolveCliPaths(cwd, parsed.promptTemplates);\n\tconst resolvedSlashCommandPaths = resolveCliPaths(cwd, parsed.slashCommands);\n\tconst resolvedThemePaths = resolveCliPaths(cwd, parsed.themes);\n\n\t// Populate the module-level CLI agent store before any session is created.\n\tsetAgentCliPaths(resolvedAgentPaths ?? []);\n\n\t// Synthetic factory: feed CLI --mode-path values into the extension runtime\n\t// so hoo-core (and any other extension that reads pi.getModeSearchPaths)\n\t// sees them alongside extension-registered dirs.\n\tconst cliModePaths = parsed.modePaths ?? [];\n\tconst cliResourcePathFactories: ExtensionFactory[] =\n\t\tcliModePaths.length === 0\n\t\t\t? []\n\t\t\t: [\n\t\t\t\t\tObject.assign(\n\t\t\t\t\t\t(pi: ExtensionAPI) => {\n\t\t\t\t\t\t\tfor (const p of cliModePaths) pi.addModeSearchPath(p);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{ internal: true },\n\t\t\t\t\t),\n\t\t\t\t];\n\tconst allExtensionFactories: ExtensionFactory[] = [\n\t\t...cliResourcePathFactories,\n\t\t...(options?.extensionFactories ?? []),\n\t];\n\tconst authStorage = AuthStorage.create();\n\t// A spawned subagent (run with a task id) is a single-shot, non-interactive\n\t// process: it renders no TUI and its prompt is a plain task, never a slash\n\t// command. Themes, slash commands, and prompt templates are dead weight in that\n\t// path, so skip loading them to trim the child's cold-boot cost. Skills, context\n\t// files, and extensions (which carry the core tools) are kept — they affect the\n\t// subagent's actual work.\n\tconst isSubagentBoot = parsed.taskId !== undefined;\n\tconst createRuntime: CreateAgentSessionRuntimeFactory = async ({\n\t\tcwd,\n\t\tagentDir,\n\t\tsessionManager,\n\t\tsessionStartEvent,\n\t}) => {\n\t\tconst services = await createAgentSessionServices({\n\t\t\tcwd,\n\t\t\tagentDir,\n\t\t\tauthStorage,\n\t\t\textensionFlagValues: parsed.unknownFlags,\n\t\t\tresourceLoaderOptions: {\n\t\t\t\tadditionalExtensionPaths: resolvedExtensionPaths,\n\t\t\t\tadditionalSkillPaths: resolvedSkillPaths,\n\t\t\t\tadditionalPromptTemplatePaths: resolvedPromptTemplatePaths,\n\t\t\t\tadditionalSlashCommandPaths: resolvedSlashCommandPaths,\n\t\t\t\tadditionalThemePaths: resolvedThemePaths,\n\t\t\t\tnoExtensions: parsed.noExtensions,\n\t\t\t\tnoSkills: parsed.noSkills,\n\t\t\t\tnoPromptTemplates: parsed.noPromptTemplates || isSubagentBoot,\n\t\t\t\tnoSlashCommands: parsed.noSlashCommands || isSubagentBoot,\n\t\t\t\tnoThemes: parsed.noThemes || isSubagentBoot,\n\t\t\t\tnoContextFiles: parsed.noContextFiles,\n\t\t\t\tsystemPrompt: parsed.systemPrompt,\n\t\t\t\textensionFactories: allExtensionFactories,\n\t\t\t},\n\t\t});\n\t\tconst { settingsManager, modelRegistry, resourceLoader } = services;\n\t\tconst diagnostics: AgentSessionRuntimeDiagnostic[] = [\n\t\t\t...services.diagnostics,\n\t\t\t...collectSettingsDiagnostics(settingsManager, \"runtime creation\"),\n\t\t\t...resourceLoader.getExtensions().errors.map(({ path, error }) => ({\n\t\t\t\ttype: \"error\" as const,\n\t\t\t\tmessage: `Failed to load extension \"${path}\": ${error}`,\n\t\t\t})),\n\t\t];\n\n\t\t// When subagent tooling is enabled, append the main session subagent instructions.\n\t\tif (parsed.subagent ?? settingsManager.getEnableSubagent()) {\n\t\t\tresourceLoader.addAppendSystemPrompt(buildTaskMainPrompt());\n\t\t}\n\n\t\tconst modelPatterns = parsed.models ?? settingsManager.getEnabledModels();\n\t\tconst scopedModels =\n\t\t\tmodelPatterns && modelPatterns.length > 0 ? await resolveModelScope(modelPatterns, modelRegistry) : [];\n\t\tconst {\n\t\t\toptions: sessionOptions,\n\t\t\tcliThinkingFromModel,\n\t\t\tdiagnostics: sessionOptionDiagnostics,\n\t\t} = buildSessionOptions(\n\t\t\tparsed,\n\t\t\tscopedModels,\n\t\t\tsessionManager.buildSessionContext().messages.length > 0,\n\t\t\tmodelRegistry,\n\t\t\tsettingsManager,\n\t\t);\n\t\tdiagnostics.push(...sessionOptionDiagnostics);\n\n\t\tif (parsed.apiKey) {\n\t\t\tif (!sessionOptions.model) {\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\ttype: \"error\",\n\t\t\t\t\tmessage: \"--api-key requires a model to be specified via --model, --provider/--model, or --models\",\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tauthStorage.setRuntimeApiKey(sessionOptions.model.provider, parsed.apiKey);\n\t\t\t}\n\t\t}\n\n\t\tconst created = await createAgentSessionFromServices({\n\t\t\tservices,\n\t\t\tsessionManager,\n\t\t\tsessionStartEvent,\n\t\t\tmodel: sessionOptions.model,\n\t\t\tthinkingLevel: sessionOptions.thinkingLevel,\n\t\t\tscopedModels: sessionOptions.scopedModels,\n\t\t\ttools: sessionOptions.tools,\n\t\t\tnoTools: sessionOptions.noTools,\n\t\t\tcustomTools: sessionOptions.customTools,\n\t\t\tenableWebTools: sessionOptions.enableWebTools,\n\t\t\tenableBrowserTools: sessionOptions.enableBrowserTools,\n\t\t\tenableFileTools: sessionOptions.enableFileTools,\n\t\t});\n\t\tconst cliThinkingOverride = parsed.thinking !== undefined || cliThinkingFromModel;\n\t\tif (created.session.model && cliThinkingOverride) {\n\t\t\tcreated.session.setThinkingLevel(created.session.thinkingLevel);\n\t\t}\n\n\t\treturn {\n\t\t\t...created,\n\t\t\tservices,\n\t\t\tdiagnostics,\n\t\t};\n\t};\n\ttime(\"createRuntime\");\n\tconst runtime = await createAgentSessionRuntime(createRuntime, {\n\t\tcwd: sessionManager.getCwd(),\n\t\tagentDir,\n\t\tsessionManager,\n\t});\n\tconst { services, session, modelFallbackMessage } = runtime;\n\tconst { settingsManager, modelRegistry, resourceLoader } = services;\n\n\tif (parsed.help) {\n\t\tconst extensionFlags = resourceLoader\n\t\t\t.getExtensions()\n\t\t\t.extensions.flatMap((extension) => Array.from(extension.flags.values()));\n\t\tprintHelp(extensionFlags);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.listModels !== undefined) {\n\t\tconst searchPattern = typeof parsed.listModels === \"string\" ? parsed.listModels : undefined;\n\t\tawait listModels(modelRegistry, searchPattern);\n\t\tprocess.exit(0);\n\t}\n\n\t// Read piped stdin content (if any) - skip for RPC mode which uses stdin for JSON-RPC\n\tlet stdinContent: string | undefined;\n\tif (appMode !== \"rpc\") {\n\t\tstdinContent = await readPipedStdin();\n\t\tif (stdinContent !== undefined && appMode === \"interactive\") {\n\t\t\tappMode = \"print\";\n\t\t}\n\t}\n\ttime(\"readPipedStdin\");\n\n\tconst { initialMessage, initialImages } = await prepareInitialMessage(\n\t\tparsed,\n\t\tsettingsManager.getImageAutoResize(),\n\t\tstdinContent,\n\t);\n\ttime(\"prepareInitialMessage\");\n\tinitTheme(settingsManager.getTheme(), appMode === \"interactive\");\n\ttime(\"initTheme\");\n\n\t// Show deprecation warnings in interactive mode\n\tif (appMode === \"interactive\" && deprecationWarnings.length > 0) {\n\t\tawait showDeprecationWarnings(deprecationWarnings);\n\t}\n\n\tconst scopedModels = [...session.scopedModels];\n\ttime(\"resolveModelScope\");\n\treportDiagnostics(runtime.diagnostics);\n\tif (runtime.diagnostics.some((diagnostic) => diagnostic.type === \"error\")) {\n\t\tprocess.exit(1);\n\t}\n\ttime(\"createAgentSession\");\n\n\tif (appMode !== \"interactive\" && !session.model) {\n\t\tconsole.error(chalk.red(formatNoModelsAvailableMessage()));\n\t\tprocess.exit(1);\n\t}\n\n\tconst startupBenchmark = isTruthyEnvFlag(process.env.HOOCODE_STARTUP_BENCHMARK);\n\tif (startupBenchmark && appMode !== \"interactive\") {\n\t\tconsole.error(chalk.red(\"Error: HOOCODE_STARTUP_BENCHMARK only supports interactive mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\tif (appMode === \"rpc\") {\n\t\tprintTimings();\n\t\tawait runRpcMode(runtime);\n\t} else if (appMode === \"interactive\") {\n\t\tif (scopedModels.length > 0 && (parsed.verbose || !settingsManager.getQuietStartup())) {\n\t\t\tconst modelList = scopedModels\n\t\t\t\t.map((sm) => {\n\t\t\t\t\tconst thinkingStr = sm.thinkingLevel ? `:${sm.thinkingLevel}` : \"\";\n\t\t\t\t\treturn `${sm.model.id}${thinkingStr}`;\n\t\t\t\t})\n\t\t\t\t.join(\", \");\n\t\t\tconsole.log(chalk.dim(`Model scope: ${modelList} ${chalk.gray(\"(Ctrl+P to cycle)\")}`));\n\t\t}\n\n\t\tconst interactiveMode = new InteractiveMode(runtime, {\n\t\t\tmigratedProviders,\n\t\t\tmodelFallbackMessage,\n\t\t\tinitialMessage,\n\t\t\tinitialImages,\n\t\t\tinitialMessages: parsed.messages,\n\t\t\tverbose: parsed.verbose,\n\t\t});\n\n\t\t// Optional hooteams bridge. `--team auto` discovers a config, spawns a\n\t\t// local hooteams child on a free port, and proceeds as if its URL had\n\t\t// been passed; the child is reaped on exit (clean or signal) via a\n\t\t// process \"exit\" hook plus the explicit stop below.\n\t\tlet autoTeam: { url: string; stop(): Promise<void> } | undefined;\n\t\tlet teamUrl = parsed.team;\n\t\tif (teamUrl === \"auto\") {\n\t\t\tconst { startAutoTeam } = await import(\"./core/team-auto.js\");\n\t\t\ttry {\n\t\t\t\tautoTeam = await startAutoTeam(process.cwd(), { log: (message) => console.log(chalk.dim(message)) });\n\t\t\t\tteamUrl = autoTeam.url;\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(chalk.red(error instanceof Error ? error.message : String(error)));\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t}\n\n\t\t// Team mirror + client. Fire-and-forget: connect failures and drops warn\n\t\t// in the background and never block the main agent. Warnings go through\n\t\t// the chat, not console.error: a raw stderr write while the TUI owns the\n\t\t// screen scribbles over the render and can leave the editor looking\n\t\t// frozen. The same connection powers team focus / nudge / attach.\n\t\tlet teamView: { stop(): void } | undefined;\n\t\tif (teamUrl) {\n\t\t\tconst { connectTeamView } = await import(\"./core/team-view.js\");\n\t\t\tconst teamClient = connectTeamView(teamUrl, {\n\t\t\t\twarn: (message) => interactiveMode.showWarning(message),\n\t\t\t});\n\t\t\tteamView = teamClient;\n\t\t\tinteractiveMode.attachTeamClient(teamClient);\n\t\t}\n\t\tif (startupBenchmark) {\n\t\t\tawait interactiveMode.init();\n\t\t\ttime(\"interactiveMode.init\");\n\t\t\tprintTimings();\n\t\t\tteamView?.stop();\n\t\t\tawait autoTeam?.stop();\n\t\t\tinteractiveMode.stop();\n\t\t\tstopThemeWatcher();\n\t\t\tif (process.stdout.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stdout.once(\"drain\", resolve));\n\t\t\t}\n\t\t\tif (process.stderr.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stderr.once(\"drain\", resolve));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tprintTimings();\n\t\ttry {\n\t\t\tawait interactiveMode.run();\n\t\t} finally {\n\t\t\tteamView?.stop();\n\t\t\tawait autoTeam?.stop();\n\t\t}\n\t} else {\n\t\tprintTimings();\n\t\tconst exitCode = await runPrintMode(runtime, {\n\t\t\tmode: toPrintOutputMode(appMode),\n\t\t\tmessages: parsed.messages,\n\t\t\tinitialMessage,\n\t\t\tinitialImages,\n\t\t\ttaskId: parsed.taskId,\n\t\t\tmaxTurns: parsed.maxTurns,\n\t\t});\n\t\tstopThemeWatcher();\n\t\trestoreStdout();\n\t\tif (exitCode !== 0) {\n\t\t\tprocess.exitCode = exitCode;\n\t\t}\n\t\t// Spawned subagents (run with a task id) must exit promptly once their work is\n\t\t// done and result.json is written. The child's runtime can leave handles open\n\t\t// (e.g. MCP client connections) that keep the event loop alive, so a natural\n\t\t// exit may never happen. When that occurs the parent lifeguard SIGKILLs the idle\n\t\t// child at the 60s heartbeat threshold and misreports an already-completed task\n\t\t// as \"stalled\". Force a clean exit after draining output to avoid that false stall.\n\t\tconst ranAsSubagent = typeof parsed.taskId === \"string\" && parsed.taskId.length > 0;\n\t\tif (ranAsSubagent) {\n\t\t\tif (process.stdout.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stdout.once(\"drain\", resolve));\n\t\t\t}\n\t\t\tif (process.stderr.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stderr.once(\"drain\", resolve));\n\t\t\t}\n\t\t\tprocess.exit(exitCode);\n\t\t}\n\t\treturn;\n\t}\n}\n"]}