@corbat-tech/coco 2.4.2 → 2.5.1

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
@@ -10,6 +10,7 @@ import os4__default, { homedir } from 'os';
10
10
  import * as fs32 from 'fs/promises';
11
11
  import fs32__default, { mkdir, writeFile, readFile, access, readdir, rm } from 'fs/promises';
12
12
  import JSON5 from 'json5';
13
+ import { Logger } from 'tslog';
13
14
  import Anthropic from '@anthropic-ai/sdk';
14
15
  import OpenAI from 'openai';
15
16
  import { jsonrepair } from 'jsonrepair';
@@ -21,7 +22,6 @@ import chalk25 from 'chalk';
21
22
  import { execSync, execFileSync, spawn, execFile, exec } from 'child_process';
22
23
  import { promisify } from 'util';
23
24
  import { GoogleGenerativeAI, FunctionCallingMode } from '@google/generative-ai';
24
- import { Logger } from 'tslog';
25
25
  import matter from 'gray-matter';
26
26
  import { minimatch } from 'minimatch';
27
27
  import hljs from 'highlight.js/lib/core';
@@ -760,6 +760,106 @@ var init_retry = __esm({
760
760
  };
761
761
  }
762
762
  });
763
+
764
+ // src/utils/logger.ts
765
+ var logger_exports = {};
766
+ __export(logger_exports, {
767
+ createChildLogger: () => createChildLogger,
768
+ createLogger: () => createLogger,
769
+ getLogger: () => getLogger,
770
+ initializeLogging: () => initializeLogging,
771
+ logEvent: () => logEvent,
772
+ logTiming: () => logTiming,
773
+ setLogger: () => setLogger
774
+ });
775
+ function levelToNumber(level) {
776
+ const levels = {
777
+ silly: 0,
778
+ trace: 1,
779
+ debug: 2,
780
+ info: 3,
781
+ warn: 4,
782
+ error: 5,
783
+ fatal: 6
784
+ };
785
+ return levels[level];
786
+ }
787
+ function createLogger(config = {}) {
788
+ const finalConfig = { ...DEFAULT_CONFIG, ...config };
789
+ const logger = new Logger({
790
+ name: finalConfig.name,
791
+ minLevel: levelToNumber(finalConfig.level),
792
+ prettyLogTemplate: finalConfig.prettyPrint ? "{{yyyy}}-{{mm}}-{{dd}} {{hh}}:{{MM}}:{{ss}} {{logLevelName}} [{{name}}] " : void 0,
793
+ prettyLogTimeZone: "local",
794
+ stylePrettyLogs: finalConfig.prettyPrint
795
+ });
796
+ if (finalConfig.logToFile && finalConfig.logDir) {
797
+ setupFileLogging(logger, finalConfig.logDir, finalConfig.name);
798
+ }
799
+ return logger;
800
+ }
801
+ function setupFileLogging(logger, logDir, name) {
802
+ if (!fs49__default.existsSync(logDir)) {
803
+ fs49__default.mkdirSync(logDir, { recursive: true });
804
+ }
805
+ const logFile = path34__default.join(logDir, `${name}.log`);
806
+ logger.attachTransport((logObj) => {
807
+ const line = JSON.stringify(logObj) + "\n";
808
+ fs49__default.appendFileSync(logFile, line);
809
+ });
810
+ }
811
+ function createChildLogger(parent, name) {
812
+ return parent.getSubLogger({ name });
813
+ }
814
+ function getLogger() {
815
+ if (!globalLogger) {
816
+ globalLogger = createLogger();
817
+ }
818
+ return globalLogger;
819
+ }
820
+ function setLogger(logger) {
821
+ globalLogger = logger;
822
+ }
823
+ function initializeLogging(projectPath, level = "info") {
824
+ const logDir = path34__default.join(projectPath, ".coco", "logs");
825
+ const logger = createLogger({
826
+ name: "coco",
827
+ level,
828
+ prettyPrint: process.stdout.isTTY ?? true,
829
+ logToFile: true,
830
+ logDir
831
+ });
832
+ setLogger(logger);
833
+ return logger;
834
+ }
835
+ function logEvent(logger, event, data = {}) {
836
+ logger.info({ event, ...data });
837
+ }
838
+ async function logTiming(logger, operation, fn) {
839
+ const start = performance.now();
840
+ try {
841
+ const result = await fn();
842
+ const duration = performance.now() - start;
843
+ logger.debug({ operation, durationMs: duration.toFixed(2), status: "success" });
844
+ return result;
845
+ } catch (error) {
846
+ const duration = performance.now() - start;
847
+ logger.error({ operation, durationMs: duration.toFixed(2), status: "error", error });
848
+ throw error;
849
+ }
850
+ }
851
+ var DEFAULT_CONFIG, globalLogger;
852
+ var init_logger = __esm({
853
+ "src/utils/logger.ts"() {
854
+ DEFAULT_CONFIG = {
855
+ name: "coco",
856
+ level: "info",
857
+ prettyPrint: true,
858
+ logToFile: false
859
+ };
860
+ globalLogger = null;
861
+ }
862
+ });
763
863
  function createAnthropicProvider(config) {
764
864
  const provider = new AnthropicProvider();
765
865
  if (config) {
@@ -787,6 +887,7 @@ var init_anthropic = __esm({
787
887
  "src/providers/anthropic.ts"() {
788
888
  init_errors();
789
889
  init_retry();
890
+ init_logger();
790
891
  DEFAULT_MODEL = "claude-opus-4-6-20260115";
791
892
  CONTEXT_WINDOWS = {
792
893
  // Kimi Code model (Anthropic-compatible endpoint)
@@ -978,8 +1079,8 @@ var init_anthropic = __esm({
978
1079
  try {
979
1080
  currentToolCall.input = currentToolInputJson ? JSON.parse(currentToolInputJson) : {};
980
1081
  } catch {
981
- console.warn(
982
- `[Anthropic] Failed to parse tool call arguments: ${currentToolInputJson?.slice(0, 100)}`
1082
+ getLogger().warn(
1083
+ `Failed to parse tool call arguments: ${currentToolInputJson?.slice(0, 100)}`
983
1084
  );
984
1085
  currentToolCall.input = {};
985
1086
  }
@@ -5143,106 +5244,6 @@ var init_types2 = __esm({
5143
5244
  }
5144
5245
  });
5145
5246
 
5146
- // src/utils/logger.ts
5147
- var logger_exports = {};
5148
- __export(logger_exports, {
5149
- createChildLogger: () => createChildLogger,
5150
- createLogger: () => createLogger,
5151
- getLogger: () => getLogger,
5152
- initializeLogging: () => initializeLogging,
5153
- logEvent: () => logEvent,
5154
- logTiming: () => logTiming,
5155
- setLogger: () => setLogger
5156
- });
5157
- function levelToNumber(level) {
5158
- const levels = {
5159
- silly: 0,
5160
- trace: 1,
5161
- debug: 2,
5162
- info: 3,
5163
- warn: 4,
5164
- error: 5,
5165
- fatal: 6
5166
- };
5167
- return levels[level];
5168
- }
5169
- function createLogger(config = {}) {
5170
- const finalConfig = { ...DEFAULT_CONFIG2, ...config };
5171
- const logger = new Logger({
5172
- name: finalConfig.name,
5173
- minLevel: levelToNumber(finalConfig.level),
5174
- prettyLogTemplate: finalConfig.prettyPrint ? "{{yyyy}}-{{mm}}-{{dd}} {{hh}}:{{MM}}:{{ss}} {{logLevelName}} [{{name}}] " : void 0,
5175
- prettyLogTimeZone: "local",
5176
- stylePrettyLogs: finalConfig.prettyPrint
5177
- });
5178
- if (finalConfig.logToFile && finalConfig.logDir) {
5179
- setupFileLogging(logger, finalConfig.logDir, finalConfig.name);
5180
- }
5181
- return logger;
5182
- }
5183
- function setupFileLogging(logger, logDir, name) {
5184
- if (!fs49__default.existsSync(logDir)) {
5185
- fs49__default.mkdirSync(logDir, { recursive: true });
5186
- }
5187
- const logFile = path34__default.join(logDir, `${name}.log`);
5188
- logger.attachTransport((logObj) => {
5189
- const line = JSON.stringify(logObj) + "\n";
5190
- fs49__default.appendFileSync(logFile, line);
5191
- });
5192
- }
5193
- function createChildLogger(parent, name) {
5194
- return parent.getSubLogger({ name });
5195
- }
5196
- function getLogger() {
5197
- if (!globalLogger) {
5198
- globalLogger = createLogger();
5199
- }
5200
- return globalLogger;
5201
- }
5202
- function setLogger(logger) {
5203
- globalLogger = logger;
5204
- }
5205
- function initializeLogging(projectPath, level = "info") {
5206
- const logDir = path34__default.join(projectPath, ".coco", "logs");
5207
- const logger = createLogger({
5208
- name: "coco",
5209
- level,
5210
- prettyPrint: process.stdout.isTTY ?? true,
5211
- logToFile: true,
5212
- logDir
5213
- });
5214
- setLogger(logger);
5215
- return logger;
5216
- }
5217
- function logEvent(logger, event, data = {}) {
5218
- logger.info({ event, ...data });
5219
- }
5220
- async function logTiming(logger, operation, fn) {
5221
- const start = performance.now();
5222
- try {
5223
- const result = await fn();
5224
- const duration = performance.now() - start;
5225
- logger.debug({ operation, durationMs: duration.toFixed(2), status: "success" });
5226
- return result;
5227
- } catch (error) {
5228
- const duration = performance.now() - start;
5229
- logger.error({ operation, durationMs: duration.toFixed(2), status: "error", error });
5230
- throw error;
5231
- }
5232
- }
5233
- var DEFAULT_CONFIG2, globalLogger;
5234
- var init_logger = __esm({
5235
- "src/utils/logger.ts"() {
5236
- DEFAULT_CONFIG2 = {
5237
- name: "coco",
5238
- level: "info",
5239
- prettyPrint: true,
5240
- logToFile: false
5241
- };
5242
- globalLogger = null;
5243
- }
5244
- });
5245
-
5246
5247
  // src/skills/loader/markdown-loader.ts
5247
5248
  var markdown_loader_exports = {};
5248
5249
  __export(markdown_loader_exports, {
@@ -7847,6 +7848,179 @@ Use /compact --force to compact anyway.
7847
7848
  };
7848
7849
  }
7849
7850
  });
7851
+
7852
+ // src/utils/error-humanizer.ts
7853
+ function extractQuotedPath(msg) {
7854
+ const single = msg.match(/'([^']+)'/);
7855
+ if (single?.[1]) return single[1];
7856
+ const double = msg.match(/"([^"]+)"/);
7857
+ return double?.[1] ?? null;
7858
+ }
7859
+ function humanizeError(message, toolName) {
7860
+ const msg = message.trim();
7861
+ if (!msg) return msg;
7862
+ if (/ECONNREFUSED/i.test(msg)) {
7863
+ return "Connection refused \u2014 the server may not be running";
7864
+ }
7865
+ if (/ENOTFOUND/i.test(msg)) {
7866
+ return "Host not found \u2014 check the URL or your internet connection";
7867
+ }
7868
+ if (/EHOSTUNREACH/i.test(msg)) {
7869
+ return "Host unreachable \u2014 check your network connection";
7870
+ }
7871
+ if (/ECONNRESET/i.test(msg)) {
7872
+ return "Connection reset \u2014 the server closed the connection unexpectedly";
7873
+ }
7874
+ if (/ERR_INVALID_URL/i.test(msg)) {
7875
+ return "Invalid URL format \u2014 check the URL syntax";
7876
+ }
7877
+ if (/CERT_|ERR_CERT_|SSL_ERROR|UNABLE_TO_VERIFY_LEAF_SIGNATURE/i.test(msg)) {
7878
+ return "SSL/TLS certificate error \u2014 the server certificate may be untrusted";
7879
+ }
7880
+ if (/fetch failed|network error|Failed to fetch/i.test(msg)) {
7881
+ return "Network request failed \u2014 check your internet connection";
7882
+ }
7883
+ if (/ENOENT/i.test(msg)) {
7884
+ const path54 = extractQuotedPath(msg);
7885
+ return path54 ? `File or directory not found: ${path54}` : "File or directory not found";
7886
+ }
7887
+ if (/EACCES/i.test(msg)) {
7888
+ const path54 = extractQuotedPath(msg);
7889
+ return path54 ? `Permission denied: ${path54}` : "Permission denied \u2014 check file permissions";
7890
+ }
7891
+ if (/EISDIR/i.test(msg)) {
7892
+ return "Expected a file but found a directory at the specified path";
7893
+ }
7894
+ if (/ENOTDIR/i.test(msg)) {
7895
+ return "Expected a directory but found a file in the path";
7896
+ }
7897
+ if (/EEXIST/i.test(msg)) {
7898
+ return "File or directory already exists";
7899
+ }
7900
+ if (/ENOSPC/i.test(msg)) {
7901
+ return "No disk space left \u2014 free up some space and try again";
7902
+ }
7903
+ if (/EROFS/i.test(msg)) {
7904
+ return "Write failed \u2014 the file system is read-only";
7905
+ }
7906
+ if (/EMFILE|ENFILE/i.test(msg)) {
7907
+ return "Too many open files \u2014 try restarting and running again";
7908
+ }
7909
+ if (/not a git repository/i.test(msg)) {
7910
+ return "Not a git repository \u2014 run 'git init' to initialize one";
7911
+ }
7912
+ if (/nothing to commit/i.test(msg)) {
7913
+ return "Nothing to commit \u2014 the working tree is clean";
7914
+ }
7915
+ if (/merge conflict|CONFLICT/i.test(msg)) {
7916
+ return "Merge conflict detected \u2014 resolve the conflicts before continuing";
7917
+ }
7918
+ if (/non-fast-forward|rejected.*push/i.test(msg)) {
7919
+ return "Push rejected \u2014 pull the latest changes first (git pull)";
7920
+ }
7921
+ if (/authentication failed/i.test(msg) && /git/i.test(msg)) {
7922
+ return "Git authentication failed \u2014 check your credentials or SSH key";
7923
+ }
7924
+ if (/branch.*already exists/i.test(msg)) {
7925
+ return "Branch already exists \u2014 choose a different name or use the existing branch";
7926
+ }
7927
+ if (/detached HEAD/i.test(msg)) {
7928
+ return "Detached HEAD \u2014 checkout a branch to start committing";
7929
+ }
7930
+ if (/(bad revision|does not exist on|unknown revision)/i.test(msg)) {
7931
+ return "Git reference not found \u2014 the branch or commit may not exist";
7932
+ }
7933
+ if (/Unexpected token.*JSON|JSON\.parse|Unexpected end of JSON/i.test(msg)) {
7934
+ return "Failed to parse JSON \u2014 the data may be malformed";
7935
+ }
7936
+ if (/SyntaxError.*Unexpected token/i.test(msg)) {
7937
+ return "Syntax error in the data \u2014 check for formatting issues";
7938
+ }
7939
+ const moduleMatch = msg.match(/Cannot find module ['"]([^'"]+)['"]/i);
7940
+ if (moduleMatch) {
7941
+ return `Module not found: '${moduleMatch[1]}' \u2014 run the install command to add it`;
7942
+ }
7943
+ if (/ERR_MODULE_NOT_FOUND|MODULE_NOT_FOUND/i.test(msg)) {
7944
+ return "Required module not found \u2014 run the install command first";
7945
+ }
7946
+ if (/ERR_REQUIRE_ESM/i.test(msg)) {
7947
+ return "Module format mismatch \u2014 this package requires ESM (type: module)";
7948
+ }
7949
+ if (/command not found/i.test(msg) || /spawn.*ENOENT/i.test(msg) && toolName === "bash_exec") {
7950
+ const cmdMatch = msg.match(/Command '([^']+)' not found|spawn ([^\s]+) ENOENT/);
7951
+ const cmd = cmdMatch?.[1] ?? cmdMatch?.[2];
7952
+ return cmd ? `Command '${cmd}' not found \u2014 is it installed and in your PATH?` : "Command not found \u2014 check it is installed and available in PATH";
7953
+ }
7954
+ if (/permission denied/i.test(msg) && /spawn|exec/i.test(msg)) {
7955
+ return "Permission denied \u2014 the script may not be executable (try: chmod +x)";
7956
+ }
7957
+ if (/\b401\b|Unauthorized/i.test(msg)) {
7958
+ return "Authentication failed (401) \u2014 check your API key or credentials";
7959
+ }
7960
+ if (/\b403\b|Forbidden/i.test(msg)) {
7961
+ return "Access denied (403) \u2014 you don't have permission for this action";
7962
+ }
7963
+ if (/\b429\b|rate.?limit/i.test(msg)) {
7964
+ return "Rate limit exceeded (429) \u2014 too many requests, wait a moment and retry";
7965
+ }
7966
+ if (/\b503\b|Service Unavailable/i.test(msg)) {
7967
+ return "Service temporarily unavailable (503) \u2014 try again in a few minutes";
7968
+ }
7969
+ if (/invalid.*api.?key|api.?key.*invalid|api.?key.*not.*found/i.test(msg)) {
7970
+ return "Invalid or missing API key \u2014 check your provider credentials";
7971
+ }
7972
+ return msg;
7973
+ }
7974
+ function looksLikeTechnicalJargon(message) {
7975
+ return JARGON_PATTERNS.some((p45) => p45.test(message));
7976
+ }
7977
+ async function humanizeWithLLM(errorMessage, toolName, provider) {
7978
+ const prompt = [
7979
+ `A developer tool called "${toolName}" produced this error:`,
7980
+ ``,
7981
+ `"""`,
7982
+ errorMessage.slice(0, 500),
7983
+ `"""`,
7984
+ ``,
7985
+ `In 1\u20132 sentences, explain what went wrong in plain English and suggest the most likely fix.`,
7986
+ `Reply with only the explanation \u2014 no preamble, no code blocks.`
7987
+ ].join("\n");
7988
+ try {
7989
+ const response = await Promise.race([
7990
+ provider.chat([{ role: "user", content: prompt }], { maxTokens: 120, temperature: 0 }),
7991
+ new Promise((resolve4) => setTimeout(() => resolve4(null), LLM_TIMEOUT_MS))
7992
+ ]);
7993
+ if (!response || !("content" in response)) return null;
7994
+ return response.content.trim() || null;
7995
+ } catch {
7996
+ return null;
7997
+ }
7998
+ }
7999
+ var JARGON_PATTERNS, LLM_TIMEOUT_MS;
8000
+ var init_error_humanizer = __esm({
8001
+ "src/utils/error-humanizer.ts"() {
8002
+ JARGON_PATTERNS = [
8003
+ /\bE[A-Z]{3,}\b/,
8004
+ // POSIX error codes: EPERM, ENOENT, EACCES, …
8005
+ /0x[0-9a-f]{4,}/i,
8006
+ // hex addresses
8007
+ /at \w[\w.]*\s*\(/,
8008
+ // stack trace "at functionName ("
8009
+ /ERR_[A-Z_]{3,}/,
8010
+ // Node.js ERR_ codes
8011
+ /TypeError:|ReferenceError:|RangeError:|SyntaxError:/,
8012
+ /zod|ZodError|ZodIssue/i,
8013
+ /Cannot read propert/i,
8014
+ /is not a function\b/i,
8015
+ /Cannot destructure property/i,
8016
+ /undefined is not/i,
8017
+ /null is not/i,
8018
+ /TS\d{4}:/
8019
+ // TypeScript error codes
8020
+ ];
8021
+ LLM_TIMEOUT_MS = 6e3;
8022
+ }
8023
+ });
7850
8024
  function zodToJsonSchema(schema) {
7851
8025
  try {
7852
8026
  if (schema instanceof z.ZodObject) {
@@ -7893,6 +8067,7 @@ var ToolRegistry;
7893
8067
  var init_registry4 = __esm({
7894
8068
  "src/tools/registry.ts"() {
7895
8069
  init_logger();
8070
+ init_error_humanizer();
7896
8071
  ToolRegistry = class {
7897
8072
  tools = /* @__PURE__ */ new Map();
7898
8073
  logger = getLogger();
@@ -7983,7 +8158,17 @@ var init_registry4 = __esm({
7983
8158
  };
7984
8159
  } catch (error) {
7985
8160
  const duration = performance.now() - startTime;
7986
- const errorMessage = error instanceof Error ? error.message : String(error);
8161
+ let errorMessage;
8162
+ if (error instanceof z.ZodError) {
8163
+ const fields = error.issues.map((issue) => {
8164
+ const field = issue.path.join(".") || "input";
8165
+ return `${field} (${issue.message.toLowerCase()})`;
8166
+ });
8167
+ errorMessage = `Invalid tool input \u2014 ${fields.join(", ")}`;
8168
+ } else {
8169
+ const rawMessage = error instanceof Error ? error.message : String(error);
8170
+ errorMessage = humanizeError(rawMessage, name);
8171
+ }
7987
8172
  this.logger.error(`Tool '${name}' failed`, { error: errorMessage, duration });
7988
8173
  options?.onProgress?.({
7989
8174
  phase: "failed",
@@ -19082,7 +19267,7 @@ var init_input_echo = __esm({
19082
19267
  DEFAULT_CONFIG7 = {
19083
19268
  maxVisibleChars: 60,
19084
19269
  prompt: "\u203A ",
19085
- placeholder: "Escribe para modificar o a\xF1adir tareas\u2026"
19270
+ placeholder: "Type to modify or add tasks\u2026"
19086
19271
  };
19087
19272
  }
19088
19273
  });
@@ -24439,7 +24624,7 @@ async function runConfigInit() {
24439
24624
  p25.outro(chalk25.green("Configuration saved to .coco/config.json"));
24440
24625
  }
24441
24626
  var CONFIG_PATH = join(process.cwd(), ".coco", "config.json");
24442
- var DEFAULT_CONFIG = {
24627
+ var DEFAULT_CONFIG2 = {
24443
24628
  provider: {
24444
24629
  type: "anthropic",
24445
24630
  model: "claude-sonnet-4-20250514"
@@ -24459,9 +24644,9 @@ async function loadConfig2() {
24459
24644
  try {
24460
24645
  const raw = await fs52.readFile(CONFIG_PATH, "utf-8");
24461
24646
  const parsed = JSON.parse(raw);
24462
- return { ...DEFAULT_CONFIG, ...parsed };
24647
+ return { ...DEFAULT_CONFIG2, ...parsed };
24463
24648
  } catch {
24464
- return { ...DEFAULT_CONFIG };
24649
+ return { ...DEFAULT_CONFIG2 };
24465
24650
  }
24466
24651
  }
24467
24652
  async function saveConfig2(config) {
@@ -31647,7 +31832,8 @@ var resumeCommand = {
31647
31832
  init_version();
31648
31833
  var NPM_REGISTRY_URL = "https://registry.npmjs.org/@corbat-tech/coco";
31649
31834
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
31650
- var FETCH_TIMEOUT_MS = 3e3;
31835
+ var FETCH_TIMEOUT_MS = 2e3;
31836
+ var STARTUP_TIMEOUT_MS = 2500;
31651
31837
  var CACHE_DIR = path34__default.join(os4__default.homedir(), ".coco");
31652
31838
  var CACHE_FILE = path34__default.join(CACHE_DIR, "version-check-cache.json");
31653
31839
  function compareVersions(a, b) {
@@ -31767,13 +31953,16 @@ function printUpdateBanner(updateInfo) {
31767
31953
  console.log();
31768
31954
  }
31769
31955
  async function checkForUpdatesInteractive() {
31770
- const updateInfo = await checkForUpdates();
31956
+ const updateInfo = await Promise.race([
31957
+ checkForUpdates(),
31958
+ new Promise((resolve4) => setTimeout(() => resolve4(null), STARTUP_TIMEOUT_MS))
31959
+ ]);
31771
31960
  if (!updateInfo) return;
31772
31961
  const p45 = await import('@clack/prompts');
31773
31962
  printUpdateBanner(updateInfo);
31774
31963
  const answer = await p45.confirm({
31775
31964
  message: "Exit now to update?",
31776
- initialValue: false
31965
+ initialValue: true
31777
31966
  });
31778
31967
  if (!p45.isCancel(answer) && answer) {
31779
31968
  console.log();
@@ -42715,6 +42904,8 @@ async function executeAgentTurn(session, userMessage, provider, toolRegistry, op
42715
42904
  const tools = toolRegistry.getToolDefinitionsForLLM();
42716
42905
  let iteration = 0;
42717
42906
  const maxIterations = session.config.agent.maxToolIterations;
42907
+ const toolErrorCounts = /* @__PURE__ */ new Map();
42908
+ const MAX_CONSECUTIVE_TOOL_ERRORS = 3;
42718
42909
  while (iteration < maxIterations) {
42719
42910
  iteration++;
42720
42911
  if (options.signal?.aborted) {
@@ -42943,6 +43134,35 @@ async function executeAgentTurn(session, userMessage, provider, toolRegistry, op
42943
43134
  });
42944
43135
  }
42945
43136
  }
43137
+ let stuckInErrorLoop = false;
43138
+ for (const executedCall of executedTools) {
43139
+ if (!executedCall.result.success && executedCall.result.error) {
43140
+ const errorKey = `${executedCall.name}:${executedCall.result.error.slice(0, 120).toLowerCase()}`;
43141
+ const count = (toolErrorCounts.get(errorKey) ?? 0) + 1;
43142
+ toolErrorCounts.set(errorKey, count);
43143
+ if (count >= MAX_CONSECUTIVE_TOOL_ERRORS) {
43144
+ stuckInErrorLoop = true;
43145
+ const idx = toolResults.findIndex((r) => r.tool_use_id === executedCall.id);
43146
+ if (idx >= 0) {
43147
+ toolResults[idx] = {
43148
+ ...toolResults[idx],
43149
+ content: [
43150
+ executedCall.result.error,
43151
+ "",
43152
+ `\u26A0\uFE0F This tool has now failed ${count} consecutive times with the same error.`,
43153
+ "Do NOT retry with the same parameters.",
43154
+ "Explain to the user what is missing or wrong, and ask for clarification if needed."
43155
+ ].join("\n"),
43156
+ is_error: true
43157
+ };
43158
+ }
43159
+ }
43160
+ } else {
43161
+ for (const key of toolErrorCounts.keys()) {
43162
+ if (key.startsWith(`${executedCall.name}:`)) toolErrorCounts.delete(key);
43163
+ }
43164
+ }
43165
+ }
42946
43166
  const assistantContent = response.content ? [{ type: "text", text: response.content }, ...toolUses] : toolUses;
42947
43167
  addMessage(session, {
42948
43168
  role: "assistant",
@@ -42952,6 +43172,9 @@ async function executeAgentTurn(session, userMessage, provider, toolRegistry, op
42952
43172
  role: "user",
42953
43173
  content: toolResults
42954
43174
  });
43175
+ if (stuckInErrorLoop) {
43176
+ break;
43177
+ }
42955
43178
  }
42956
43179
  options.onStream?.({ type: "done" });
42957
43180
  return {
@@ -43044,18 +43267,21 @@ var INTENT_PATTERNS = {
43044
43267
  /run\s+(the\s+)?(complete|build)\s+phase/i
43045
43268
  ],
43046
43269
  // Task
43270
+ // NOTE: patterns must require an explicit task ID (number or code like "AUTH-001")
43271
+ // to avoid false-positive matches on natural language like "implementa la tarea de X".
43272
+ // Generic phrases like "do the task" or "haz la tarea" intentionally omitted.
43047
43273
  task: [
43048
- // Spanish
43049
- /^(haz|implementa|crea)\s+(la\s+)?tarea/i,
43050
- /^(trabaja\s+en|work\s+on)/i,
43051
- /^(ejecuta|execute)\s+(la\s+)?tarea/i,
43052
- /completa\s+(la\s+)?tarea/i,
43274
+ // Spanish — ID required
43275
+ /^(haz|ejecuta|completa)\s+(la\s+)?tarea\s+#?[a-zA-Z0-9][a-zA-Z0-9_-]*/i,
43276
+ /^(trabaja\s+en)\s+(la\s+)?tarea\s+#?[a-zA-Z0-9][a-zA-Z0-9_-]*/i,
43053
43277
  /marcar\s+tarea\s+como\s+(hecha|completada)/i,
43054
- // English
43055
- /^(do|complete|finish)\s+(the\s+)?task/i,
43056
- /work\s+on\s+(the\s+)?task/i,
43278
+ // English — ID required
43279
+ /^(do|complete|finish|run)\s+(the\s+)?task\s+#?[a-zA-Z0-9][a-zA-Z0-9_-]*/i,
43280
+ /^work\s+on\s+(the\s+)?task\s+#?[a-zA-Z0-9][a-zA-Z0-9_-]*/i,
43057
43281
  /mark\s+task\s+as\s+(done|complete)/i,
43058
- /start\s+(the\s+)?task/i
43282
+ // Explicit task reference with ID (e.g. "task 3", "task AUTH-001")
43283
+ /^task\s+#?[a-zA-Z0-9][a-zA-Z0-9_-]*/i,
43284
+ /^tarea\s+#?[a-zA-Z0-9][a-zA-Z0-9_-]*/i
43059
43285
  ],
43060
43286
  // Init
43061
43287
  init: [
@@ -43170,8 +43396,8 @@ var INTENT_PATTERNS = {
43170
43396
  var ENTITY_PATTERNS = {
43171
43397
  /** Extract sprint number */
43172
43398
  sprint: /(?:sprint|s)\s*(?:number|num|n)?\s*[:#]?\s*(\d+)/i,
43173
- /** Extract task ID */
43174
- taskId: /(?:task|tarea)\s*(?:id)?\s*[:#]?\s*([a-zA-Z0-9_-]+)/i,
43399
+ /** Extract task ID — requires at least 2 chars to avoid grabbing prepositions like "de" */
43400
+ taskId: /(?:task|tarea)\s*(?:id|#)?\s*:?\s*(\d+|[a-zA-Z][a-zA-Z0-9_-]{1,})/i,
43175
43401
  /** Extract project name */
43176
43402
  projectName: /(?:project|proyecto)\s*(?:name|nombre)?\s*[:\s]+([a-zA-Z0-9_-]+)/i,
43177
43403
  /** Extract flags like --dry-run, --yes */
@@ -43181,9 +43407,7 @@ function calculateConfidenceBoost(input) {
43181
43407
  if (input.length < 20) {
43182
43408
  boost += 0.1;
43183
43409
  }
43184
- if (/^(haz|crea|genera|construye|implementa|ejecuta|inicializa|abre|create|make|build|run|start|open|exec)/i.test(
43185
- input
43186
- )) {
43410
+ if (/^(haz|genera|construye|ejecuta|inicializa|build|run|start|open|exec)\b/i.test(input)) {
43187
43411
  boost += 0.15;
43188
43412
  }
43189
43413
  if (/(converge|orchestrate|complete|output|plan|build|init|ship|release|publish|open|exec)/i.test(
@@ -43207,7 +43431,8 @@ var DEFAULT_INTENT_CONFIG = {
43207
43431
  autoExecute: false,
43208
43432
  autoExecuteThreshold: CONFIDENCE["HIGH"],
43209
43433
  alwaysConfirm: ["init", "build", "output", "status"],
43210
- autoExecutePreferences: {}
43434
+ // exit always runs immediately — no confirmation dialog
43435
+ autoExecutePreferences: { exit: true }
43211
43436
  };
43212
43437
  function createIntentRecognizer(config = {}) {
43213
43438
  const fullConfig = { ...DEFAULT_INTENT_CONFIG, ...config };
@@ -43284,19 +43509,7 @@ function createIntentRecognizer(config = {}) {
43284
43509
  raw: input
43285
43510
  };
43286
43511
  }
43287
- const intentTypes = [
43288
- "plan",
43289
- "build",
43290
- "task",
43291
- "init",
43292
- "output",
43293
- "status",
43294
- "trust",
43295
- "ship",
43296
- "open",
43297
- "help",
43298
- "exit"
43299
- ];
43512
+ const intentTypes = ["status", "trust", "help", "exit"];
43300
43513
  let bestMatch = null;
43301
43514
  for (const type of intentTypes) {
43302
43515
  const match = matchIntent(trimmedInput, type);
@@ -43384,9 +43597,10 @@ function createIntentRecognizer(config = {}) {
43384
43597
  }
43385
43598
  return { command: "ship", args };
43386
43599
  case "open":
43387
- if (intent.entities.args?.[0]) {
43388
- args.push(intent.entities.args[0]);
43600
+ if (!intent.entities.args?.[0]) {
43601
+ return null;
43389
43602
  }
43603
+ args.push(intent.entities.args[0]);
43390
43604
  if (intent.entities.flags?.includes("exec")) {
43391
43605
  args.push("--exec");
43392
43606
  }
@@ -43505,6 +43719,7 @@ function renderStatusBar(projectPath, config, gitCtx) {
43505
43719
 
43506
43720
  // src/cli/repl/index.ts
43507
43721
  init_subprocess_registry();
43722
+ init_error_humanizer();
43508
43723
  async function startRepl(options = {}) {
43509
43724
  const projectPath = options.projectPath ?? process.cwd();
43510
43725
  registerGlobalCleanup();
@@ -43869,6 +44084,7 @@ async function startRepl(options = {}) {
43869
44084
  const turnActionedInterruptions = [];
43870
44085
  const turnQueuedMessages = [];
43871
44086
  const pendingClassifications = [];
44087
+ const pendingExplanations = [];
43872
44088
  concurrentCapture.reset();
43873
44089
  inputEcho.reset();
43874
44090
  concurrentCapture.start(
@@ -43939,6 +44155,9 @@ async function startRepl(options = {}) {
43939
44155
  }
43940
44156
  renderToolStart(result2.name, result2.input);
43941
44157
  renderToolEnd(result2);
44158
+ if (!result2.result.success && result2.result.error && looksLikeTechnicalJargon(result2.result.error)) {
44159
+ pendingExplanations.push(humanizeWithLLM(result2.result.error, result2.name, provider));
44160
+ }
43942
44161
  if (isQualityLoop()) {
43943
44162
  setSpinner("Processing results & checking quality...");
43944
44163
  } else {
@@ -43975,7 +44194,7 @@ async function startRepl(options = {}) {
43975
44194
  clearSpinner();
43976
44195
  },
43977
44196
  onToolPreparing: (toolName) => {
43978
- setSpinner(`Preparing: ${toolName}\u2026`);
44197
+ setSpinner(getToolPreparingDescription(toolName));
43979
44198
  },
43980
44199
  onBeforeConfirmation: () => {
43981
44200
  inputEcho.suspend();
@@ -44043,6 +44262,19 @@ async function startRepl(options = {}) {
44043
44262
  continue;
44044
44263
  }
44045
44264
  console.log();
44265
+ if (pendingExplanations.length > 0) {
44266
+ const settled = await Promise.race([
44267
+ Promise.allSettled(pendingExplanations),
44268
+ new Promise((resolve4) => setTimeout(() => resolve4(null), 2e3))
44269
+ ]);
44270
+ if (settled) {
44271
+ for (const r of settled) {
44272
+ if (r.status === "fulfilled" && r.value) {
44273
+ console.log(chalk25.dim(` \u{1F4A1} ${r.value}`));
44274
+ }
44275
+ }
44276
+ }
44277
+ }
44046
44278
  if (isQualityLoop() && result.content) {
44047
44279
  const qualityResult = parseQualityLoopReport(result.content);
44048
44280
  if (qualityResult) {
@@ -44240,6 +44472,29 @@ async function checkProjectTrust(projectPath) {
44240
44472
  console.log(chalk25.green(" \u2713 Access granted") + chalk25.dim(" \u2022 /trust to manage"));
44241
44473
  return true;
44242
44474
  }
44475
+ function getToolPreparingDescription(toolName) {
44476
+ switch (toolName) {
44477
+ case "write_file":
44478
+ return "Generating file content\u2026";
44479
+ case "edit_file":
44480
+ return "Planning edits\u2026";
44481
+ case "bash_exec":
44482
+ return "Building command\u2026";
44483
+ case "web_search":
44484
+ return "Building search query\u2026";
44485
+ case "web_fetch":
44486
+ return "Preparing request\u2026";
44487
+ case "run_tests":
44488
+ return "Setting up test run\u2026";
44489
+ case "git_commit":
44490
+ return "Composing commit\u2026";
44491
+ case "semantic_search":
44492
+ case "grep_search":
44493
+ return "Building search\u2026";
44494
+ default:
44495
+ return `Preparing ${toolName}\u2026`;
44496
+ }
44497
+ }
44243
44498
  function getToolRunningDescription(name, input) {
44244
44499
  switch (name) {
44245
44500
  case "codebase_map":
@@ -44266,8 +44521,11 @@ function getToolRunningDescription(name, input) {
44266
44521
  }
44267
44522
  case "list_directory":
44268
44523
  return "Listing directory\u2026";
44269
- case "bash_exec":
44270
- return "Running command\u2026";
44524
+ case "bash_exec": {
44525
+ const cmd = typeof input.command === "string" ? input.command.trim() : "";
44526
+ const displayCmd = cmd.replace(/^[\w=]+=\S+\s+/, "").slice(0, 55);
44527
+ return displayCmd ? `Running: ${displayCmd}\u2026` : "Running command\u2026";
44528
+ }
44271
44529
  case "run_tests":
44272
44530
  return "Running tests\u2026";
44273
44531
  case "git_status":