@corbat-tech/coco 2.24.1 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -81,6 +81,33 @@ var init_version = __esm({
81
81
  VERSION = findPackageJson().version;
82
82
  }
83
83
  });
84
+
85
+ // src/config/schema.ts
86
+ var schema_exports = {};
87
+ __export(schema_exports, {
88
+ CocoConfigSchema: () => CocoConfigSchema,
89
+ GitHubConfigSchema: () => GitHubConfigSchema,
90
+ IntegrationsConfigSchema: () => IntegrationsConfigSchema,
91
+ MCPConfigSchema: () => MCPConfigSchema,
92
+ MCPServerConfigEntrySchema: () => MCPServerConfigEntrySchema,
93
+ PersistenceConfigSchema: () => PersistenceConfigSchema,
94
+ ProjectConfigSchema: () => ProjectConfigSchema,
95
+ ProviderConfigSchema: () => ProviderConfigSchema,
96
+ QualityConfigSchema: () => QualityConfigSchema,
97
+ ShipConfigSchema: () => ShipConfigSchema,
98
+ SkillsConfigSchema: () => SkillsConfigSchema,
99
+ StackConfigSchema: () => StackConfigSchema,
100
+ ToolsConfigSchema: () => ToolsConfigSchema,
101
+ createDefaultConfigObject: () => createDefaultConfigObject,
102
+ validateConfig: () => validateConfig
103
+ });
104
+ function validateConfig(config) {
105
+ const result = CocoConfigSchema.safeParse(config);
106
+ if (result.success) {
107
+ return { success: true, data: result.data };
108
+ }
109
+ return { success: false, error: result.error };
110
+ }
84
111
  function createDefaultConfigObject(projectName, language = "typescript") {
85
112
  return {
86
113
  project: {
@@ -200,7 +227,15 @@ var init_schema = __esm({
200
227
  enabled: z.boolean().default(true),
201
228
  configFile: z.string().optional(),
202
229
  // Path to external MCP config file
203
- servers: z.array(MCPServerConfigEntrySchema).default([])
230
+ servers: z.array(MCPServerConfigEntrySchema).default([]),
231
+ /** Default timeout for MCP requests in milliseconds */
232
+ defaultTimeout: z.number().min(1e3).default(6e4).optional(),
233
+ /** Auto-discover MCP servers from well-known locations */
234
+ autoDiscover: z.boolean().default(true).optional(),
235
+ /** Log level for MCP operations */
236
+ logLevel: z.enum(["debug", "info", "warn", "error"]).default("info").optional(),
237
+ /** Path to custom servers directory */
238
+ customServersPath: z.string().optional()
204
239
  });
205
240
  ToolsConfigSchema = z.object({
206
241
  webSearch: z.object({
@@ -243,8 +278,12 @@ var init_schema = __esm({
243
278
  enabled: z.boolean().default(true),
244
279
  /** Override global skills directory */
245
280
  globalDir: z.string().optional(),
281
+ /** Override global skills directories (preferred over globalDir) */
282
+ globalDirs: z.array(z.string()).optional(),
246
283
  /** Override project skills directory */
247
284
  projectDir: z.string().optional(),
285
+ /** Override project skills directories (preferred over projectDir) */
286
+ projectDirs: z.array(z.string()).optional(),
248
287
  /** Auto-activate skills based on user messages */
249
288
  autoActivate: z.boolean().default(true),
250
289
  /** Maximum concurrent active markdown skills */
@@ -445,7 +484,7 @@ var init_errors = __esm({
445
484
  };
446
485
  }
447
486
  });
448
- var COCO_HOME, CONFIG_PATHS;
487
+ var COCO_HOME, CONFIG_PATHS, LEGACY_PATHS;
449
488
  var init_paths = __esm({
450
489
  "src/config/paths.ts"() {
451
490
  COCO_HOME = join(homedir(), ".coco");
@@ -475,12 +514,16 @@ var init_paths = __esm({
475
514
  /** Search index directory: ~/.coco/search-index/ */
476
515
  searchIndex: join(COCO_HOME, "search-index"),
477
516
  /** Global skills directory: ~/.coco/skills/ */
478
- skills: join(COCO_HOME, "skills")
517
+ skills: join(COCO_HOME, "skills"),
518
+ /** MCP server registry: ~/.coco/mcp.json */
519
+ mcpRegistry: join(COCO_HOME, "mcp.json")
479
520
  };
480
- ({
521
+ LEGACY_PATHS = {
481
522
  /** Old config location */
482
- oldConfig: join(homedir(), ".config", "corbat-coco")
483
- });
523
+ oldConfig: join(homedir(), ".config", "corbat-coco"),
524
+ /** Old MCP config directory (pre-unification) */
525
+ oldMcpDir: join(homedir(), ".config", "coco", "mcp")
526
+ };
484
527
  }
485
528
  });
486
529
 
@@ -658,8 +701,8 @@ async function configExists(configPath, scope = "any") {
658
701
  }
659
702
  return false;
660
703
  }
661
- function getConfigValue(config, path58) {
662
- const keys = path58.split(".");
704
+ function getConfigValue(config, path59) {
705
+ const keys = path59.split(".");
663
706
  let current = config;
664
707
  for (const key of keys) {
665
708
  if (current === null || current === void 0 || typeof current !== "object") {
@@ -4650,7 +4693,9 @@ var init_openai = __esm({
4650
4693
  break;
4651
4694
  case "response.completed":
4652
4695
  {
4696
+ const emittedCallIds = /* @__PURE__ */ new Set();
4653
4697
  for (const toolCall of toolCallAssembler.finalizeAll(this.name)) {
4698
+ if (toolCall.id) emittedCallIds.add(toolCall.id);
4654
4699
  yield {
4655
4700
  type: "tool_use_end",
4656
4701
  toolCall: {
@@ -4660,9 +4705,20 @@ var init_openai = __esm({
4660
4705
  }
4661
4706
  };
4662
4707
  }
4663
- const hasToolCalls = event.response.output.some(
4664
- (i) => i.type === "function_call"
4665
- );
4708
+ const outputItems = event.response?.output ?? [];
4709
+ for (const item of outputItems) {
4710
+ if (item.type !== "function_call" || !item.call_id || !item.name) continue;
4711
+ if (emittedCallIds.has(item.call_id)) continue;
4712
+ yield {
4713
+ type: "tool_use_end",
4714
+ toolCall: {
4715
+ id: item.call_id,
4716
+ name: item.name,
4717
+ input: parseToolCallArguments(item.arguments ?? "{}", this.name)
4718
+ }
4719
+ };
4720
+ }
4721
+ const hasToolCalls = outputItems.some((i) => i.type === "function_call");
4666
4722
  yield {
4667
4723
  type: "done",
4668
4724
  stopReason: hasToolCalls ? "tool_use" : "end_turn"
@@ -5277,6 +5333,8 @@ var init_codex = __esm({
5277
5333
  const decoder = new TextDecoder();
5278
5334
  let buffer = "";
5279
5335
  const toolCallAssembler = new ResponsesToolCallAssembler();
5336
+ const emittedToolCallIds = /* @__PURE__ */ new Set();
5337
+ const emittedToolCallSignatures = /* @__PURE__ */ new Set();
5280
5338
  let lastActivityTime = Date.now();
5281
5339
  const timeoutController = new AbortController();
5282
5340
  const timeoutInterval = setInterval(() => {
@@ -5339,6 +5397,9 @@ var init_codex = __esm({
5339
5397
  this.name
5340
5398
  );
5341
5399
  if (toolCall) {
5400
+ if (toolCall.id) emittedToolCallIds.add(toolCall.id);
5401
+ const signature = `${toolCall.name}:${JSON.stringify(toolCall.input ?? {})}`;
5402
+ emittedToolCallSignatures.add(signature);
5342
5403
  yield {
5343
5404
  type: "tool_use_end",
5344
5405
  toolCall: {
@@ -5352,6 +5413,9 @@ var init_codex = __esm({
5352
5413
  }
5353
5414
  case "response.completed": {
5354
5415
  for (const toolCall of toolCallAssembler.finalizeAll(this.name)) {
5416
+ if (toolCall.id) emittedToolCallIds.add(toolCall.id);
5417
+ const signature = `${toolCall.name}:${JSON.stringify(toolCall.input ?? {})}`;
5418
+ emittedToolCallSignatures.add(signature);
5355
5419
  yield {
5356
5420
  type: "tool_use_end",
5357
5421
  toolCall: {
@@ -5361,8 +5425,26 @@ var init_codex = __esm({
5361
5425
  }
5362
5426
  };
5363
5427
  }
5364
- const resp = event.response;
5365
- const output = resp?.output ?? [];
5428
+ const responsePayload = event.response;
5429
+ const output = responsePayload?.output ?? [];
5430
+ for (const item of output) {
5431
+ if (item.type !== "function_call" || !item.call_id || !item.name) continue;
5432
+ const parsedInput = parseToolCallArguments(item.arguments ?? "{}", this.name);
5433
+ const signature = `${item.name}:${JSON.stringify(parsedInput ?? {})}`;
5434
+ if (emittedToolCallIds.has(item.call_id) || emittedToolCallSignatures.has(signature)) {
5435
+ continue;
5436
+ }
5437
+ emittedToolCallIds.add(item.call_id);
5438
+ emittedToolCallSignatures.add(signature);
5439
+ yield {
5440
+ type: "tool_use_end",
5441
+ toolCall: {
5442
+ id: item.call_id,
5443
+ name: item.name,
5444
+ input: parsedInput
5445
+ }
5446
+ };
5447
+ }
5366
5448
  const hasToolCalls = output.some((i) => i.type === "function_call");
5367
5449
  yield {
5368
5450
  type: "done",
@@ -6953,6 +7035,9 @@ var init_errors2 = __esm({
6953
7035
  };
6954
7036
  }
6955
7037
  });
7038
+ function getDefaultRegistryPath() {
7039
+ return CONFIG_PATHS.mcpRegistry;
7040
+ }
6956
7041
  function validateServerConfig(config) {
6957
7042
  if (!config || typeof config !== "object") {
6958
7043
  throw new MCPError(-32602 /* INVALID_PARAMS */, "Server config must be an object");
@@ -7003,9 +7088,6 @@ function validateServerConfig(config) {
7003
7088
  }
7004
7089
  }
7005
7090
  }
7006
- function getDefaultRegistryPath() {
7007
- return join(DEFAULT_MCP_CONFIG_DIR, DEFAULT_REGISTRY_FILE);
7008
- }
7009
7091
  function parseRegistry(json2) {
7010
7092
  try {
7011
7093
  const parsed = JSON.parse(json2);
@@ -7020,13 +7102,82 @@ function parseRegistry(json2) {
7020
7102
  function serializeRegistry(servers) {
7021
7103
  return JSON.stringify({ servers, version: "1.0" }, null, 2);
7022
7104
  }
7023
- var DEFAULT_MCP_CONFIG_DIR, DEFAULT_REGISTRY_FILE;
7105
+ async function migrateMCPData(opts) {
7106
+ const oldDir = LEGACY_PATHS.oldMcpDir;
7107
+ const newRegistry = CONFIG_PATHS.mcpRegistry;
7108
+ const newConfig = CONFIG_PATHS.config;
7109
+ try {
7110
+ await migrateRegistry(oldDir, newRegistry);
7111
+ await migrateGlobalConfig(oldDir, newConfig);
7112
+ } catch (error) {
7113
+ getLogger().warn(
7114
+ `[MCP] Migration failed unexpectedly: ${error instanceof Error ? error.message : String(error)}`
7115
+ );
7116
+ }
7117
+ }
7118
+ async function migrateRegistry(oldDir, newRegistry) {
7119
+ const oldFile = join(oldDir, "registry.json");
7120
+ if (await fileExists(newRegistry)) return;
7121
+ if (!await fileExists(oldFile)) return;
7122
+ try {
7123
+ const content = await readFile(oldFile, "utf-8");
7124
+ const servers = parseRegistry(content);
7125
+ await mkdir(dirname(newRegistry), { recursive: true });
7126
+ await writeFile(newRegistry, serializeRegistry(servers), "utf-8");
7127
+ getLogger().info(
7128
+ `[MCP] Migrated registry from ${oldFile} to ${newRegistry}. The old file can be safely deleted.`
7129
+ );
7130
+ } catch (error) {
7131
+ getLogger().warn(
7132
+ `[MCP] Could not migrate registry: ${error instanceof Error ? error.message : String(error)}`
7133
+ );
7134
+ }
7135
+ }
7136
+ async function migrateGlobalConfig(oldDir, newConfigPath) {
7137
+ const oldFile = join(oldDir, "config.json");
7138
+ if (!await fileExists(oldFile)) return;
7139
+ try {
7140
+ const oldContent = await readFile(oldFile, "utf-8");
7141
+ const oldMcpConfig = JSON.parse(oldContent);
7142
+ let cocoConfig = {};
7143
+ if (await fileExists(newConfigPath)) {
7144
+ const existing = await readFile(newConfigPath, "utf-8");
7145
+ cocoConfig = JSON.parse(existing);
7146
+ }
7147
+ const existingMcp = cocoConfig.mcp ?? {};
7148
+ const fieldsToMigrate = ["defaultTimeout", "autoDiscover", "logLevel", "customServersPath"];
7149
+ let didMerge = false;
7150
+ for (const field of fieldsToMigrate) {
7151
+ if (oldMcpConfig[field] !== void 0 && existingMcp[field] === void 0) {
7152
+ existingMcp[field] = oldMcpConfig[field];
7153
+ didMerge = true;
7154
+ }
7155
+ }
7156
+ if (!didMerge) return;
7157
+ cocoConfig.mcp = existingMcp;
7158
+ await mkdir(dirname(newConfigPath), { recursive: true });
7159
+ await writeFile(newConfigPath, JSON.stringify(cocoConfig, null, 2), "utf-8");
7160
+ getLogger().info(`[MCP] Migrated global MCP settings from ${oldFile} into ${newConfigPath}.`);
7161
+ } catch (error) {
7162
+ getLogger().warn(
7163
+ `[MCP] Could not migrate global MCP config: ${error instanceof Error ? error.message : String(error)}`
7164
+ );
7165
+ }
7166
+ }
7167
+ async function fileExists(path59) {
7168
+ try {
7169
+ await access(path59);
7170
+ return true;
7171
+ } catch {
7172
+ return false;
7173
+ }
7174
+ }
7024
7175
  var init_config = __esm({
7025
7176
  "src/mcp/config.ts"() {
7177
+ init_paths();
7026
7178
  init_types();
7027
7179
  init_errors2();
7028
- DEFAULT_MCP_CONFIG_DIR = join(homedir(), ".config", "coco", "mcp");
7029
- DEFAULT_REGISTRY_FILE = "registry.json";
7180
+ init_logger();
7030
7181
  }
7031
7182
  });
7032
7183
 
@@ -7124,6 +7275,9 @@ var init_registry = __esm({
7124
7275
  * Load registry from disk
7125
7276
  */
7126
7277
  async load() {
7278
+ if (this.registryPath === getDefaultRegistryPath()) {
7279
+ await migrateMCPData();
7280
+ }
7127
7281
  try {
7128
7282
  await access(this.registryPath);
7129
7283
  const content = await readFile(this.registryPath, "utf-8");
@@ -7143,8 +7297,8 @@ var init_registry = __esm({
7143
7297
  /**
7144
7298
  * Ensure directory exists
7145
7299
  */
7146
- async ensureDir(path58) {
7147
- await mkdir(dirname(path58), { recursive: true });
7300
+ async ensureDir(path59) {
7301
+ await mkdir(dirname(path59), { recursive: true });
7148
7302
  }
7149
7303
  };
7150
7304
  }
@@ -7378,8 +7532,8 @@ async function loadSkillFromDirectory(skillDir, scope) {
7378
7532
  if (await isMarkdownSkill(skillDir)) {
7379
7533
  return loadMarkdownMetadata(skillDir, scope);
7380
7534
  }
7381
- const hasTs = await fileExists(path37__default.join(skillDir, "index.ts"));
7382
- const hasJs = await fileExists(path37__default.join(skillDir, "index.js"));
7535
+ const hasTs = await fileExists2(path37__default.join(skillDir, "index.ts"));
7536
+ const hasJs = await fileExists2(path37__default.join(skillDir, "index.js"));
7383
7537
  if (hasTs || hasJs) {
7384
7538
  return null;
7385
7539
  }
@@ -7397,7 +7551,7 @@ async function loadFullSkill(metadata) {
7397
7551
  );
7398
7552
  return null;
7399
7553
  }
7400
- async function fileExists(filePath) {
7554
+ async function fileExists2(filePath) {
7401
7555
  try {
7402
7556
  await fs34__default.access(filePath);
7403
7557
  return true;
@@ -7412,19 +7566,48 @@ var init_loader2 = __esm({
7412
7566
  init_typescript_loader();
7413
7567
  }
7414
7568
  });
7569
+ function parseDiscoveryOptions(options) {
7570
+ return typeof options === "string" ? { globalDir: options } : options ?? {};
7571
+ }
7572
+ function normalizeDirectories(dirs, relativeBaseDir) {
7573
+ const seen = /* @__PURE__ */ new Set();
7574
+ const normalized = [];
7575
+ const home = homedir();
7576
+ for (const dir of dirs) {
7577
+ const trimmed = dir.trim();
7578
+ if (!trimmed) continue;
7579
+ const expanded = trimmed === "~" ? home : trimmed.startsWith("~/") ? path37__default.join(home, trimmed.slice(2)) : trimmed;
7580
+ const resolved = path37__default.isAbsolute(expanded) ? path37__default.resolve(expanded) : path37__default.resolve(relativeBaseDir ?? process.cwd(), expanded);
7581
+ if (seen.has(resolved)) continue;
7582
+ seen.add(resolved);
7583
+ normalized.push(resolved);
7584
+ }
7585
+ return normalized;
7586
+ }
7587
+ function resolveDiscoveryDirs(projectPath, options) {
7588
+ const opts = parseDiscoveryOptions(options);
7589
+ const globalDirs = normalizeDirectories(
7590
+ opts.globalDirs && opts.globalDirs.length > 0 ? opts.globalDirs : opts.globalDir ? [opts.globalDir] : GLOBAL_SKILLS_DIRS
7591
+ );
7592
+ const projectDirs = normalizeDirectories(
7593
+ opts.projectDirs && opts.projectDirs.length > 0 ? opts.projectDirs : opts.projectDir ? [opts.projectDir] : PROJECT_SKILLS_DIRNAMES.map((d) => path37__default.join(projectPath, d)),
7594
+ projectPath
7595
+ );
7596
+ return { globalDirs, projectDirs };
7597
+ }
7415
7598
  async function discoverAllSkills(projectPath, builtinSkills = [], options) {
7416
- const opts = typeof options === "string" ? { globalDir: options } : options ?? {};
7417
7599
  const allSkills = /* @__PURE__ */ new Map();
7600
+ const { globalDirs, projectDirs } = resolveDiscoveryDirs(projectPath, options);
7418
7601
  for (const skill of builtinSkills) {
7419
7602
  const meta = nativeSkillToMetadata(skill, "builtin");
7420
7603
  allSkills.set(meta.id, meta);
7421
7604
  }
7422
- const resolvedGlobalDir = opts.globalDir ?? GLOBAL_SKILLS_DIR;
7423
- const globalSkills = await scanSkillsDirectory(resolvedGlobalDir, "global");
7424
- for (const meta of globalSkills) {
7425
- applyWithPriority(allSkills, meta);
7605
+ for (const dir of globalDirs) {
7606
+ const globalSkills = await scanSkillsDirectory(dir, "global");
7607
+ for (const meta of globalSkills) {
7608
+ applyWithPriority(allSkills, meta);
7609
+ }
7426
7610
  }
7427
- const projectDirs = opts.projectDir ? [opts.projectDir] : PROJECT_SKILLS_DIRNAMES.map((d) => path37__default.join(projectPath, d));
7428
7611
  for (const dir of projectDirs) {
7429
7612
  const projectSkills = await scanSkillsDirectory(dir, "project");
7430
7613
  for (const meta of projectSkills) {
@@ -7490,11 +7673,11 @@ async function scanNestedSkills(dir, scope, depth) {
7490
7673
  }
7491
7674
  function applyWithPriority(map, meta) {
7492
7675
  const existing = map.get(meta.id);
7493
- if (!existing || SCOPE_PRIORITY[meta.scope] > SCOPE_PRIORITY[existing.scope]) {
7676
+ if (!existing || SCOPE_PRIORITY[meta.scope] >= SCOPE_PRIORITY[existing.scope]) {
7494
7677
  map.set(meta.id, meta);
7495
7678
  }
7496
7679
  }
7497
- var GLOBAL_SKILLS_DIR, PROJECT_SKILLS_DIRNAMES, MAX_NESTING_DEPTH;
7680
+ var GLOBAL_SKILLS_DIRS, PROJECT_SKILLS_DIRNAMES, MAX_NESTING_DEPTH;
7498
7681
  var init_discovery = __esm({
7499
7682
  "src/skills/discovery.ts"() {
7500
7683
  init_types2();
@@ -7502,14 +7685,31 @@ var init_discovery = __esm({
7502
7685
  init_typescript_loader();
7503
7686
  init_paths();
7504
7687
  init_logger();
7505
- GLOBAL_SKILLS_DIR = path37__default.join(COCO_HOME, "skills");
7688
+ GLOBAL_SKILLS_DIRS = [
7689
+ path37__default.join(homedir(), ".codex", "skills"),
7690
+ // Codex CLI legacy compat
7691
+ path37__default.join(homedir(), ".gemini", "skills"),
7692
+ // Gemini CLI compat
7693
+ path37__default.join(homedir(), ".opencode", "skills"),
7694
+ // OpenCode compat
7695
+ path37__default.join(homedir(), ".claude", "skills"),
7696
+ // Claude Code compat
7697
+ path37__default.join(homedir(), ".agents", "skills"),
7698
+ // shared cross-agent standard
7699
+ path37__default.join(COCO_HOME, "skills")
7700
+ // Coco native global directory (authoritative for Coco)
7701
+ ];
7506
7702
  PROJECT_SKILLS_DIRNAMES = [
7507
7703
  ".claude/skills",
7508
- // Claude compat — read for migration/interop (lowest project priority)
7509
- ".agents/skills",
7510
- // Shared cross-agent standard (medium priority)
7511
- ".coco/skills"
7512
- // Coco native — authoritative (highest project priority)
7704
+ // Claude Code compat — read for migration/interop
7705
+ ".codex/skills",
7706
+ // Codex CLI (OpenAI) compat
7707
+ ".gemini/skills",
7708
+ // Gemini CLI (Google) compat
7709
+ ".opencode/skills",
7710
+ // OpenCode compat
7711
+ ".agents/skills"
7712
+ // Native — cross-agent standard, authoritative (highest priority)
7513
7713
  ];
7514
7714
  MAX_NESTING_DEPTH = 1;
7515
7715
  }
@@ -7811,7 +8011,9 @@ var init_registry2 = __esm({
7811
8011
  }
7812
8012
  const discoveryOptions = {
7813
8013
  globalDir: globalDir ?? this._config.globalDir,
7814
- projectDir: this._config.projectDir
8014
+ globalDirs: this._config.globalDirs,
8015
+ projectDir: this._config.projectDir,
8016
+ projectDirs: this._config.projectDirs
7815
8017
  };
7816
8018
  const discovered = await discoverAllSkills(projectPath, builtinSkills, discoveryOptions);
7817
8019
  const disabledSet = new Set(this._config.disabled ?? []);
@@ -8032,6 +8234,7 @@ __export(skills_exports, {
8032
8234
  nativeSkillToLoaded: () => nativeSkillToLoaded,
8033
8235
  nativeSkillToMetadata: () => nativeSkillToMetadata,
8034
8236
  resolveCategory: () => resolveCategory,
8237
+ resolveDiscoveryDirs: () => resolveDiscoveryDirs,
8035
8238
  scanSkillsDirectory: () => scanSkillsDirectory,
8036
8239
  stem: () => stem,
8037
8240
  tokenize: () => tokenize
@@ -8732,9 +8935,9 @@ function createEmptyMemoryContext() {
8732
8935
  errors: []
8733
8936
  };
8734
8937
  }
8735
- function createMissingMemoryFile(path58, level) {
8938
+ function createMissingMemoryFile(path59, level) {
8736
8939
  return {
8737
- path: path58,
8940
+ path: path59,
8738
8941
  level,
8739
8942
  content: "",
8740
8943
  sections: [],
@@ -9339,7 +9542,8 @@ async function createDefaultReplConfig() {
9339
9542
  agent: {
9340
9543
  systemPrompt: COCO_SYSTEM_PROMPT,
9341
9544
  maxToolIterations: 25,
9342
- confirmDestructive: true
9545
+ confirmDestructive: true,
9546
+ enableAutoSwitchProvider: false
9343
9547
  }
9344
9548
  };
9345
9549
  }
@@ -10179,8 +10383,8 @@ __export(trust_store_exports, {
10179
10383
  saveTrustStore: () => saveTrustStore,
10180
10384
  updateLastAccessed: () => updateLastAccessed
10181
10385
  });
10182
- async function ensureDir(path58) {
10183
- await mkdir(dirname(path58), { recursive: true });
10386
+ async function ensureDir(path59) {
10387
+ await mkdir(dirname(path59), { recursive: true });
10184
10388
  }
10185
10389
  async function loadTrustStore(storePath = TRUST_STORE_PATH) {
10186
10390
  try {
@@ -10265,8 +10469,8 @@ function canPerformOperation(store, projectPath, operation) {
10265
10469
  };
10266
10470
  return permissions[level]?.includes(operation) ?? false;
10267
10471
  }
10268
- function normalizePath(path58) {
10269
- return join(path58);
10472
+ function normalizePath(path59) {
10473
+ return join(path59);
10270
10474
  }
10271
10475
  function createTrustStore(storePath = TRUST_STORE_PATH) {
10272
10476
  let store = null;
@@ -10622,12 +10826,12 @@ function humanizeError(message, toolName) {
10622
10826
  return msg;
10623
10827
  }
10624
10828
  if (/ENOENT/i.test(msg)) {
10625
- const path58 = extractQuotedPath(msg);
10626
- return path58 ? `File or directory not found: ${path58}` : "File or directory not found";
10829
+ const path59 = extractQuotedPath(msg);
10830
+ return path59 ? `File or directory not found: ${path59}` : "File or directory not found";
10627
10831
  }
10628
10832
  if (/EACCES/i.test(msg)) {
10629
- const path58 = extractQuotedPath(msg);
10630
- return path58 ? `Permission denied: ${path58}` : "Permission denied \u2014 check file permissions";
10833
+ const path59 = extractQuotedPath(msg);
10834
+ return path59 ? `Permission denied: ${path59}` : "Permission denied \u2014 check file permissions";
10631
10835
  }
10632
10836
  if (/EISDIR/i.test(msg)) {
10633
10837
  return "Expected a file but found a directory at the specified path";
@@ -10899,7 +11103,7 @@ var MAX_CONSECUTIVE_ERRORS, _safetyNetInstalled;
10899
11103
  var init_error_resilience = __esm({
10900
11104
  "src/cli/repl/error-resilience.ts"() {
10901
11105
  init_errors();
10902
- MAX_CONSECUTIVE_ERRORS = 2;
11106
+ MAX_CONSECUTIVE_ERRORS = 5;
10903
11107
  _safetyNetInstalled = false;
10904
11108
  }
10905
11109
  });
@@ -11100,7 +11304,7 @@ Suggestion: ${error.suggestion}`;
11100
11304
  };
11101
11305
  }
11102
11306
  });
11103
- async function fileExists2(filePath) {
11307
+ async function fileExists3(filePath) {
11104
11308
  try {
11105
11309
  await fs34__default.access(filePath);
11106
11310
  return true;
@@ -11561,9 +11765,9 @@ var init_diff_renderer = __esm({
11561
11765
  getTerminalWidth = () => process.stdout.columns || 80;
11562
11766
  }
11563
11767
  });
11564
- async function fileExists3(path58) {
11768
+ async function fileExists4(path59) {
11565
11769
  try {
11566
- await access(path58);
11770
+ await access(path59);
11567
11771
  return true;
11568
11772
  } catch {
11569
11773
  return false;
@@ -11578,7 +11782,7 @@ async function dirHasFiles(dir) {
11578
11782
  }
11579
11783
  }
11580
11784
  async function detectMaturity(cwd) {
11581
- const hasPackageJson = await fileExists3(join(cwd, "package.json"));
11785
+ const hasPackageJson = await fileExists4(join(cwd, "package.json"));
11582
11786
  if (!hasPackageJson) {
11583
11787
  const otherManifests = [
11584
11788
  "go.mod",
@@ -11591,7 +11795,7 @@ async function detectMaturity(cwd) {
11591
11795
  ];
11592
11796
  let hasAnyManifest = false;
11593
11797
  for (const m of otherManifests) {
11594
- if (await fileExists3(join(cwd, m))) {
11798
+ if (await fileExists4(join(cwd, m))) {
11595
11799
  hasAnyManifest = true;
11596
11800
  break;
11597
11801
  }
@@ -11632,7 +11836,7 @@ async function detectMaturity(cwd) {
11632
11836
  cwd,
11633
11837
  ignore: ["node_modules/**", "dist/**", "build/**"]
11634
11838
  });
11635
- const hasCI = await fileExists3(join(cwd, ".github/workflows")) && await dirHasFiles(join(cwd, ".github/workflows"));
11839
+ const hasCI = await fileExists4(join(cwd, ".github/workflows")) && await dirHasFiles(join(cwd, ".github/workflows"));
11636
11840
  const lintConfigs = [
11637
11841
  ".eslintrc.js",
11638
11842
  ".eslintrc.json",
@@ -11645,7 +11849,7 @@ async function detectMaturity(cwd) {
11645
11849
  ];
11646
11850
  let hasLintConfig = false;
11647
11851
  for (const config of lintConfigs) {
11648
- if (await fileExists3(join(cwd, config))) {
11852
+ if (await fileExists4(join(cwd, config))) {
11649
11853
  hasLintConfig = true;
11650
11854
  break;
11651
11855
  }
@@ -15943,16 +16147,16 @@ var init_evaluator = __esm({
15943
16147
  * Find source files in project, adapting to the detected language stack.
15944
16148
  */
15945
16149
  async findSourceFiles() {
15946
- const { access: access16 } = await import('fs/promises');
16150
+ const { access: access17 } = await import('fs/promises');
15947
16151
  const { join: join26 } = await import('path');
15948
16152
  let isJava = false;
15949
16153
  try {
15950
- await access16(join26(this.projectPath, "pom.xml"));
16154
+ await access17(join26(this.projectPath, "pom.xml"));
15951
16155
  isJava = true;
15952
16156
  } catch {
15953
16157
  for (const f of ["build.gradle", "build.gradle.kts"]) {
15954
16158
  try {
15955
- await access16(join26(this.projectPath, f));
16159
+ await access17(join26(this.projectPath, f));
15956
16160
  isJava = true;
15957
16161
  break;
15958
16162
  } catch {
@@ -16444,7 +16648,7 @@ async function checkTestCoverage(diff, cwd) {
16444
16648
  );
16445
16649
  if (!hasTestChange) {
16446
16650
  const ext = src.path.match(/\.(ts|tsx|js|jsx)$/)?.[0] ?? ".ts";
16447
- const testExists = await fileExists2(path37__default.join(cwd, `${baseName}.test${ext}`)) || await fileExists2(path37__default.join(cwd, `${baseName}.spec${ext}`));
16651
+ const testExists = await fileExists3(path37__default.join(cwd, `${baseName}.test${ext}`)) || await fileExists3(path37__default.join(cwd, `${baseName}.spec${ext}`));
16448
16652
  if (testExists) {
16449
16653
  if (src.additions >= TEST_COVERAGE_LARGE_CHANGE_THRESHOLD) {
16450
16654
  findings.push({
@@ -18126,7 +18330,7 @@ var init_github = __esm({
18126
18330
  async function detectVersionFile(cwd) {
18127
18331
  for (const { file, stack, field } of VERSION_FILES) {
18128
18332
  const fullPath = path37__default.join(cwd, file);
18129
- if (await fileExists2(fullPath)) {
18333
+ if (await fileExists3(fullPath)) {
18130
18334
  const version = await readVersionFromFile(fullPath, stack, field);
18131
18335
  if (version) {
18132
18336
  return { path: file, stack, currentVersion: version, field };
@@ -18231,7 +18435,7 @@ var init_version_detector = __esm({
18231
18435
  async function detectChangelog(cwd) {
18232
18436
  for (const name of CHANGELOG_NAMES) {
18233
18437
  const fullPath = path37__default.join(cwd, name);
18234
- if (await fileExists2(fullPath)) {
18438
+ if (await fileExists3(fullPath)) {
18235
18439
  const content = await readFile(fullPath, "utf-8");
18236
18440
  const format = detectFormat(content);
18237
18441
  return { path: name, format };
@@ -18313,11 +18517,11 @@ var init_changelog = __esm({
18313
18517
  }
18314
18518
  });
18315
18519
  async function detectStack(cwd) {
18316
- if (await fileExists2(path37__default.join(cwd, "package.json"))) return "node";
18317
- if (await fileExists2(path37__default.join(cwd, "Cargo.toml"))) return "rust";
18318
- if (await fileExists2(path37__default.join(cwd, "pyproject.toml"))) return "python";
18319
- if (await fileExists2(path37__default.join(cwd, "go.mod"))) return "go";
18320
- if (await fileExists2(path37__default.join(cwd, "pom.xml"))) return "java";
18520
+ if (await fileExists3(path37__default.join(cwd, "package.json"))) return "node";
18521
+ if (await fileExists3(path37__default.join(cwd, "Cargo.toml"))) return "rust";
18522
+ if (await fileExists3(path37__default.join(cwd, "pyproject.toml"))) return "python";
18523
+ if (await fileExists3(path37__default.join(cwd, "go.mod"))) return "go";
18524
+ if (await fileExists3(path37__default.join(cwd, "pom.xml"))) return "java";
18321
18525
  return "unknown";
18322
18526
  }
18323
18527
  async function detectPackageManager(cwd, stack) {
@@ -18325,16 +18529,16 @@ async function detectPackageManager(cwd, stack) {
18325
18529
  if (stack === "python") return "pip";
18326
18530
  if (stack === "go") return "go";
18327
18531
  if (stack === "node") {
18328
- if (await fileExists2(path37__default.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
18329
- if (await fileExists2(path37__default.join(cwd, "yarn.lock"))) return "yarn";
18330
- if (await fileExists2(path37__default.join(cwd, "bun.lockb"))) return "bun";
18532
+ if (await fileExists3(path37__default.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
18533
+ if (await fileExists3(path37__default.join(cwd, "yarn.lock"))) return "yarn";
18534
+ if (await fileExists3(path37__default.join(cwd, "bun.lockb"))) return "bun";
18331
18535
  return "npm";
18332
18536
  }
18333
18537
  return null;
18334
18538
  }
18335
18539
  async function detectCI(cwd) {
18336
18540
  const ghDir = path37__default.join(cwd, ".github", "workflows");
18337
- if (await fileExists2(ghDir)) {
18541
+ if (await fileExists3(ghDir)) {
18338
18542
  let workflowFiles = [];
18339
18543
  let hasCodeQL = false;
18340
18544
  let hasLinting = false;
@@ -18357,7 +18561,7 @@ async function detectCI(cwd) {
18357
18561
  }
18358
18562
  return { type: "github-actions", workflowFiles, hasCodeQL, hasLinting };
18359
18563
  }
18360
- if (await fileExists2(path37__default.join(cwd, ".gitlab-ci.yml"))) {
18564
+ if (await fileExists3(path37__default.join(cwd, ".gitlab-ci.yml"))) {
18361
18565
  return {
18362
18566
  type: "gitlab-ci",
18363
18567
  workflowFiles: [".gitlab-ci.yml"],
@@ -18365,7 +18569,7 @@ async function detectCI(cwd) {
18365
18569
  hasLinting: false
18366
18570
  };
18367
18571
  }
18368
- if (await fileExists2(path37__default.join(cwd, ".circleci"))) {
18572
+ if (await fileExists3(path37__default.join(cwd, ".circleci"))) {
18369
18573
  return { type: "circle-ci", workflowFiles: [], hasCodeQL: false, hasLinting: false };
18370
18574
  }
18371
18575
  return { type: "none", workflowFiles: [], hasCodeQL: false, hasLinting: false };
@@ -21639,13 +21843,13 @@ __export(stack_detector_exports, {
21639
21843
  detectProjectStack: () => detectProjectStack
21640
21844
  });
21641
21845
  async function detectStack2(cwd) {
21642
- if (await fileExists2(path37__default.join(cwd, "package.json"))) return "node";
21643
- if (await fileExists2(path37__default.join(cwd, "Cargo.toml"))) return "rust";
21644
- if (await fileExists2(path37__default.join(cwd, "pyproject.toml"))) return "python";
21645
- if (await fileExists2(path37__default.join(cwd, "go.mod"))) return "go";
21646
- if (await fileExists2(path37__default.join(cwd, "pom.xml"))) return "java";
21647
- if (await fileExists2(path37__default.join(cwd, "build.gradle"))) return "java";
21648
- if (await fileExists2(path37__default.join(cwd, "build.gradle.kts"))) return "java";
21846
+ if (await fileExists3(path37__default.join(cwd, "package.json"))) return "node";
21847
+ if (await fileExists3(path37__default.join(cwd, "Cargo.toml"))) return "rust";
21848
+ if (await fileExists3(path37__default.join(cwd, "pyproject.toml"))) return "python";
21849
+ if (await fileExists3(path37__default.join(cwd, "go.mod"))) return "go";
21850
+ if (await fileExists3(path37__default.join(cwd, "pom.xml"))) return "java";
21851
+ if (await fileExists3(path37__default.join(cwd, "build.gradle"))) return "java";
21852
+ if (await fileExists3(path37__default.join(cwd, "build.gradle.kts"))) return "java";
21649
21853
  return "unknown";
21650
21854
  }
21651
21855
  async function detectPackageManager3(cwd, stack) {
@@ -21653,17 +21857,17 @@ async function detectPackageManager3(cwd, stack) {
21653
21857
  if (stack === "python") return "pip";
21654
21858
  if (stack === "go") return "go";
21655
21859
  if (stack === "java") {
21656
- if (await fileExists2(path37__default.join(cwd, "build.gradle")) || await fileExists2(path37__default.join(cwd, "build.gradle.kts"))) {
21860
+ if (await fileExists3(path37__default.join(cwd, "build.gradle")) || await fileExists3(path37__default.join(cwd, "build.gradle.kts"))) {
21657
21861
  return "gradle";
21658
21862
  }
21659
- if (await fileExists2(path37__default.join(cwd, "pom.xml"))) {
21863
+ if (await fileExists3(path37__default.join(cwd, "pom.xml"))) {
21660
21864
  return "maven";
21661
21865
  }
21662
21866
  }
21663
21867
  if (stack === "node") {
21664
- if (await fileExists2(path37__default.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
21665
- if (await fileExists2(path37__default.join(cwd, "yarn.lock"))) return "yarn";
21666
- if (await fileExists2(path37__default.join(cwd, "bun.lockb"))) return "bun";
21868
+ if (await fileExists3(path37__default.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
21869
+ if (await fileExists3(path37__default.join(cwd, "yarn.lock"))) return "yarn";
21870
+ if (await fileExists3(path37__default.join(cwd, "bun.lockb"))) return "bun";
21667
21871
  return "npm";
21668
21872
  }
21669
21873
  return null;
@@ -21701,7 +21905,7 @@ async function parsePackageJson(cwd) {
21701
21905
  if (allDeps["@playwright/test"]) testingFrameworks.push("playwright");
21702
21906
  if (allDeps.cypress) testingFrameworks.push("cypress");
21703
21907
  const languages = ["JavaScript"];
21704
- if (allDeps.typescript || await fileExists2(path37__default.join(cwd, "tsconfig.json"))) {
21908
+ if (allDeps.typescript || await fileExists3(path37__default.join(cwd, "tsconfig.json"))) {
21705
21909
  languages.push("TypeScript");
21706
21910
  }
21707
21911
  return {
@@ -21802,7 +22006,7 @@ async function detectProjectStack(cwd) {
21802
22006
  testingFrameworks = parsed.testingFrameworks;
21803
22007
  languages = parsed.languages;
21804
22008
  } else if (stack === "java") {
21805
- const isGradle = await fileExists2(path37__default.join(cwd, "build.gradle")) || await fileExists2(path37__default.join(cwd, "build.gradle.kts"));
22009
+ const isGradle = await fileExists3(path37__default.join(cwd, "build.gradle")) || await fileExists3(path37__default.join(cwd, "build.gradle.kts"));
21806
22010
  const parsed = isGradle ? { dependencies: {}, frameworks: [], buildTools: ["gradle"], testingFrameworks: ["JUnit"] } : await parsePomXml(cwd);
21807
22011
  dependencies = parsed.dependencies;
21808
22012
  frameworks = parsed.frameworks;
@@ -22088,6 +22292,251 @@ var init_tools = __esm({
22088
22292
  }
22089
22293
  });
22090
22294
 
22295
+ // src/mcp/config-loader.ts
22296
+ var config_loader_exports = {};
22297
+ __export(config_loader_exports, {
22298
+ loadMCPConfigFile: () => loadMCPConfigFile,
22299
+ loadMCPServersFromCOCOConfig: () => loadMCPServersFromCOCOConfig,
22300
+ loadProjectMCPFile: () => loadProjectMCPFile,
22301
+ mergeMCPConfigs: () => mergeMCPConfigs
22302
+ });
22303
+ function expandEnvVar(value) {
22304
+ return value.replace(/\$\{([^}]+)\}/g, (match, name) => process.env[name] ?? match);
22305
+ }
22306
+ function expandEnvObject(env2) {
22307
+ const result = {};
22308
+ for (const [k, v] of Object.entries(env2)) {
22309
+ result[k] = expandEnvVar(v);
22310
+ }
22311
+ return result;
22312
+ }
22313
+ function expandHeaders(headers) {
22314
+ const result = {};
22315
+ for (const [k, v] of Object.entries(headers)) {
22316
+ result[k] = expandEnvVar(v);
22317
+ }
22318
+ return result;
22319
+ }
22320
+ function convertStandardEntry(name, entry) {
22321
+ if (entry.command) {
22322
+ return {
22323
+ name,
22324
+ transport: "stdio",
22325
+ enabled: entry.enabled ?? true,
22326
+ stdio: {
22327
+ command: entry.command,
22328
+ args: entry.args,
22329
+ env: entry.env ? expandEnvObject(entry.env) : void 0
22330
+ }
22331
+ };
22332
+ }
22333
+ if (entry.url) {
22334
+ const headers = entry.headers ? expandHeaders(entry.headers) : void 0;
22335
+ const authHeader = headers?.["Authorization"] ?? headers?.["authorization"];
22336
+ let auth;
22337
+ if (authHeader) {
22338
+ if (authHeader.startsWith("Bearer ")) {
22339
+ auth = { type: "bearer", token: authHeader.slice(7) };
22340
+ } else {
22341
+ auth = { type: "apikey", token: authHeader };
22342
+ }
22343
+ }
22344
+ return {
22345
+ name,
22346
+ transport: "http",
22347
+ enabled: entry.enabled ?? true,
22348
+ http: {
22349
+ url: entry.url,
22350
+ ...headers && Object.keys(headers).length > 0 ? { headers } : {},
22351
+ ...auth ? { auth } : {}
22352
+ }
22353
+ };
22354
+ }
22355
+ throw new Error(`Server "${name}" must have either "command" (stdio) or "url" (http) defined`);
22356
+ }
22357
+ async function loadMCPConfigFile(configPath) {
22358
+ try {
22359
+ await access(configPath);
22360
+ } catch {
22361
+ throw new MCPError(-32003 /* CONNECTION_ERROR */, `Config file not found: ${configPath}`);
22362
+ }
22363
+ let content;
22364
+ try {
22365
+ content = await readFile(configPath, "utf-8");
22366
+ } catch (error) {
22367
+ throw new MCPError(
22368
+ -32003 /* CONNECTION_ERROR */,
22369
+ `Failed to read config file: ${error instanceof Error ? error.message : "Unknown error"}`
22370
+ );
22371
+ }
22372
+ let parsed;
22373
+ try {
22374
+ parsed = JSON.parse(content);
22375
+ } catch {
22376
+ throw new MCPError(-32700 /* PARSE_ERROR */, "Invalid JSON in config file");
22377
+ }
22378
+ const obj = parsed;
22379
+ if (obj.mcpServers && typeof obj.mcpServers === "object" && !Array.isArray(obj.mcpServers)) {
22380
+ return loadStandardFormat(obj, configPath);
22381
+ }
22382
+ if (obj.servers && Array.isArray(obj.servers)) {
22383
+ return loadCocoFormat(obj, configPath);
22384
+ }
22385
+ throw new MCPError(
22386
+ -32602 /* INVALID_PARAMS */,
22387
+ 'Config file must have either a "mcpServers" object (standard) or a "servers" array (Coco format)'
22388
+ );
22389
+ }
22390
+ function loadStandardFormat(config, configPath) {
22391
+ const validServers = [];
22392
+ const errors = [];
22393
+ for (const [name, entry] of Object.entries(config.mcpServers)) {
22394
+ if (name.startsWith("_")) continue;
22395
+ try {
22396
+ const converted = convertStandardEntry(name, entry);
22397
+ validateServerConfig(converted);
22398
+ validServers.push(converted);
22399
+ } catch (error) {
22400
+ const message = error instanceof Error ? error.message : "Unknown error";
22401
+ errors.push(`Server '${name}': ${message}`);
22402
+ }
22403
+ }
22404
+ if (errors.length > 0) {
22405
+ getLogger().warn(`[MCP] Some servers in ${configPath} failed to load: ${errors.join("; ")}`);
22406
+ }
22407
+ return validServers;
22408
+ }
22409
+ async function loadProjectMCPFile(projectPath) {
22410
+ const mcpJsonPath = path37__default.join(projectPath, ".mcp.json");
22411
+ try {
22412
+ await access(mcpJsonPath);
22413
+ } catch {
22414
+ return [];
22415
+ }
22416
+ try {
22417
+ return await loadMCPConfigFile(mcpJsonPath);
22418
+ } catch (error) {
22419
+ getLogger().warn(
22420
+ `[MCP] Failed to load .mcp.json: ${error instanceof Error ? error.message : String(error)}`
22421
+ );
22422
+ return [];
22423
+ }
22424
+ }
22425
+ function loadCocoFormat(config, configPath) {
22426
+ const validServers = [];
22427
+ const errors = [];
22428
+ for (const server of config.servers) {
22429
+ try {
22430
+ const converted = convertCocoServerEntry(server);
22431
+ validateServerConfig(converted);
22432
+ validServers.push(converted);
22433
+ } catch (error) {
22434
+ const message = error instanceof Error ? error.message : "Unknown error";
22435
+ errors.push(`Server '${server.name || "unknown"}': ${message}`);
22436
+ }
22437
+ }
22438
+ if (errors.length > 0) {
22439
+ getLogger().warn(`[MCP] Some servers in ${configPath} failed to load: ${errors.join("; ")}`);
22440
+ }
22441
+ return validServers;
22442
+ }
22443
+ function convertCocoServerEntry(server) {
22444
+ const base = {
22445
+ name: server.name,
22446
+ description: server.description,
22447
+ transport: server.transport,
22448
+ enabled: server.enabled ?? true,
22449
+ metadata: server.metadata
22450
+ };
22451
+ if (server.transport === "stdio" && server.stdio) {
22452
+ return {
22453
+ ...base,
22454
+ stdio: {
22455
+ command: server.stdio.command,
22456
+ args: server.stdio.args,
22457
+ env: server.stdio.env ? expandEnvObject(server.stdio.env) : void 0,
22458
+ cwd: server.stdio.cwd
22459
+ }
22460
+ };
22461
+ }
22462
+ if (server.transport === "http" && server.http) {
22463
+ return {
22464
+ ...base,
22465
+ http: {
22466
+ url: server.http.url,
22467
+ ...server.http.headers ? { headers: expandHeaders(server.http.headers) } : {},
22468
+ ...server.http.auth ? { auth: server.http.auth } : {},
22469
+ ...server.http.timeout !== void 0 ? { timeout: server.http.timeout } : {}
22470
+ }
22471
+ };
22472
+ }
22473
+ throw new Error(`Missing configuration for transport: ${server.transport}`);
22474
+ }
22475
+ function mergeMCPConfigs(base, ...overrides) {
22476
+ const merged = /* @__PURE__ */ new Map();
22477
+ for (const server of base) {
22478
+ merged.set(server.name, server);
22479
+ }
22480
+ for (const override of overrides) {
22481
+ for (const server of override) {
22482
+ const existing = merged.get(server.name);
22483
+ if (existing) {
22484
+ merged.set(server.name, { ...existing, ...server });
22485
+ } else {
22486
+ merged.set(server.name, server);
22487
+ }
22488
+ }
22489
+ }
22490
+ return Array.from(merged.values());
22491
+ }
22492
+ async function loadMCPServersFromCOCOConfig(configPath) {
22493
+ const { loadConfig: loadConfig3 } = await Promise.resolve().then(() => (init_loader(), loader_exports));
22494
+ const { MCPServerConfigEntrySchema: MCPServerConfigEntrySchema2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
22495
+ const config = await loadConfig3(configPath);
22496
+ if (!config.mcp?.servers || config.mcp.servers.length === 0) {
22497
+ return [];
22498
+ }
22499
+ const servers = [];
22500
+ for (const entry of config.mcp.servers) {
22501
+ try {
22502
+ const parsed = MCPServerConfigEntrySchema2.parse(entry);
22503
+ const serverConfig = {
22504
+ name: parsed.name,
22505
+ description: parsed.description,
22506
+ transport: parsed.transport,
22507
+ enabled: parsed.enabled,
22508
+ ...parsed.transport === "stdio" && parsed.command && {
22509
+ stdio: {
22510
+ command: parsed.command,
22511
+ args: parsed.args,
22512
+ env: parsed.env ? expandEnvObject(parsed.env) : void 0
22513
+ }
22514
+ },
22515
+ ...parsed.transport === "http" && parsed.url && {
22516
+ http: {
22517
+ url: parsed.url,
22518
+ auth: parsed.auth
22519
+ }
22520
+ }
22521
+ };
22522
+ validateServerConfig(serverConfig);
22523
+ servers.push(serverConfig);
22524
+ } catch (error) {
22525
+ const message = error instanceof Error ? error.message : "Unknown error";
22526
+ getLogger().warn(`[MCP] Failed to load server '${entry.name}': ${message}`);
22527
+ }
22528
+ }
22529
+ return servers;
22530
+ }
22531
+ var init_config_loader = __esm({
22532
+ "src/mcp/config-loader.ts"() {
22533
+ init_config();
22534
+ init_types();
22535
+ init_errors2();
22536
+ init_logger();
22537
+ }
22538
+ });
22539
+
22091
22540
  // src/cli/repl/hooks/types.ts
22092
22541
  function isHookEvent(value) {
22093
22542
  return typeof value === "string" && HOOK_EVENTS.includes(value);
@@ -23415,8 +23864,8 @@ Generated by Corbat-Coco v0.1.0
23415
23864
 
23416
23865
  // src/cli/commands/init.ts
23417
23866
  function registerInitCommand(program2) {
23418
- program2.command("init").description("Initialize a new Corbat-Coco project").argument("[path]", "Project directory path", ".").option("-t, --template <template>", "Project template to use").option("-y, --yes", "Skip prompts and use defaults").option("--skip-discovery", "Skip the discovery phase (use existing spec)").action(async (path58, options) => {
23419
- await runInit(path58, options);
23867
+ program2.command("init").description("Initialize a new Corbat-Coco project").argument("[path]", "Project directory path", ".").option("-t, --template <template>", "Project template to use").option("-y, --yes", "Skip prompts and use defaults").option("--skip-discovery", "Skip the discovery phase (use existing spec)").action(async (path59, options) => {
23868
+ await runInit(path59, options);
23420
23869
  });
23421
23870
  }
23422
23871
  async function runInit(projectPath, options) {
@@ -23495,18 +23944,18 @@ async function gatherProjectInfo() {
23495
23944
  language
23496
23945
  };
23497
23946
  }
23498
- function getDefaultProjectInfo(path58) {
23499
- const name = path58 === "." ? "my-project" : path58.split("/").pop() || "my-project";
23947
+ function getDefaultProjectInfo(path59) {
23948
+ const name = path59 === "." ? "my-project" : path59.split("/").pop() || "my-project";
23500
23949
  return {
23501
23950
  name,
23502
23951
  description: "",
23503
23952
  language: "typescript"
23504
23953
  };
23505
23954
  }
23506
- async function checkExistingProject(path58) {
23955
+ async function checkExistingProject(path59) {
23507
23956
  try {
23508
23957
  const fs55 = await import('fs/promises');
23509
- await fs55.access(`${path58}/.coco`);
23958
+ await fs55.access(`${path59}/.coco`);
23510
23959
  return true;
23511
23960
  } catch {
23512
23961
  return false;
@@ -27007,20 +27456,20 @@ async function createCliPhaseContext(projectPath, _onUserInput) {
27007
27456
  },
27008
27457
  tools: {
27009
27458
  file: {
27010
- async read(path58) {
27459
+ async read(path59) {
27011
27460
  const fs55 = await import('fs/promises');
27012
- return fs55.readFile(path58, "utf-8");
27461
+ return fs55.readFile(path59, "utf-8");
27013
27462
  },
27014
- async write(path58, content) {
27463
+ async write(path59, content) {
27015
27464
  const fs55 = await import('fs/promises');
27016
27465
  const nodePath = await import('path');
27017
- await fs55.mkdir(nodePath.dirname(path58), { recursive: true });
27018
- await fs55.writeFile(path58, content, "utf-8");
27466
+ await fs55.mkdir(nodePath.dirname(path59), { recursive: true });
27467
+ await fs55.writeFile(path59, content, "utf-8");
27019
27468
  },
27020
- async exists(path58) {
27469
+ async exists(path59) {
27021
27470
  const fs55 = await import('fs/promises');
27022
27471
  try {
27023
- await fs55.access(path58);
27472
+ await fs55.access(path59);
27024
27473
  return true;
27025
27474
  } catch {
27026
27475
  return false;
@@ -27376,10 +27825,10 @@ function getPhaseStatusForPhase(phase) {
27376
27825
  }
27377
27826
  async function loadProjectState(cwd, config) {
27378
27827
  const fs55 = await import('fs/promises');
27379
- const path58 = await import('path');
27380
- const statePath = path58.join(cwd, ".coco", "state.json");
27381
- const backlogPath = path58.join(cwd, ".coco", "planning", "backlog.json");
27382
- const checkpointDir = path58.join(cwd, ".coco", "checkpoints");
27828
+ const path59 = await import('path');
27829
+ const statePath = path59.join(cwd, ".coco", "state.json");
27830
+ const backlogPath = path59.join(cwd, ".coco", "planning", "backlog.json");
27831
+ const checkpointDir = path59.join(cwd, ".coco", "checkpoints");
27383
27832
  let currentPhase = "idle";
27384
27833
  let metrics;
27385
27834
  let sprint;
@@ -28781,8 +29230,8 @@ async function saveConfig2(config) {
28781
29230
  await fs55.mkdir(dir, { recursive: true });
28782
29231
  await fs55.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2));
28783
29232
  }
28784
- function getNestedValue(obj, path58) {
28785
- const keys = path58.split(".");
29233
+ function getNestedValue(obj, path59) {
29234
+ const keys = path59.split(".");
28786
29235
  let current = obj;
28787
29236
  for (const key of keys) {
28788
29237
  if (current === null || current === void 0 || typeof current !== "object") {
@@ -28792,8 +29241,8 @@ function getNestedValue(obj, path58) {
28792
29241
  }
28793
29242
  return current;
28794
29243
  }
28795
- function setNestedValue(obj, path58, value) {
28796
- const keys = path58.split(".");
29244
+ function setNestedValue(obj, path59, value) {
29245
+ const keys = path59.split(".");
28797
29246
  let current = obj;
28798
29247
  for (let i = 0; i < keys.length - 1; i++) {
28799
29248
  const key = keys[i];
@@ -29068,6 +29517,21 @@ async function runDisableServer(name) {
29068
29517
  init_skills();
29069
29518
  init_skills2();
29070
29519
  init_paths();
29520
+ async function loadSkillsSettings() {
29521
+ try {
29522
+ const { loadConfig: loadConfig3 } = await Promise.resolve().then(() => (init_loader(), loader_exports));
29523
+ const config = await loadConfig3();
29524
+ return {
29525
+ globalDir: config.skills?.globalDir,
29526
+ globalDirs: config.skills?.globalDirs,
29527
+ projectDir: config.skills?.projectDir,
29528
+ projectDirs: config.skills?.projectDirs,
29529
+ disabled: config.skills?.disabled ?? []
29530
+ };
29531
+ } catch {
29532
+ return {};
29533
+ }
29534
+ }
29071
29535
  function registerSkillsCommand(program2) {
29072
29536
  const skillsCommand = program2.command("skills").description("Manage agent skills (SKILL.md and native)");
29073
29537
  skillsCommand.command("list").description("List all discovered skills").option("-s, --scope <scope>", "Filter by scope (builtin|global|project)").option("-k, --kind <kind>", "Filter by kind (markdown|native)").action(runList);
@@ -29075,6 +29539,16 @@ function registerSkillsCommand(program2) {
29075
29539
  skillsCommand.command("remove").description("Remove an installed skill").argument("<name>", "Skill name to remove").option("-g, --global", "Remove from global directory").option("-y, --yes", "Skip confirmation").action(runRemove);
29076
29540
  skillsCommand.command("info").description("Show details about a skill").argument("<name>", "Skill name").action(runInfo);
29077
29541
  skillsCommand.command("create").description("Create a new skill from template").argument("<name>", "Skill name").option("-g, --global", "Create in global directory").action(runCreate);
29542
+ skillsCommand.command("doctor").description("Explain skill discovery paths, conflicts, and winners").action(runDoctor);
29543
+ }
29544
+ async function loadSkillsDiscoveryOptions() {
29545
+ const settings = await loadSkillsSettings();
29546
+ return {
29547
+ globalDir: settings.globalDir,
29548
+ globalDirs: settings.globalDirs,
29549
+ projectDir: settings.projectDir,
29550
+ projectDirs: settings.projectDirs
29551
+ };
29078
29552
  }
29079
29553
  async function runList(options) {
29080
29554
  p26.intro(chalk.magenta("Skills"));
@@ -29082,7 +29556,8 @@ async function runList(options) {
29082
29556
  let allSkills;
29083
29557
  try {
29084
29558
  const builtins = getBuiltinSkillsForDiscovery();
29085
- allSkills = await discoverAllSkills(projectPath, builtins);
29559
+ const discoveryOptions = await loadSkillsDiscoveryOptions();
29560
+ allSkills = await discoverAllSkills(projectPath, builtins, discoveryOptions);
29086
29561
  } catch (error) {
29087
29562
  p26.log.error(
29088
29563
  `Failed to discover skills: ${error instanceof Error ? error.message : String(error)}`
@@ -29110,8 +29585,8 @@ async function runList(options) {
29110
29585
  }
29111
29586
  const scopeLabels = {
29112
29587
  builtin: "Builtin",
29113
- global: `Global (${CONFIG_PATHS.skills})`,
29114
- project: `Project (${projectPath}/.coco/skills/)`
29588
+ global: `Global (multi-dir, default includes ${CONFIG_PATHS.skills})`,
29589
+ project: `Project (.agents/skills/, .claude/skills/, ...)`
29115
29590
  };
29116
29591
  for (const [scope, skills] of byScope) {
29117
29592
  const label = scopeLabels[scope] ?? scope;
@@ -29133,7 +29608,7 @@ async function runAdd(source, options) {
29133
29608
  const isGithubShorthand = source.includes("/") && !isGitUrl;
29134
29609
  const isLocalPath = source.startsWith(".") || source.startsWith("/");
29135
29610
  if (isLocalPath) {
29136
- const targetDir = options.global ? CONFIG_PATHS.skills : path37__default.join(process.cwd(), ".coco", "skills");
29611
+ const targetDir = options.global ? CONFIG_PATHS.skills : path37__default.join(process.cwd(), ".agents", "skills");
29137
29612
  const sourcePath = path37__default.resolve(source);
29138
29613
  const skillName = path37__default.basename(sourcePath);
29139
29614
  const destPath = path37__default.join(targetDir, skillName);
@@ -29163,13 +29638,14 @@ async function runAdd(source, options) {
29163
29638
  spinner18.stop("Skill installed successfully");
29164
29639
  } catch (error) {
29165
29640
  spinner18.stop("Installation failed");
29166
- const stderr = error?.stderr?.toString()?.trim();
29641
+ const spawnError = error;
29642
+ const stderr = spawnError.stderr?.toString().trim();
29167
29643
  const msg = stderr || (error instanceof Error ? error.message : String(error));
29168
29644
  p26.log.error(`Failed to install skill: ${msg}`);
29169
- p26.log.info("Try installing manually: git clone the repo into .coco/skills/");
29645
+ p26.log.info("Try installing manually: git clone the repo into .agents/skills/");
29170
29646
  }
29171
29647
  } else if (isGitUrl) {
29172
- const targetDir = options.global ? CONFIG_PATHS.skills : path37__default.join(process.cwd(), ".coco", "skills");
29648
+ const targetDir = options.global ? CONFIG_PATHS.skills : path37__default.join(process.cwd(), ".agents", "skills");
29173
29649
  await fs34__default.mkdir(targetDir, { recursive: true });
29174
29650
  const skillName = source.split("/").pop()?.replace(".git", "") ?? "skill";
29175
29651
  const skillDir = path37__default.join(targetDir, skillName);
@@ -29184,7 +29660,8 @@ async function runAdd(source, options) {
29184
29660
  spinner18.stop(`Skill cloned to ${skillDir}`);
29185
29661
  } catch (error) {
29186
29662
  spinner18.stop("Clone failed");
29187
- const stderr = error?.stderr?.toString()?.trim();
29663
+ const spawnError = error;
29664
+ const stderr = spawnError.stderr?.toString().trim();
29188
29665
  const msg = stderr || (error instanceof Error ? error.message : String(error));
29189
29666
  p26.log.error(`Failed to clone skill: ${msg}`);
29190
29667
  }
@@ -29197,8 +29674,13 @@ async function runAdd(source, options) {
29197
29674
  }
29198
29675
  async function runRemove(name, options) {
29199
29676
  p26.intro(chalk.magenta("Remove Skill"));
29200
- const targetDir = options.global ? CONFIG_PATHS.skills : path37__default.join(process.cwd(), ".coco", "skills");
29201
- const skillPath = path37__default.join(targetDir, name);
29677
+ const targetDir = options.global ? CONFIG_PATHS.skills : path37__default.join(process.cwd(), ".agents", "skills");
29678
+ const skillPath = path37__default.resolve(targetDir, name);
29679
+ if (!skillPath.startsWith(path37__default.resolve(targetDir) + path37__default.sep)) {
29680
+ p26.log.error(`Invalid skill name: "${name}"`);
29681
+ p26.outro("");
29682
+ return;
29683
+ }
29202
29684
  try {
29203
29685
  await fs34__default.access(skillPath);
29204
29686
  } catch {
@@ -29226,7 +29708,8 @@ async function runInfo(name) {
29226
29708
  let allSkills;
29227
29709
  try {
29228
29710
  const builtins = getBuiltinSkillsForDiscovery();
29229
- allSkills = await discoverAllSkills(projectPath, builtins);
29711
+ const discoveryOptions = await loadSkillsDiscoveryOptions();
29712
+ allSkills = await discoverAllSkills(projectPath, builtins, discoveryOptions);
29230
29713
  } catch (error) {
29231
29714
  p26.log.error(
29232
29715
  `Failed to discover skills: ${error instanceof Error ? error.message : String(error)}`
@@ -29277,7 +29760,7 @@ async function runInfo(name) {
29277
29760
  }
29278
29761
  async function runCreate(name, options) {
29279
29762
  p26.intro(chalk.magenta("Create Skill"));
29280
- const targetDir = options.global ? CONFIG_PATHS.skills : path37__default.join(process.cwd(), ".coco", "skills");
29763
+ const targetDir = options.global ? CONFIG_PATHS.skills : path37__default.join(process.cwd(), ".agents", "skills");
29281
29764
  const skillDir = path37__default.join(targetDir, name);
29282
29765
  try {
29283
29766
  await fs34__default.access(skillDir);
@@ -29337,6 +29820,98 @@ when this skill is activated (automatically via matching or manually via /${name
29337
29820
  p26.log.info(`Edit ${path37__default.join(skillDir, "SKILL.md")} to add instructions.`);
29338
29821
  p26.outro("");
29339
29822
  }
29823
+ function registerWinner(winners, candidate, candidateScanOrder, scanOrderById) {
29824
+ const existing = winners.get(candidate.id);
29825
+ const existingOrder = scanOrderById.get(candidate.id) ?? -1;
29826
+ const scopePriority = { builtin: 1, global: 2, project: 3 };
29827
+ if (!existing || scopePriority[candidate.scope] > scopePriority[existing.scope] || scopePriority[candidate.scope] === scopePriority[existing.scope] && candidateScanOrder >= existingOrder) {
29828
+ winners.set(candidate.id, candidate);
29829
+ scanOrderById.set(candidate.id, candidateScanOrder);
29830
+ }
29831
+ }
29832
+ async function runDoctor() {
29833
+ p26.intro(chalk.magenta("Skills Doctor"));
29834
+ const projectPath = process.cwd();
29835
+ const settings = await loadSkillsSettings();
29836
+ const discoveryOptions = {
29837
+ globalDir: settings.globalDir,
29838
+ globalDirs: settings.globalDirs,
29839
+ projectDir: settings.projectDir,
29840
+ projectDirs: settings.projectDirs
29841
+ };
29842
+ const disabled = new Set(settings.disabled ?? []);
29843
+ const builtins = getBuiltinSkillsForDiscovery();
29844
+ const { globalDirs, projectDirs } = resolveDiscoveryDirs(projectPath, discoveryOptions);
29845
+ p26.log.step("Discovery paths (scan order, later wins within same scope)");
29846
+ console.log(chalk.dim(" Global:"));
29847
+ for (const dir of globalDirs) {
29848
+ console.log(chalk.dim(` - ${dir}`));
29849
+ }
29850
+ console.log(chalk.dim(" Project:"));
29851
+ for (const dir of projectDirs) {
29852
+ console.log(chalk.dim(` - ${dir}`));
29853
+ }
29854
+ console.log();
29855
+ const winners = /* @__PURE__ */ new Map();
29856
+ const winnerScanOrderById = /* @__PURE__ */ new Map();
29857
+ const candidatesById = /* @__PURE__ */ new Map();
29858
+ let scanOrder = 0;
29859
+ for (const skill of builtins) {
29860
+ const meta = nativeSkillToMetadata(skill, "builtin");
29861
+ meta.path = "<builtin>";
29862
+ registerWinner(winners, meta, scanOrder, winnerScanOrderById);
29863
+ candidatesById.set(meta.id, [meta]);
29864
+ scanOrder += 1;
29865
+ }
29866
+ for (const dir of globalDirs) {
29867
+ const metas = await scanSkillsDirectory(dir, "global");
29868
+ const names = metas.map((m) => m.name).sort().join(", ");
29869
+ p26.log.info(`Global ${dir}: ${metas.length} skills${names ? ` (${names})` : ""}`);
29870
+ for (const meta of metas) {
29871
+ registerWinner(winners, meta, scanOrder, winnerScanOrderById);
29872
+ const list = candidatesById.get(meta.id) ?? [];
29873
+ list.push(meta);
29874
+ candidatesById.set(meta.id, list);
29875
+ scanOrder += 1;
29876
+ }
29877
+ }
29878
+ for (const dir of projectDirs) {
29879
+ const metas = await scanSkillsDirectory(dir, "project");
29880
+ const names = metas.map((m) => m.name).sort().join(", ");
29881
+ p26.log.info(`Project ${dir}: ${metas.length} skills${names ? ` (${names})` : ""}`);
29882
+ for (const meta of metas) {
29883
+ registerWinner(winners, meta, scanOrder, winnerScanOrderById);
29884
+ const list = candidatesById.get(meta.id) ?? [];
29885
+ list.push(meta);
29886
+ candidatesById.set(meta.id, list);
29887
+ scanOrder += 1;
29888
+ }
29889
+ }
29890
+ const conflicts = Array.from(candidatesById.entries()).filter(([, list]) => list.length > 1).sort(([a], [b]) => a.localeCompare(b));
29891
+ const activeWinners = Array.from(winners.values()).filter((meta) => !disabled.has(meta.id));
29892
+ console.log();
29893
+ p26.log.step(`Final active skills: ${activeWinners.length}`);
29894
+ if (disabled.size > 0) {
29895
+ p26.log.info(`Disabled by config: ${Array.from(disabled).sort().join(", ")}`);
29896
+ }
29897
+ if (conflicts.length === 0) {
29898
+ p26.log.success("No naming conflicts detected.");
29899
+ p26.outro("");
29900
+ return;
29901
+ }
29902
+ p26.log.step(`Conflicts detected: ${conflicts.length}`);
29903
+ for (const [id, list] of conflicts) {
29904
+ const winner = winners.get(id);
29905
+ if (!winner) continue;
29906
+ const disabledTag = disabled.has(winner.id) ? chalk.yellow(" [DISABLED]") : "";
29907
+ console.log(` ${chalk.bold(id)} -> winner: ${winner.path} [${winner.scope}]${disabledTag}`);
29908
+ for (const candidate of list) {
29909
+ const marker = candidate.path === winner.path ? chalk.green("WIN") : chalk.dim("LOSE");
29910
+ console.log(` - ${marker} ${candidate.path} [${candidate.scope}]`);
29911
+ }
29912
+ }
29913
+ p26.outro("");
29914
+ }
29340
29915
 
29341
29916
  // src/cli/commands/check.ts
29342
29917
  init_evaluator();
@@ -29699,9 +30274,9 @@ function registerCheckCommand(program2) {
29699
30274
  // src/swarm/spec-parser.ts
29700
30275
  async function parseSwarmSpec(filePath) {
29701
30276
  const fs55 = await import('fs/promises');
29702
- const path58 = await import('path');
30277
+ const path59 = await import('path');
29703
30278
  const rawContent = await fs55.readFile(filePath, "utf-8");
29704
- const ext = path58.extname(filePath).toLowerCase();
30279
+ const ext = path59.extname(filePath).toLowerCase();
29705
30280
  if (ext === ".yaml" || ext === ".yml") {
29706
30281
  return parseYamlSpec(rawContent);
29707
30282
  }
@@ -30031,8 +30606,8 @@ var DEFAULT_AGENT_CONFIG = {
30031
30606
  };
30032
30607
  async function loadAgentConfig(projectPath) {
30033
30608
  const fs55 = await import('fs/promises');
30034
- const path58 = await import('path');
30035
- const configPath = path58.join(projectPath, ".coco", "swarm", "agents.json");
30609
+ const path59 = await import('path');
30610
+ const configPath = path59.join(projectPath, ".coco", "swarm", "agents.json");
30036
30611
  try {
30037
30612
  const raw = await fs55.readFile(configPath, "utf-8");
30038
30613
  const parsed = JSON.parse(raw);
@@ -30223,16 +30798,16 @@ async function createBoard(projectPath, spec) {
30223
30798
  }
30224
30799
  async function loadBoard(projectPath) {
30225
30800
  const fs55 = await import('fs/promises');
30226
- const path58 = await import('path');
30227
- const boardPath = path58.join(projectPath, ".coco", "swarm", "task-board.json");
30801
+ const path59 = await import('path');
30802
+ const boardPath = path59.join(projectPath, ".coco", "swarm", "task-board.json");
30228
30803
  const raw = await fs55.readFile(boardPath, "utf-8");
30229
30804
  return JSON.parse(raw);
30230
30805
  }
30231
30806
  async function saveBoard(projectPath, board) {
30232
30807
  const fs55 = await import('fs/promises');
30233
- const path58 = await import('path');
30234
- const boardDir = path58.join(projectPath, ".coco", "swarm");
30235
- const boardPath = path58.join(boardDir, "task-board.json");
30808
+ const path59 = await import('path');
30809
+ const boardDir = path59.join(projectPath, ".coco", "swarm");
30810
+ const boardPath = path59.join(boardDir, "task-board.json");
30236
30811
  await fs55.mkdir(boardDir, { recursive: true });
30237
30812
  await fs55.writeFile(boardPath, JSON.stringify(board, null, 2), "utf-8");
30238
30813
  }
@@ -30400,9 +30975,9 @@ async function defaultPromptHandler(q) {
30400
30975
  }
30401
30976
  async function writeAssumptionsFile(projectPath, projectName, questions, assumptions) {
30402
30977
  const fs55 = await import('fs/promises');
30403
- const path58 = await import('path');
30404
- const swarmDir = path58.join(projectPath, ".coco", "swarm");
30405
- const assumptionsPath = path58.join(swarmDir, "assumptions.md");
30978
+ const path59 = await import('path');
30979
+ const swarmDir = path59.join(projectPath, ".coco", "swarm");
30980
+ const assumptionsPath = path59.join(swarmDir, "assumptions.md");
30406
30981
  await fs55.mkdir(swarmDir, { recursive: true });
30407
30982
  const now = (/* @__PURE__ */ new Date()).toISOString();
30408
30983
  const content = [
@@ -30426,9 +31001,9 @@ async function writeAssumptionsFile(projectPath, projectName, questions, assumpt
30426
31001
  // src/swarm/events.ts
30427
31002
  async function appendSwarmEvent(projectPath, event) {
30428
31003
  const fs55 = await import('fs/promises');
30429
- const path58 = await import('path');
30430
- const eventsDir = path58.join(projectPath, ".coco", "swarm");
30431
- const eventsFile = path58.join(eventsDir, "events.jsonl");
31004
+ const path59 = await import('path');
31005
+ const eventsDir = path59.join(projectPath, ".coco", "swarm");
31006
+ const eventsFile = path59.join(eventsDir, "events.jsonl");
30432
31007
  await fs55.mkdir(eventsDir, { recursive: true });
30433
31008
  await fs55.appendFile(eventsFile, JSON.stringify(event) + "\n", "utf-8");
30434
31009
  }
@@ -30439,9 +31014,9 @@ function createEventId() {
30439
31014
  // src/swarm/knowledge.ts
30440
31015
  async function appendKnowledge(projectPath, entry) {
30441
31016
  const fs55 = await import('fs/promises');
30442
- const path58 = await import('path');
30443
- const knowledgeDir = path58.join(projectPath, ".coco", "swarm");
30444
- const knowledgeFile = path58.join(knowledgeDir, "knowledge.jsonl");
31017
+ const path59 = await import('path');
31018
+ const knowledgeDir = path59.join(projectPath, ".coco", "swarm");
31019
+ const knowledgeFile = path59.join(knowledgeDir, "knowledge.jsonl");
30445
31020
  await fs55.mkdir(knowledgeDir, { recursive: true });
30446
31021
  await fs55.appendFile(knowledgeFile, JSON.stringify(entry) + "\n", "utf-8");
30447
31022
  }
@@ -30748,10 +31323,10 @@ async function runSwarmLifecycle(options) {
30748
31323
  async function stageInit(ctx) {
30749
31324
  const { projectPath, spec } = ctx.options;
30750
31325
  const fs55 = await import('fs/promises');
30751
- const path58 = await import('path');
30752
- await fs55.mkdir(path58.join(projectPath, ".coco", "swarm"), { recursive: true });
31326
+ const path59 = await import('path');
31327
+ await fs55.mkdir(path59.join(projectPath, ".coco", "swarm"), { recursive: true });
30753
31328
  await fs55.mkdir(ctx.options.outputPath, { recursive: true });
30754
- const specSummaryPath = path58.join(projectPath, ".coco", "swarm", "spec-summary.json");
31329
+ const specSummaryPath = path59.join(projectPath, ".coco", "swarm", "spec-summary.json");
30755
31330
  const specSummary = {
30756
31331
  projectName: spec.projectName,
30757
31332
  description: spec.description,
@@ -30823,8 +31398,8 @@ async function stagePlan(ctx) {
30823
31398
  ]);
30824
31399
  await createBoard(projectPath, spec);
30825
31400
  const fs55 = await import('fs/promises');
30826
- const path58 = await import('path');
30827
- const planPath = path58.join(projectPath, ".coco", "swarm", "plan.json");
31401
+ const path59 = await import('path');
31402
+ const planPath = path59.join(projectPath, ".coco", "swarm", "plan.json");
30828
31403
  await fs55.writeFile(
30829
31404
  planPath,
30830
31405
  JSON.stringify({ pm: pmResult, architect: archResult, bestPractices: bpResult }, null, 2),
@@ -31041,7 +31616,7 @@ async function stageIntegrate(ctx) {
31041
31616
  async function stageOutput(ctx) {
31042
31617
  const { projectPath, outputPath } = ctx.options;
31043
31618
  const fs55 = await import('fs/promises');
31044
- const path58 = await import('path');
31619
+ const path59 = await import('path');
31045
31620
  const board = await loadBoard(projectPath);
31046
31621
  const featureResults = Array.from(ctx.featureResults.values());
31047
31622
  const summary = {
@@ -31056,7 +31631,7 @@ async function stageOutput(ctx) {
31056
31631
  globalScore: computeGlobalScore(featureResults)
31057
31632
  };
31058
31633
  await fs55.mkdir(outputPath, { recursive: true });
31059
- const summaryPath = path58.join(outputPath, "swarm-summary.json");
31634
+ const summaryPath = path59.join(outputPath, "swarm-summary.json");
31060
31635
  await fs55.writeFile(summaryPath, JSON.stringify(summary, null, 2), "utf-8");
31061
31636
  const passed = summary.globalScore >= ctx.options.minScore;
31062
31637
  await emitGate(projectPath, "global-score", passed, `Global score: ${summary.globalScore}`);
@@ -31403,8 +31978,8 @@ var SwarmOrchestrator = class {
31403
31978
  noQuestions = false,
31404
31979
  onProgress
31405
31980
  } = options;
31406
- const path58 = await import('path');
31407
- const projectPath = path58.dirname(path58.resolve(specFile));
31981
+ const path59 = await import('path');
31982
+ const projectPath = path59.dirname(path59.resolve(specFile));
31408
31983
  onProgress?.("init", `Parsing spec file: ${specFile}`);
31409
31984
  const spec = await parseSwarmSpec(specFile);
31410
31985
  onProgress?.("init", `Initializing provider: ${providerType}`);
@@ -31415,7 +31990,7 @@ var SwarmOrchestrator = class {
31415
31990
  await runSwarmLifecycle({
31416
31991
  spec,
31417
31992
  projectPath,
31418
- outputPath: path58.resolve(outputPath),
31993
+ outputPath: path59.resolve(outputPath),
31419
31994
  provider,
31420
31995
  agentConfig,
31421
31996
  minScore,
@@ -33092,7 +33667,13 @@ async function ensureConfiguredV2(config) {
33092
33667
  try {
33093
33668
  const recommended = getRecommendedModel(prov.id);
33094
33669
  const model = recommended?.id || prov.models[0]?.id || "";
33095
- const providerId = prov.id === "openai" && hasOpenAIOAuthTokens && !process.env[prov.envVar] ? "codex" : prov.id;
33670
+ let providerId = prov.id;
33671
+ if (prov.id === "openai" && hasOpenAIOAuthTokens && !process.env[prov.envVar]) {
33672
+ const tokenResult = await getOrRefreshOAuthToken("openai");
33673
+ if (!tokenResult) continue;
33674
+ process.env["OPENAI_CODEX_TOKEN"] = tokenResult.accessToken;
33675
+ providerId = "codex";
33676
+ }
33096
33677
  const provider = await createProvider(providerId, { model });
33097
33678
  if (await provider.isAvailable()) {
33098
33679
  return {
@@ -34330,8 +34911,8 @@ async function listTrustedProjects2(trustStore) {
34330
34911
  p26.log.message("");
34331
34912
  for (const project of projects) {
34332
34913
  const level = project.approvalLevel.toUpperCase().padEnd(5);
34333
- const path58 = project.path.length > 50 ? "..." + project.path.slice(-47) : project.path;
34334
- p26.log.message(` [${level}] ${path58}`);
34914
+ const path59 = project.path.length > 50 ? "..." + project.path.slice(-47) : project.path;
34915
+ p26.log.message(` [${level}] ${path59}`);
34335
34916
  p26.log.message(` Last accessed: ${new Date(project.lastAccessed).toLocaleString()}`);
34336
34917
  }
34337
34918
  p26.log.message("");
@@ -35562,8 +36143,8 @@ function displayRewindResult(result) {
35562
36143
  const fileName = filePath.split("/").pop() ?? filePath;
35563
36144
  console.log(`${chalk.green(String.fromCodePoint(10003))} Restored: ${fileName}`);
35564
36145
  }
35565
- for (const { path: path58, error } of result.filesFailed) {
35566
- const fileName = path58.split("/").pop() ?? path58;
36146
+ for (const { path: path59, error } of result.filesFailed) {
36147
+ const fileName = path59.split("/").pop() ?? path59;
35567
36148
  console.log(`${chalk.red(String.fromCodePoint(10007))} Failed: ${fileName} (${error})`);
35568
36149
  }
35569
36150
  if (result.conversationRestored) {
@@ -48250,8 +48831,8 @@ function formatToolSummary(toolName, input) {
48250
48831
  case "grep":
48251
48832
  case "search_files": {
48252
48833
  const pattern = String(input.pattern || "");
48253
- const path58 = input.path ? ` in ${input.path}` : "";
48254
- return `"${pattern}"${path58}`;
48834
+ const path59 = input.path ? ` in ${input.path}` : "";
48835
+ return `"${pattern}"${path59}`;
48255
48836
  }
48256
48837
  case "bash_exec": {
48257
48838
  const cmd = String(input.command || "");
@@ -48272,8 +48853,8 @@ function formatToolSummary(toolName, input) {
48272
48853
  function formatUrl(url) {
48273
48854
  try {
48274
48855
  const u = new URL(url);
48275
- const path58 = u.pathname.replace(/\/$/, "");
48276
- const display = path58 ? `${u.hostname} \u203A ${path58.slice(1)}` : u.hostname;
48856
+ const path59 = u.pathname.replace(/\/$/, "");
48857
+ const display = path59 ? `${u.hostname} \u203A ${path59.slice(1)}` : u.hostname;
48277
48858
  const max = Math.max(getTerminalWidth2() - 20, 50);
48278
48859
  return display.length > max ? display.slice(0, max - 1) + "\u2026" : display;
48279
48860
  } catch {
@@ -50334,6 +50915,7 @@ var ParallelToolExecutor = class {
50334
50915
  if (signal?.aborted) {
50335
50916
  return { executed: null, skipped: true, reason: "Operation cancelled" };
50336
50917
  }
50918
+ let hookWarning = null;
50337
50919
  if (hookRegistry && hookExecutor) {
50338
50920
  const preContext = {
50339
50921
  event: "PreToolUse",
@@ -50343,7 +50925,23 @@ var ParallelToolExecutor = class {
50343
50925
  projectPath: projectPath ?? process.cwd(),
50344
50926
  timestamp: /* @__PURE__ */ new Date()
50345
50927
  };
50346
- const preResult = await hookExecutor.executeHooks(hookRegistry, preContext);
50928
+ let preResult;
50929
+ try {
50930
+ preResult = await hookExecutor.executeHooks(hookRegistry, preContext);
50931
+ } catch (error) {
50932
+ if (isAbortError(error, signal)) {
50933
+ return { executed: null, skipped: true, reason: "Operation cancelled" };
50934
+ }
50935
+ const msg = error instanceof Error ? error.message : String(error);
50936
+ hookWarning = `PreToolUse hook failed: ${msg}`;
50937
+ preResult = {
50938
+ event: "PreToolUse",
50939
+ results: [],
50940
+ allSucceeded: false,
50941
+ shouldContinue: true,
50942
+ duration: 0
50943
+ };
50944
+ }
50347
50945
  onHookExecuted?.("PreToolUse", preResult);
50348
50946
  if (!preResult.shouldContinue) {
50349
50947
  return {
@@ -50361,9 +50959,27 @@ var ParallelToolExecutor = class {
50361
50959
  }
50362
50960
  onToolStart?.(toolCall, index, total);
50363
50961
  const startTime = performance.now();
50364
- const result = await registry.execute(toolCall.name, toolCall.input, { signal });
50962
+ let result;
50963
+ try {
50964
+ result = await registry.execute(toolCall.name, toolCall.input, { signal });
50965
+ } catch (error) {
50966
+ if (isAbortError(error, signal)) {
50967
+ return { executed: null, skipped: true, reason: "Operation cancelled" };
50968
+ }
50969
+ const msg = error instanceof Error ? error.message : String(error);
50970
+ result = {
50971
+ success: false,
50972
+ error: `Unexpected error in ${toolCall.name}: ${msg}`,
50973
+ duration: 0
50974
+ };
50975
+ }
50365
50976
  const duration = performance.now() - startTime;
50366
- const output = result.success ? JSON.stringify(result.data, null, 2) : result.error ?? "Unknown error";
50977
+ const outputParts = [];
50978
+ if (hookWarning) outputParts.push(hookWarning);
50979
+ outputParts.push(
50980
+ result.success ? JSON.stringify(result.data, null, 2) : result.error ?? "Unknown error"
50981
+ );
50982
+ const output = outputParts.filter(Boolean).join("\n");
50367
50983
  const executedCall = {
50368
50984
  id: toolCall.id,
50369
50985
  name: toolCall.name,
@@ -50391,8 +51007,25 @@ var ParallelToolExecutor = class {
50391
51007
  projectPath: projectPath ?? process.cwd(),
50392
51008
  timestamp: /* @__PURE__ */ new Date()
50393
51009
  };
50394
- const postResult = await hookExecutor.executeHooks(hookRegistry, postContext);
50395
- onHookExecuted?.("PostToolUse", postResult);
51010
+ try {
51011
+ const postResult = await hookExecutor.executeHooks(hookRegistry, postContext);
51012
+ onHookExecuted?.("PostToolUse", postResult);
51013
+ } catch (error) {
51014
+ if (isAbortError(error, signal)) {
51015
+ return { executed: null, skipped: true, reason: "Operation cancelled" };
51016
+ }
51017
+ const msg = error instanceof Error ? error.message : String(error);
51018
+ const warningResult = {
51019
+ event: "PostToolUse",
51020
+ results: [],
51021
+ allSucceeded: false,
51022
+ shouldContinue: true,
51023
+ duration: 0
51024
+ };
51025
+ onHookExecuted?.("PostToolUse", warningResult);
51026
+ executedCall.result.output = `${executedCall.result.output}
51027
+ PostToolUse hook failed: ${msg}`;
51028
+ }
50396
51029
  }
50397
51030
  onToolEnd?.(executedCall);
50398
51031
  return { executed: executedCall, skipped: false };
@@ -50493,9 +51126,20 @@ async function executeAgentTurn(session, userMessage, provider, toolRegistry, op
50493
51126
  const allTools = toolRegistry.getToolDefinitionsForLLM();
50494
51127
  const tools = session.planMode ? filterReadOnlyTools(allTools) : allTools;
50495
51128
  let iteration = 0;
50496
- const maxIterations = session.config.agent.maxToolIterations;
51129
+ let maxIterations = session.config.agent.maxToolIterations;
51130
+ const HARD_MAX_ITERATIONS = 100;
51131
+ const MAX_AUTO_ITERATION_EXTENSIONS = 2;
51132
+ const AUTO_ITERATION_EXTENSION_SIZE = Math.max(
51133
+ 5,
51134
+ Math.ceil(session.config.agent.maxToolIterations * 0.4)
51135
+ );
51136
+ let autoIterationExtensionsUsed = 0;
50497
51137
  const toolErrorCounts = /* @__PURE__ */ new Map();
50498
51138
  const MAX_CONSECUTIVE_TOOL_ERRORS = 3;
51139
+ const MAX_NO_TOOL_RECOVERY_ATTEMPTS = 3;
51140
+ let noToolRecoveryAttempts = 0;
51141
+ let lastSuccessIterationSignature = "";
51142
+ let repeatedSuccessIterationCount = 0;
50499
51143
  const ITERATION_LIMIT_WARNING_RATIO = 0.75;
50500
51144
  const ITERATION_LIMIT_SUMMARY_PROMPT = `[System: You have now used all allowed iterations and tool calls are no longer available. Write your final response immediately: (1) briefly state what was completed, (2) describe what still needs to be done, (3) give specific next steps so the user can continue. Be concise and direct.]`;
50501
51145
  const INLINE_RESULT_MAX_CHARS = 8e3;
@@ -50510,6 +51154,38 @@ async function executeAgentTurn(session, userMessage, provider, toolRegistry, op
50510
51154
  [... ${omitted.toLocaleString()} characters omitted \u2014 use read_file with offset/limit to retrieve more of '${toolName}' output ...]
50511
51155
  ${tail}`;
50512
51156
  }
51157
+ function shouldRecoverNoToolTurn(stopReason, content) {
51158
+ const trimmed = content.trim();
51159
+ if (stopReason === "tool_use") {
51160
+ return {
51161
+ recover: true,
51162
+ reason: "The previous response indicated tool use, but no tool calls were received. Re-emit the tool call(s) now."
51163
+ };
51164
+ }
51165
+ if (stopReason === "max_tokens" && trimmed.length === 0) {
51166
+ return {
51167
+ recover: true,
51168
+ reason: "The previous response was cut off before producing any usable output. Continue immediately."
51169
+ };
51170
+ }
51171
+ const planningOnly = trimmed.length > 0 && trimmed.length < 320 && /^(voy a|ahora voy|i('| )?ll|i will|let me|starting|preparing|activating|continuo|contin[uú]o|de acuerdo[, ]+voy)\b/i.test(
51172
+ trimmed
51173
+ );
51174
+ if (planningOnly) {
51175
+ return {
51176
+ recover: true,
51177
+ reason: "Do not only describe the next step. Execute it now with concrete tool calls."
51178
+ };
51179
+ }
51180
+ return { recover: false, reason: "" };
51181
+ }
51182
+ function shouldAutoExtendIterationBudget(latestExecuted, latestToolResults, isInErrorLoop) {
51183
+ if (isInErrorLoop) return false;
51184
+ if (latestExecuted.length === 0) return false;
51185
+ if (latestToolResults.length === 0) return false;
51186
+ if (repeatedSuccessIterationCount >= 2) return false;
51187
+ return latestExecuted.some((c) => c.result.success);
51188
+ }
50513
51189
  while (iteration < maxIterations) {
50514
51190
  iteration++;
50515
51191
  const isLastIteration = iteration === maxIterations;
@@ -50655,9 +51331,24 @@ ${tail}`;
50655
51331
  });
50656
51332
  continue;
50657
51333
  }
51334
+ const noToolRecovery = shouldRecoverNoToolTurn(lastStopReason, responseContent);
51335
+ if (noToolRecovery.recover && noToolRecoveryAttempts < MAX_NO_TOOL_RECOVERY_ATTEMPTS && iteration < maxIterations) {
51336
+ noToolRecoveryAttempts++;
51337
+ addMessage(session, {
51338
+ role: "assistant",
51339
+ content: responseContent || "[No output returned in previous step.]"
51340
+ });
51341
+ addMessage(session, {
51342
+ role: "user",
51343
+ content: `[System: ${noToolRecovery.reason}]`
51344
+ });
51345
+ continue;
51346
+ }
51347
+ noToolRecoveryAttempts = 0;
50658
51348
  addMessage(session, { role: "assistant", content: responseContent });
50659
51349
  break;
50660
51350
  }
51351
+ noToolRecoveryAttempts = 0;
50661
51352
  const response = {
50662
51353
  content: responseContent,
50663
51354
  toolCalls: collectedToolCalls
@@ -50665,6 +51356,7 @@ ${tail}`;
50665
51356
  const toolResults = [];
50666
51357
  const toolUses = [];
50667
51358
  let turnAborted = false;
51359
+ let currentIterationExecuted = [];
50668
51360
  const totalTools = response.toolCalls.length;
50669
51361
  const confirmedTools = [];
50670
51362
  const declinedTools = /* @__PURE__ */ new Map();
@@ -50749,6 +51441,7 @@ ${tail}`;
50749
51441
  });
50750
51442
  for (const executed of parallelResult.executed) {
50751
51443
  executedTools.push(executed);
51444
+ currentIterationExecuted.push(executed);
50752
51445
  if (executed.name === "manage_permissions" && executed.result.success) {
50753
51446
  const action = executed.input.action;
50754
51447
  const patterns = executed.input.patterns;
@@ -50876,11 +51569,24 @@ ${tail}`;
50876
51569
  }
50877
51570
  }
50878
51571
  }
51572
+ if (currentIterationExecuted.length > 0 && currentIterationExecuted.every((c) => c.result.success)) {
51573
+ const iterationSignature = currentIterationExecuted.map((c) => `${c.name}:${JSON.stringify(c.input)}`).join("|");
51574
+ if (iterationSignature === lastSuccessIterationSignature) {
51575
+ repeatedSuccessIterationCount++;
51576
+ } else {
51577
+ lastSuccessIterationSignature = iterationSignature;
51578
+ repeatedSuccessIterationCount = 0;
51579
+ }
51580
+ } else {
51581
+ lastSuccessIterationSignature = "";
51582
+ repeatedSuccessIterationCount = 0;
51583
+ }
51584
+ const canAutoExtendNow = isLastIteration && toolResults.length > 0 && autoIterationExtensionsUsed < MAX_AUTO_ITERATION_EXTENSIONS && maxIterations < HARD_MAX_ITERATIONS && shouldAutoExtendIterationBudget(executedTools, toolResults, stuckInErrorLoop);
50879
51585
  if (toolResults.length > 0) {
50880
51586
  const warningThreshold = Math.ceil(maxIterations * ITERATION_LIMIT_WARNING_RATIO);
50881
51587
  const lastIdx = toolResults.length - 1;
50882
51588
  const last = toolResults[lastIdx];
50883
- if (isLastIteration && !stuckInErrorLoop) {
51589
+ if (isLastIteration && !stuckInErrorLoop && !canAutoExtendNow) {
50884
51590
  toolResults[lastIdx] = {
50885
51591
  ...last,
50886
51592
  content: typeof last.content === "string" ? last.content + `
@@ -50942,6 +51648,16 @@ ${ITERATION_LIMIT_SUMMARY_PROMPT}` : ITERATION_LIMIT_SUMMARY_PROMPT
50942
51648
  }
50943
51649
  break;
50944
51650
  }
51651
+ if (canAutoExtendNow) {
51652
+ autoIterationExtensionsUsed++;
51653
+ const oldMax = maxIterations;
51654
+ maxIterations = Math.min(maxIterations + AUTO_ITERATION_EXTENSION_SIZE, HARD_MAX_ITERATIONS);
51655
+ addMessage(session, {
51656
+ role: "user",
51657
+ content: `[System: Iteration budget auto-extended from ${oldMax} to ${maxIterations} because progress is being made. Continue execution and prioritize completing the remaining critical work.]`
51658
+ });
51659
+ continue;
51660
+ }
50945
51661
  if (isLastIteration && toolResults.length > 0) {
50946
51662
  let summaryThinkingEnded = false;
50947
51663
  options.onThinkingStart?.();
@@ -51606,7 +52322,7 @@ async function startRepl(options = {}) {
51606
52322
  }
51607
52323
  initializeContextManager(session, provider);
51608
52324
  const { createLLMClassifier: createLLMClassifier2 } = await Promise.resolve().then(() => (init_llm_classifier(), llm_classifier_exports));
51609
- const llmClassifier = createLLMClassifier2(provider);
52325
+ let llmClassifier = createLLMClassifier2(provider);
51610
52326
  const { detectProjectStack: detectProjectStack2 } = await Promise.resolve().then(() => (init_stack_detector(), stack_detector_exports));
51611
52327
  const [projectContext] = await Promise.all([
51612
52328
  detectProjectStack2(projectPath),
@@ -51658,7 +52374,13 @@ async function startRepl(options = {}) {
51658
52374
  const { registerMCPTools: registerMCPTools2 } = await Promise.resolve().then(() => (init_tools(), tools_exports));
51659
52375
  const mcpRegistry = new MCPRegistryImpl2();
51660
52376
  await mcpRegistry.load();
51661
- const enabledServers = mcpRegistry.listEnabledServers();
52377
+ const registryServers = mcpRegistry.listEnabledServers();
52378
+ const { loadProjectMCPFile: loadProjectMCPFile2, mergeMCPConfigs: mergeMCPConfigs2 } = await Promise.resolve().then(() => (init_config_loader(), config_loader_exports));
52379
+ const projectServers = await loadProjectMCPFile2(process.cwd());
52380
+ const enabledServers = mergeMCPConfigs2(
52381
+ registryServers,
52382
+ projectServers.filter((s) => s.enabled !== false)
52383
+ );
51662
52384
  if (enabledServers.length > 0) {
51663
52385
  mcpManager = getMCPServerManager2();
51664
52386
  let connections;
@@ -51748,6 +52470,127 @@ async function startRepl(options = {}) {
51748
52470
  let warned75 = false;
51749
52471
  let warned90 = false;
51750
52472
  let consecutiveErrors = 0;
52473
+ const AUTO_SWITCH_THRESHOLD = 2;
52474
+ const autoSwitchHistory = /* @__PURE__ */ new Set();
52475
+ const enableAutoSwitchProvider = session.config.agent.enableAutoSwitchProvider === true;
52476
+ const buildReplayMessage = (message) => {
52477
+ if (typeof message === "string") {
52478
+ const trimmed = message.trim();
52479
+ return trimmed.length > 0 ? message : null;
52480
+ }
52481
+ const textParts = [];
52482
+ let imageCount = 0;
52483
+ for (const block of message) {
52484
+ if (block.type === "text" && typeof block.text === "string" && block.text.trim().length > 0) {
52485
+ textParts.push(block.text.trim());
52486
+ } else if (block.type === "image") {
52487
+ imageCount++;
52488
+ }
52489
+ }
52490
+ const text13 = textParts.join("\n\n").trim();
52491
+ if (text13.length > 0) {
52492
+ if (imageCount > 0) {
52493
+ return `${text13}
52494
+
52495
+ [System: The original request included ${imageCount} image(s). Use the image context already provided in this conversation.]`;
52496
+ }
52497
+ return text13;
52498
+ }
52499
+ if (imageCount > 0) {
52500
+ return `[System: Retry the previous image-based user request (${imageCount} image(s)). Use the existing image context in the conversation and do not repeat the same failed action.]`;
52501
+ }
52502
+ return null;
52503
+ };
52504
+ const showRecoveryAlternatives = () => {
52505
+ console.log(chalk.yellow(" Choose how to continue:"));
52506
+ console.log(chalk.dim(" 1. /provider \u2192 switch provider"));
52507
+ console.log(chalk.dim(" 2. /model \u2192 switch model"));
52508
+ console.log(chalk.dim(" 3. Retry with a narrower scope/task"));
52509
+ console.log(chalk.dim(" 4. If needed, share constraints so Coco can adapt strategy"));
52510
+ if (!enableAutoSwitchProvider) {
52511
+ console.log(chalk.dim(" 5. (Optional) enable `agent.enableAutoSwitchProvider` in config"));
52512
+ }
52513
+ };
52514
+ const getAutoSwitchCandidates = (current) => {
52515
+ const ordered = [];
52516
+ const push = (p45) => {
52517
+ if (p45 !== current && !ordered.includes(p45)) ordered.push(p45);
52518
+ };
52519
+ if (current === "openai") {
52520
+ push("codex");
52521
+ push("kimi");
52522
+ push("openrouter");
52523
+ } else if (current === "codex") {
52524
+ push("openai");
52525
+ push("openrouter");
52526
+ push("anthropic");
52527
+ } else if (current === "anthropic") {
52528
+ push("openai");
52529
+ push("codex");
52530
+ push("gemini");
52531
+ } else if (current === "gemini") {
52532
+ push("openai");
52533
+ push("codex");
52534
+ push("anthropic");
52535
+ } else if (current === "kimi") {
52536
+ push("openai");
52537
+ push("codex");
52538
+ push("openrouter");
52539
+ }
52540
+ const genericOrder = [
52541
+ "codex",
52542
+ "openai",
52543
+ "anthropic",
52544
+ "gemini",
52545
+ "kimi",
52546
+ "openrouter",
52547
+ "deepseek",
52548
+ "groq",
52549
+ "mistral",
52550
+ "together",
52551
+ "qwen",
52552
+ "lmstudio",
52553
+ "ollama"
52554
+ ];
52555
+ for (const p45 of genericOrder) push(p45);
52556
+ return ordered;
52557
+ };
52558
+ const attemptAutoProviderSwitch = async (reason, originalMessage) => {
52559
+ if (!originalMessage) return false;
52560
+ const currentType = session.config.provider.type;
52561
+ const candidates = getAutoSwitchCandidates(currentType);
52562
+ for (const candidate of candidates) {
52563
+ const edge = `${currentType}->${candidate}`;
52564
+ if (autoSwitchHistory.has(edge)) continue;
52565
+ try {
52566
+ const nextInternalId = getInternalProviderId(candidate);
52567
+ const nextProvider = await createProvider(nextInternalId, {
52568
+ maxTokens: session.config.provider.maxTokens
52569
+ });
52570
+ const ok = await nextProvider.isAvailable();
52571
+ if (!ok) {
52572
+ autoSwitchHistory.add(edge);
52573
+ continue;
52574
+ }
52575
+ provider = nextProvider;
52576
+ session.config.provider.type = candidate;
52577
+ session.config.provider.model = getDefaultModel(candidate);
52578
+ setAgentProvider(provider);
52579
+ initializeContextManager(session, provider);
52580
+ llmClassifier = createLLMClassifier2(provider);
52581
+ autoSwitchHistory.add(edge);
52582
+ console.log(
52583
+ chalk.cyan(
52584
+ ` \u21BA Auto-switched provider: ${currentType} \u2192 ${candidate} (${reason.slice(0, 80)})`
52585
+ )
52586
+ );
52587
+ return true;
52588
+ } catch {
52589
+ autoSwitchHistory.add(edge);
52590
+ }
52591
+ }
52592
+ return false;
52593
+ };
51751
52594
  while (true) {
51752
52595
  let autoInput = null;
51753
52596
  if (pendingQueuedMessages.length > 0) {
@@ -51867,6 +52710,7 @@ ${imagePrompts}`.trim() : imagePrompts;
51867
52710
  agentMessage = input ?? "";
51868
52711
  }
51869
52712
  const originalUserMessage = typeof agentMessage === "string" ? agentMessage : null;
52713
+ const replayUserMessage = buildReplayMessage(agentMessage);
51870
52714
  if (session.skillRegistry && session.skillRegistry.config.autoActivate !== false && typeof agentMessage === "string" && agentMessage.length > 0) {
51871
52715
  const matches = session.skillRegistry.findRelevantSkills(agentMessage, 3, 0.4);
51872
52716
  const mdMatches = matches.filter((m) => m.skill.kind === "markdown");
@@ -52141,12 +52985,12 @@ ${imagePrompts}`.trim() : imagePrompts;
52141
52985
  "\n\n## The user interrupted and modified the task:",
52142
52986
  `- ${modParts}`,
52143
52987
  toolSummary,
52144
- `Apply the user's modification to the original task: "${originalUserMessage || ""}"`
52988
+ `Apply the user's modification to the original task: "${replayUserMessage || ""}"`
52145
52989
  ].join("\n");
52146
52990
  pendingInterruptionContext = ctx;
52147
52991
  pendingModificationPreview = modParts;
52148
- if (originalUserMessage) {
52149
- pendingQueuedMessages = [originalUserMessage, ...turnQueuedMessages];
52992
+ if (replayUserMessage) {
52993
+ pendingQueuedMessages = [replayUserMessage, ...turnQueuedMessages];
52150
52994
  } else {
52151
52995
  pendingQueuedMessages = turnQueuedMessages;
52152
52996
  }
@@ -52181,27 +53025,35 @@ ${imagePrompts}`.trim() : imagePrompts;
52181
53025
  }
52182
53026
  if (result.error) {
52183
53027
  session.messages.length = preCallMessageLength;
52184
- if (originalUserMessage !== null && consecutiveErrors < MAX_CONSECUTIVE_ERRORS && !isNonRetryableProviderError(new Error(result.error))) {
53028
+ if (replayUserMessage !== null && consecutiveErrors < MAX_CONSECUTIVE_ERRORS && !isNonRetryableProviderError(new Error(result.error))) {
52185
53029
  consecutiveErrors++;
52186
53030
  const humanized = humanizeProviderError(new Error(result.error));
52187
53031
  renderError(humanized);
53032
+ let switched = false;
53033
+ if (enableAutoSwitchProvider && consecutiveErrors >= AUTO_SWITCH_THRESHOLD) {
53034
+ switched = await attemptAutoProviderSwitch(humanized, replayUserMessage);
53035
+ } else if (!enableAutoSwitchProvider && consecutiveErrors >= AUTO_SWITCH_THRESHOLD) {
53036
+ console.log(
53037
+ chalk.dim(
53038
+ " Tip: repeated provider errors detected. Use /provider, or enable `agent.enableAutoSwitchProvider`."
53039
+ )
53040
+ );
53041
+ }
52188
53042
  console.log(
52189
53043
  chalk.dim(
52190
53044
  ` \u21BB Retrying automatically (attempt ${consecutiveErrors}/${MAX_CONSECUTIVE_ERRORS})\u2026`
52191
53045
  )
52192
53046
  );
52193
- const recoveryPrefix = `[System: The previous attempt failed with: "${humanized}". Please try a different approach, tool, or method to complete the task. Do NOT repeat the exact same action that caused the error.]
53047
+ const recoveryPrefix = (switched ? `[System: Provider auto-switched to "${session.config.provider.type}" after repeated failures. Adapt your strategy to this provider and continue.]
53048
+
53049
+ ` : "") + `[System: The previous attempt failed with: "${humanized}". Please try a different approach, tool, or method to complete the task. Do NOT repeat the exact same action that caused the error.]
52194
53050
 
52195
53051
  `;
52196
- pendingQueuedMessages = [recoveryPrefix + originalUserMessage];
53052
+ pendingQueuedMessages = [recoveryPrefix + replayUserMessage];
52197
53053
  } else {
52198
53054
  renderError(result.error);
52199
- console.log(
52200
- chalk.dim(" Recovery failed or error is non-retryable. Returning to prompt.")
52201
- );
52202
- console.log(
52203
- chalk.dim(" Tip: Try /provider or /model to switch, then rephrase and retry.")
52204
- );
53055
+ console.log(chalk.dim(" Automatic recovery stopped for this turn."));
53056
+ showRecoveryAlternatives();
52205
53057
  consecutiveErrors = 0;
52206
53058
  }
52207
53059
  console.log();
@@ -52365,39 +53217,55 @@ ${imagePrompts}`.trim() : imagePrompts;
52365
53217
  console.log(chalk.dim(" \u2022 Check your subscription status and billing"));
52366
53218
  console.log(chalk.dim(" \u2022 Try a different provider: /provider"));
52367
53219
  console.log(chalk.dim(" \u2022 Switch to a different model: /model"));
53220
+ if (!enableAutoSwitchProvider) {
53221
+ console.log(
53222
+ chalk.dim(" \u2022 Optional: enable `agent.enableAutoSwitchProvider` for auto-failover")
53223
+ );
53224
+ }
53225
+ showRecoveryAlternatives();
52368
53226
  console.log();
52369
53227
  continue;
52370
53228
  }
52371
- if (originalUserMessage !== null && consecutiveErrors < MAX_CONSECUTIVE_ERRORS && !isNonRetryableProviderError(error)) {
53229
+ if (replayUserMessage !== null && consecutiveErrors < MAX_CONSECUTIVE_ERRORS && !isNonRetryableProviderError(error)) {
52372
53230
  consecutiveErrors++;
52373
53231
  session.messages.length = preCallMessageLength;
52374
53232
  const humanized = humanizeProviderError(error);
52375
53233
  renderError(humanized);
53234
+ let switched = false;
53235
+ if (enableAutoSwitchProvider && consecutiveErrors >= AUTO_SWITCH_THRESHOLD) {
53236
+ switched = await attemptAutoProviderSwitch(humanized, replayUserMessage);
53237
+ } else if (!enableAutoSwitchProvider && consecutiveErrors >= AUTO_SWITCH_THRESHOLD) {
53238
+ console.log(
53239
+ chalk.dim(
53240
+ " Tip: repeated provider errors detected. Use /provider, or enable `agent.enableAutoSwitchProvider`."
53241
+ )
53242
+ );
53243
+ }
52376
53244
  console.log(
52377
53245
  chalk.dim(
52378
53246
  ` \u21BB Retrying automatically (attempt ${consecutiveErrors}/${MAX_CONSECUTIVE_ERRORS})\u2026`
52379
53247
  )
52380
53248
  );
52381
- const recoveryPrefix = `[System: The previous attempt failed with the following error: "${humanized}". Please try a different approach, tool, or method to complete the task. Do NOT repeat the exact same action that caused the error.]
53249
+ const recoveryPrefix = (switched ? `[System: Provider auto-switched to "${session.config.provider.type}" after repeated failures. Adapt your strategy to this provider and continue.]
53250
+
53251
+ ` : "") + `[System: The previous attempt failed with the following error: "${humanized}". Please try a different approach, tool, or method to complete the task. Do NOT repeat the exact same action that caused the error.]
52382
53252
 
52383
53253
  `;
52384
- pendingQueuedMessages = [recoveryPrefix + originalUserMessage];
53254
+ pendingQueuedMessages = [recoveryPrefix + replayUserMessage];
52385
53255
  continue;
52386
53256
  }
52387
53257
  if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
52388
53258
  consecutiveErrors = 0;
52389
53259
  session.messages.length = preCallMessageLength;
52390
53260
  renderError(errorMsg);
52391
- console.log(chalk.dim(" Recovery failed after multiple attempts. Returning to prompt."));
52392
- console.log(
52393
- chalk.dim(" Tip: Try /provider or /model to switch, then rephrase and retry.")
52394
- );
53261
+ console.log(chalk.dim(" Recovery exhausted after multiple attempts."));
53262
+ showRecoveryAlternatives();
52395
53263
  continue;
52396
53264
  }
52397
53265
  session.messages.length = preCallMessageLength;
52398
53266
  consecutiveErrors = 0;
52399
53267
  renderError(errorMsg);
52400
- console.log(chalk.dim(" Tip: Try /provider or /model to switch, then rephrase and retry."));
53268
+ showRecoveryAlternatives();
52401
53269
  } finally {
52402
53270
  clearSpinner();
52403
53271
  if (originalSystemPrompt !== void 0) {