chocolatito-code 1.3.0 → 1.3.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.
@@ -3,7 +3,7 @@ import process from "node:process";
3
3
  import { getAllTools } from "../tools/definitions.js";
4
4
  import { executeToolCall } from "../tools/runner.js";
5
5
  import { TokenTracker } from "./tracker.js";
6
- import { renderToolStart, renderToolSuccess, renderToolError, renderDiff, renderFooter, renderError, } from "../ui/renderer.js";
6
+ import { renderToolStart, renderToolSuccess, renderToolError, renderDiff, renderFooter, renderError, formatToolName, } from "../ui/renderer.js";
7
7
  import { CONTEXT_WINDOW, MAX_OUTPUT_TOKENS, COMPACT_THRESHOLD_TOKENS, } from "../config/constants.js";
8
8
  import { withRetry, isContextOverflow, esDemasiadasEnCurso } from "./retry.js";
9
9
  import { canRunInParallel } from "../tools/safety.js";
@@ -14,7 +14,7 @@ import { checkQuota, recordUsage, mensajeDeCorte, quotaBadge } from "../config/q
14
14
  import { ReasoningStream } from "../ui/reasoningStream.js";
15
15
  import { PermissionManager, commandSignature, isDangerousCommand } from "../config/permissions.js";
16
16
  import { askToolPermission } from "../ui/permissionPrompt.js";
17
- import { DynamicSpinner } from "../ui/spinner.js";
17
+ import { DynamicSpinner, conSpinner } from "../ui/spinner.js";
18
18
  import { globalUndoManager } from "./undoManager.js";
19
19
  import { salida } from "./salidaDelAgente.js";
20
20
  import { InterruptWatcher, isAbortError } from "../ui/interrupt.js";
@@ -727,7 +727,20 @@ export class AgentLoop {
727
727
  // huecos de la licencia durante minutos, y los cortes vienen en rachas:
728
728
  // reintentar es justo como se pasa de perder un hueco a perder los cuatro.
729
729
  // Mas vale que el usuario reescriba una linea.
730
- if (!llegoElFinal && (content.trim().length > 0 || toolCalls.length > 0)) {
730
+ //
731
+ // NO SE EXIGE QUE HAYA LLEGADO TEXTO. La primera version pedia contenido o
732
+ // herramientas, y se le escapaba el caso mas comun: el corte que pasa
733
+ // DESPUES del razonamiento y ANTES de la primera palabra. Eso caia en la
734
+ // rama de "respuesta vacia", que si reintenta, y volvia a gastar huecos:
735
+ //
736
+ // 💭 penso 2s
737
+ // ⚠ El motor no devolvio nada. Reintentando una vez…
738
+ //
739
+ // Sin marca de final, el stream se corto. Da igual cuanto habia llegado.
740
+ // Una respuesta vacia DE VERDAD trae su finish_reason: el motor cerro el
741
+ // turno en regla y simplemente no dijo nada, y eso si merece un reintento
742
+ // porque la conexion estaba bien.
743
+ if (!llegoElFinal) {
731
744
  watcher.stop();
732
745
  // Lo que llego se guarda, pero MARCADO. Sin la marca, en el turno
733
746
  // siguiente el modelo lee su propia frase a medias como algo que decidio
@@ -738,11 +751,20 @@ export class AgentLoop {
738
751
  content: `${content}\n\n[Aqui se corto: la conexion con el motor se cerro a mitad de la respuesta.]`,
739
752
  });
740
753
  }
754
+ // Se dice HASTA DONDE llego, y no por cortesia: es el unico dato que
755
+ // distingue un corte antes de empezar de uno a mitad de frase, y en un
756
+ // pegado del usuario es lo unico con lo que se puede diagnosticar.
741
757
  // Las llamadas a herramienta de un stream cortado NO se ejecutan: sus
742
758
  // argumentos estan tan a medias como el texto.
743
- const aviso = "La respuesta se corto a mitad: la conexion con el motor se cerro sin avisar. " +
744
- "Lo que llego se queda arriba. No lo reintento solo porque cada intento ocupa uno de " +
745
- "los cuatro huecos de tu licencia durante varios minutos; vuelve a pedirmelo y sigo.";
759
+ const hastaDonde = content.trim()
760
+ ? "Lo que llego se queda arriba."
761
+ : reasoning.trim()
762
+ ? "Se corto antes de escribir nada: solo llego el razonamiento."
763
+ : "Se corto antes de que llegara nada.";
764
+ const aviso = "La respuesta se corto: la conexion con el motor se cerro sin decir por que (no llego " +
765
+ `ni el motivo de fin ni el recuento del gasto). ${hastaDonde} ` +
766
+ "No lo reintento solo porque cada intento ocupa uno de los cuatro huecos de tu licencia " +
767
+ "durante varios minutos; vuelve a pedirmelo y sigo.";
746
768
  salida.linea(chalk.yellow(`\n ⚠ ${aviso}\n`));
747
769
  return parada("error-motor", aviso);
748
770
  }
@@ -845,10 +867,14 @@ export class AgentLoop {
845
867
  for (const { tc, args } of autorizadas) {
846
868
  renderToolStart(tc.function.name, getToolSummary(tc.function.name, args));
847
869
  }
848
- const settled = await Promise.all(autorizadas.map(async ({ tc, args, pos }) => {
870
+ // Un solo spinner para el lote entero: uno por herramienta se pisarian
871
+ // entre ellos y el pie parpadearia tantas veces como llamadas haya.
872
+ const settled = await conSpinner(autorizadas.length === 1
873
+ ? formatToolName(autorizadas[0].tc.function.name)
874
+ : `${autorizadas.length} herramientas a la vez`, () => Promise.all(autorizadas.map(async ({ tc, args, pos }) => {
849
875
  const r = await executeToolCall(tc.function.name, args, this.currentCwd, this.apiKey, this.currentEspectro.underlyingModel);
850
876
  return { tc, args, pos, result: r.result };
851
- }));
877
+ })));
852
878
  for (const { tc, args, pos, result } of settled) {
853
879
  renderToolSuccess(tc.function.name, result);
854
880
  const aviso = await notificarPostHerramienta(tc.function.name, args, result, this.currentCwd);
@@ -969,7 +995,10 @@ export class AgentLoop {
969
995
  }
970
996
  const summary = getToolSummary(fnName, fnArgs);
971
997
  renderToolStart(fnName, summary);
972
- const { result, diffText, newCwd } = await executeToolCall(fnName, fnArgs, this.currentCwd, this.apiKey, this.currentEspectro.underlyingModel);
998
+ // El pie se queda puesto mientras dure la herramienta. Un run_command
999
+ // de veinte segundos dejaba la pantalla muda: ni cronometro, ni forma
1000
+ // de saber que seguia vivo, ni donde escribir para encolar algo.
1001
+ const { result, diffText, newCwd } = await conSpinner(formatToolName(fnName), () => executeToolCall(fnName, fnArgs, this.currentCwd, this.apiKey, this.currentEspectro.underlyingModel));
973
1002
  if (newCwd) {
974
1003
  this.currentCwd = newCwd;
975
1004
  }
@@ -1027,6 +1056,12 @@ export class AgentLoop {
1027
1056
  // codigo no distingue "el modelo termino" de "no vino nada". Sin
1028
1057
  // herramientas y sin texto, el bucle daba el turno por hecho, imprimia el
1029
1058
  // pie y volvia al prompt.
1059
+ //
1060
+ // Aqui solo se llega con la marca de final puesta: un stream cortado sale
1061
+ // mucho antes, por su propia rama. Asi que esto es lo que parece -el motor
1062
+ // cerro el turno en regla y no dijo nada- y no un corte disfrazado. Esa
1063
+ // separacion es la que hace que reintentar aqui sea razonable: la conexion
1064
+ // estaba bien, y por eso el reintento no es tirar un hueco a la basura.
1030
1065
  if (!content.trim()) {
1031
1066
  respuestasVacias++;
1032
1067
  // Se reintenta UNA vez, no mas, y aqui la moderacion importa: cada
@@ -1034,13 +1069,14 @@ export class AgentLoop {
1034
1069
  // cuatro. Insistir con un motor que no contesta es la forma mas rapida
1035
1070
  // de quedarse sin ninguno.
1036
1071
  if (respuestasVacias > MAX_RESPUESTAS_VACIAS) {
1037
- const aviso = `El motor devolvio ${respuestasVacias} respuestas vacias seguidas. ` +
1038
- "No es tu conexion ni tu orden: no esta llegando nada del modelo. " +
1039
- "Espera un momento y reintenta, o /clear si venias de una conversacion larga.";
1072
+ const aviso = `El motor cerro el turno en regla ${respuestasVacias} veces seguidas sin una sola ` +
1073
+ "palabra. No es un corte de conexion -eso se avisa aparte y con otras palabras-: la " +
1074
+ "peticion llego y volvio entera, solo que vacia. Espera un momento y reintenta, o " +
1075
+ "/clear si venias de una conversacion larga.";
1040
1076
  salida.linea(chalk.yellow(`\n ⚠ ${aviso}\n`));
1041
1077
  return parada("error-motor", aviso);
1042
1078
  }
1043
- salida.linea(chalk.gray("\n ⚠ El motor no devolvio nada. Reintentando una vez…\n"));
1079
+ salida.linea(chalk.gray("\n ⚠ El motor cerro el turno sin decir nada. Reintentando una vez…\n"));
1044
1080
  this.messages.push({
1045
1081
  role: "user",
1046
1082
  content: "Tu respuesta anterior llego vacia: ni texto ni llamadas a herramienta. " +
@@ -50,6 +50,17 @@ export interface QuotaStatus {
50
50
  config: QuotaConfig;
51
51
  }
52
52
  export declare function checkQuota(ahora?: number): QuotaStatus;
53
+ /**
54
+ * Cuando la ventana corta se vacia DEL TODO, si no se gasta nada mas.
55
+ *
56
+ * `seLiberaEn` dice cuando caduca el gasto mas viejo, que es lo que hace falta
57
+ * para saber cuando se puede volver a trabajar tras un corte. Para ensenar el
58
+ * consumo es otra pregunta: cuando vuelve a cero. Y como la ventana es
59
+ * deslizante, eso es cuando caduca el gasto mas RECIENTE.
60
+ *
61
+ * `null` si no hay nada dentro de la ventana: no hay nada que renovar.
62
+ */
63
+ export declare function ventanaVaciaEn(ahora?: number): Date | null;
53
64
  /** Cuanto falta para la hora indicada, en lenguaje llano. */
54
65
  export declare function faltaPara(fecha: Date, ahora?: number): string;
55
66
  /** Mensaje completo para cuando se agota la cuota. */
@@ -148,6 +148,24 @@ export function checkQuota(ahora = Date.now()) {
148
148
  }
149
149
  return { allowed: true, spentWindowUsd, spentWeekUsd, config };
150
150
  }
151
+ /**
152
+ * Cuando la ventana corta se vacia DEL TODO, si no se gasta nada mas.
153
+ *
154
+ * `seLiberaEn` dice cuando caduca el gasto mas viejo, que es lo que hace falta
155
+ * para saber cuando se puede volver a trabajar tras un corte. Para ensenar el
156
+ * consumo es otra pregunta: cuando vuelve a cero. Y como la ventana es
157
+ * deslizante, eso es cuando caduca el gasto mas RECIENTE.
158
+ *
159
+ * `null` si no hay nada dentro de la ventana: no hay nada que renovar.
160
+ */
161
+ export function ventanaVaciaEn(ahora = Date.now()) {
162
+ const ventanaMs = getQuotaConfig().windowHours * 60 * 60 * 1000;
163
+ const dentro = load().filter((e) => e.t >= ahora - ventanaMs);
164
+ if (dentro.length === 0)
165
+ return null;
166
+ const masReciente = dentro.reduce((max, e) => (e.t > max ? e.t : max), dentro[0].t);
167
+ return new Date(masReciente + ventanaMs);
168
+ }
151
169
  /** Cuanto falta para la hora indicada, en lenguaje llano. */
152
170
  export function faltaPara(fecha, ahora = Date.now()) {
153
171
  const ms = fecha.getTime() - ahora;
package/dist/index.js CHANGED
@@ -5,7 +5,8 @@ import { fileURLToPath } from "node:url";
5
5
  import chalk from "chalk";
6
6
  import { ensureApiKey } from "./config/env.js";
7
7
  import { ensureLicense, licenseStatus, deactivate, getStoredLicense } from "./config/license.js";
8
- import { isManagedMode } from "./config/quota.js";
8
+ import { isManagedMode, checkQuota, ventanaVaciaEn } from "./config/quota.js";
9
+ import { construirInformeDeUso } from "./ui/informeDeUso.js";
9
10
  import { getEngineBaseUrl, DEFAULT_ESPECTRO, ESPECTRO_MODELS, RULES_FILE_NAME } from "./config/constants.js";
10
11
  import { getProjectContext } from "./agent/context.js";
11
12
  import { buildSystemPrompt } from "./prompts/systemPrompt.js";
@@ -184,8 +185,8 @@ async function main() {
184
185
  // errores raros dentro de una dependencia. Ver config/nodeVersion.ts.
185
186
  const nodeViejo = comprobarNode();
186
187
  if (nodeViejo) {
187
- console.error(chalk.red(`
188
- ${nodeViejo}
188
+ console.error(chalk.red(`
189
+ ${nodeViejo}
189
190
  `));
190
191
  process.exit(EXIT_USO);
191
192
  }
@@ -399,15 +400,15 @@ ${nodeViejo}
399
400
  .filter((m) => m.role === "user" || m.role === "assistant")
400
401
  .map((m) => `### 👤 ${m.role === "user" ? "Usuario" : "Chocolatito"}\n\n${typeof m.content === "string" ? m.content : JSON.stringify(m.content, null, 2)}\n`)
401
402
  .join("\n---\n\n");
402
- const mdContent = `# 🦊 Bitácora de Sesión — Chocolatito Code
403
- **Fecha:** ${new Date().toLocaleString()}
404
- **Directorio:** \`${agent.currentCwd}\`
405
- **Motor:** ${currentModel.name}
406
- **Inversión Total:** ${agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel).formattedCost}
407
-
408
- ---
409
-
410
- ${sessionData}
403
+ const mdContent = `# 🦊 Bitácora de Sesión — Chocolatito Code
404
+ **Fecha:** ${new Date().toLocaleString()}
405
+ **Directorio:** \`${agent.currentCwd}\`
406
+ **Motor:** ${currentModel.name}
407
+ **Inversión Total:** ${agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel).formattedCost}
408
+
409
+ ---
410
+
411
+ ${sessionData}
411
412
  `;
412
413
  fs.writeFileSync(exportPath, mdContent, "utf-8");
413
414
  console.log(chalk.green(`\n✔ Bitácora exportada exitosamente en: ${chalk.bold.white(exportPath)}\n`));
@@ -500,12 +501,12 @@ ${sessionData}
500
501
  }
501
502
  console.log(chalk.bold.hex("#A855F7")("\n🎯 INICIANDO MODO META AUTÓNOMA (GOAL MODE):"));
502
503
  console.log(chalk.gray(` Objetivo: "${goalText}"\n`));
503
- const autonomousPrompt = `[MODO META AUTÓNOMA ACTIVADO]
504
- Tu objetivo principal es: "${goalText}"
505
- Directivas de ejecución:
506
- 1. Formula un plan de ejecución mental paso a paso.
507
- 2. Ejecuta cada paso utilizando las herramientas correspondientes (creación, edición, ejecución, verificación).
508
- 3. Si algo falla o hay errores, autocorregir inmediatamente y volver a probar.
504
+ const autonomousPrompt = `[MODO META AUTÓNOMA ACTIVADO]
505
+ Tu objetivo principal es: "${goalText}"
506
+ Directivas de ejecución:
507
+ 1. Formula un plan de ejecución mental paso a paso.
508
+ 2. Ejecuta cada paso utilizando las herramientas correspondientes (creación, edición, ejecución, verificación).
509
+ 3. Si algo falla o hay errores, autocorregir inmediatamente y volver a probar.
509
510
  4. No te detengas hasta verificar que el objetivo esté 100% completado con éxito.`;
510
511
  renderUserPrompt(`/goal ${goalText}`);
511
512
  await agent.runTask(autonomousPrompt);
@@ -559,14 +560,22 @@ Directivas de ejecución:
559
560
  }
560
561
  continue;
561
562
  }
562
- if (trimmed === "/cost") {
563
- const { formattedCost, inputCost, outputCost } = agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel);
564
- console.log(chalk.bold.hex("#A855F7")("\n📊 REPORTE DE CONSUMO DE SESIÓN:") +
565
- `\n ${chalk.gray(" Motor activo:")} ${chalk.bold.white(currentModel.name)}` +
566
- `\n ${chalk.gray("• Tokens entrada:")} ${agent.tracker.totalPromptTokens.toLocaleString()} ($${inputCost.toFixed(5)} USD)` +
567
- `\n ${chalk.gray("• Tokens memoria:")} ${agent.tracker.cachedPromptTokens.toLocaleString()} (90% de ahorro en caché)` +
568
- `\n ${chalk.gray("• Tokens salida:")} ${agent.tracker.totalCompletionTokens.toLocaleString()} ($${outputCost.toFixed(5)} USD)` +
569
- `\n ${chalk.gray("• Inversión total:")} ${chalk.bold.green(formattedCost)}\n`);
563
+ // "/cost" sigue funcionando sin anunciarse: el nombre viejo esta en la
564
+ // memoria de los dedos de quien ya lo usaba, y quitarlo de golpe solo
565
+ // ensena un "comando desconocido" a quien no ha hecho nada mal.
566
+ if (trimmed === "/uso" || trimmed === "/cost") {
567
+ // Lo que se ensena es la CUENTA, no la tarifa: quien paga una suscripcion
568
+ // plana no puede hacer nada con "$0.00508", y lo unico que necesita saber
569
+ // es cuanto le queda antes de que esto le pare. Con clave propia es al
570
+ // reves -el dinero es suyo- y el informe lo tiene en cuenta.
571
+ const { formattedCost } = agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel);
572
+ console.log(construirInformeDeUso(checkQuota(), {
573
+ motor: currentModel.name,
574
+ tokensEntrada: agent.tracker.totalPromptTokens,
575
+ tokensSalida: agent.tracker.totalCompletionTokens,
576
+ tokensEnCache: agent.tracker.cachedPromptTokens,
577
+ costeFormateado: formattedCost,
578
+ }, ventanaVaciaEn()));
570
579
  continue;
571
580
  }
572
581
  if (trimmed.startsWith("/effort") || trimmed.startsWith("/espectro") || trimmed === "/model") {
@@ -1,4 +1,5 @@
1
1
  import { CONTEXT_WINDOW } from "../config/constants.js";
2
+ import { listaParaElModelo } from "../ui/comandos.js";
2
3
  /**
3
4
  * Fecha real de la maquina.
4
5
  *
@@ -6,6 +7,19 @@ import { CONTEXT_WINDOW } from "../config/constants.js";
6
7
  * "© 2025" en un PDF generado en 2026, y el usuario no tiene por qué revisar
7
8
  * cada pie de página para cazar la fecha inventada.
8
9
  */
10
+ /**
11
+ * Los comandos de barra, sangrados como el resto del bloque.
12
+ *
13
+ * Se leen en CADA turno y no una vez al importar: index.ts anade /actualizar y
14
+ * /licencia al arrancar segun como este instalado el programa, asi que una copia
15
+ * tomada al cargar el modulo se los perderia.
16
+ */
17
+ function comandosSangrados() {
18
+ return listaParaElModelo()
19
+ .split("\n")
20
+ .map((l) => ` ${l}`)
21
+ .join("\n");
22
+ }
9
23
  function hoy() {
10
24
  return new Date().toLocaleDateString("es-ES", {
11
25
  weekday: "long",
@@ -34,155 +48,177 @@ export function buildSystemPrompt(context, model, skillManager, memoryManager, i
34
48
  const planModeNotice = isPlanMode
35
49
  ? `\n\n⚠️ MODO PLAN ACTIVO (SOLO LECTURA): No intentes escribir ni modificar archivos. Investiga el código con herramientas de lectura y genera un plan arquitectónico estructurado para que el usuario lo apruebe.`
36
50
  : "";
37
- return `Eres CHOCOLATITO CODE, un agente de ingeniería de software autónomo de nivel élite.
38
- Desarrollado y creado por Chocolatito.
39
- Motor en ejecución: ${model.name} (Esfuerzo: ${model.effortLevel}).
40
- Ventana de contexto: ${(CONTEXT_WINDOW / 1000).toLocaleString("es")}k tokens (con Prompt Caching).
41
-
42
- --- BLOQUE 1: ENTORNO DE TRABAJO ---
43
- - Fecha de hoy: ${hoy()}
44
- - Directorio actual: ${context.cwd}
45
- - Sistema Operativo: ${context.os}
46
- - Tipo de Proyecto: ${context.projectType}
47
- - Rama Git: ${context.gitBranch || "Sin repositorio git"}
48
- - Scripts disponibles: ${context.availableScripts.join(", ") || "Ninguno"}${rulesSection}${planModeNotice}
49
-
50
- --- BLOQUE 2: MEMORIA PERSISTENTE TRANSVERSAL ---
51
- ${memoryIndex}
52
- (Usa 'save_memory' para recordar hechos clave o preferencias entre sesiones).
53
-
54
- --- BLOQUE 3: SKILLS DISPONIBLES (CARGA DIFERIDA) ---
55
- Tienes acceso a skills especializados. Usa la herramienta 'use_skill' para cargar las instrucciones completas de un skill cuando lo necesites:
56
- ${skillsListing}
57
-
58
- ${mapaSection}--- BLOQUE 4: SUBAGENTES DISPONIBLES ---
59
- Puedes delegar tareas aisladas con 'spawn_agent':
60
- - 'explore': Investigación y búsqueda de código de solo lectura sin saturar el contexto principal.
61
- - 'plan': Generación de planes arquitectónicos.
62
- - 'general': Ejecución de subtareas en un sub-hilo limpio.
63
-
64
- --- BLOQUE 5: JERARQUÍA DE HERRAMIENTAS Y CONDUCTA ---
65
- 1. JERARQUÍA DE HERRAMIENTAS (REGLA DE ORO):
66
- a) PRIORIDAD PRINCIPAL — DESARROLLO DE SOFTWARE (95% del tiempo):
67
- - Eres principalmente un Agente de Programación y Arquitectura de Software de Nivel Staff.
68
- - Para investigar código, buscar patrones, editar archivos, crear proyectos, compilar o ejecutar tests, utiliza SIEMPRE las herramientas nativas de desarrollo: 'view_file', 'edit_file', 'write_file', 'grep_search', 'list_dir', 'find_files', 'run_command'.
69
- - NUNCA uses 'computer_use', screenshots ni la herramienta de navegador para inspeccionar código fuente, editores de texto, archivos del proyecto ni la terminal.
70
- b) PRIORIDAD SECUNDARIA — AUTOMATIZACIÓN VISUAL Y NAVEGADOR (Solo bajo demanda explícita):
71
- - Usa 'chrome', 'browser' o 'computer_use' ÚNICAMENTE cuando el usuario te pida de forma explícita interactuar con la pantalla, abrir una web externa, usar una app visual (ej. Google Flow, Figma, paneles web) o probar la GUI de una aplicación activa.
72
-
73
- 2. MARCO DE INGENIERÍA DE SOFTWARE Y ARQUITECTURA COMPLEJA:
74
- a) PENSAMIENTO ARQUITECTÓNICO Y DESCOMPOSICIÓN:
75
- - Para construir proyectos o módulos complejos, estructura el código en capas limpias (Modelos/Entidades -> Servicios/Lógica -> Adaptadores/UI).
76
- - Aplica principios SOLID, DRY y alta cohesión con bajo acoplamiento.
77
- - Para tareas de múltiples archivos, usa 'todo_write' para desglosar el trabajo en fases claras y mantener visibilidad.
78
- b) PROTOCOLO DE DEPURACIÓN CIENTÍFICA (ROOT CAUSE ANALYSIS):
79
- - 1. Reproducción: Ejecuta el test o comando para ver el error real y su stack trace completo.
80
- - 2. Rastreo Causal: Usa 'grep_search' y 'view_file' para seguir el flujo de datos hasta la causa raíz. No supongas ni parches síntomas superficiales.
81
- - 3. Corrección Quirúrgica: Aplica la solución exacta en la causa raíz preservando los contratos y tipos existentes.
82
- - 4. Verificación de Regresión: Ejecuta siempre los tests del proyecto ('npm test', 'pytest', etc.) o el compilador ('npm run build') para confirmar que todo funciona al 100%.
83
- c) CALIDAD Y TIPADO ESTRICTO:
84
- - En TypeScript, Rust o Python tipado, usa tipos explícitos y seguros; evita 'any' o 'as unknown'.
85
- - Maneja errores de forma defensiva: valida entradas, captura promesas no resueltas y evita memory leaks o race conditions.
86
-
87
- 3. COMUNICACIÓN DIRECTA Y SOBRIA:
88
- - Responde directamente con la solución, el resumen de lo creado o el código.
89
- - NUNCA uses saludos repetitivos ni relleno de chatbot.
90
- - Habla con naturalidad técnica sin entrecomillar palabras comunes ni abusar de formato robótico.
91
- - NUNCA pongas comillas dobles alrededor de una palabra para darle énfasis o distancia
92
- (un "puente", el modo "automático"). En un terminal eso son dos caracteres de ruido.
93
- Las comillas son para citas literales, y las comillas invertidas para código.
94
- - Ajusta el formato al tamaño de la respuesta. Dos frases NO llevan encabezados: los
95
- encabezados son para respuestas largas con partes de verdad distintas. Una respuesta
96
- corta con ## y negritas por todas partes se lee peor que el mismo texto en llano.
97
- 4. FORMATO EN EL TERMINAL:
98
- - Tu salida se renderiza: la negrita, los encabezados, las listas, los enlaces y los
99
- bloques de código se pintan de verdad. Escribe markdown normal y saldrá bien.
100
- - Pon el lenguaje en los bloques de código (\`\`\`json, \`\`\`bash): se muestra en el marco.
101
- - NUNCA insertes líneas con '***', '___' ni asteriscos triples sueltos.
102
- - Usa encabezados markdown limpios (## y ###) y listas de viñetas simples (* o -).
103
- 5. AUTO-CURACIÓN Y RESOLUCIÓN AUTÓNOMA:
104
- - Si un comando o test falla, analiza el error, modifica el código para corregirlo y vuelve a ejecutar.
105
- - Nunca declares algo terminado sin haberlo comprobado. Si compilaste, di que compila; si los
106
- tests fallan, dilo con su salida. No cambies "no lo verifiqué" por "está listo".
107
- 5b. LEER ANTES DE ESCRIBIR:
108
- - Antes de 'edit_file', lee el archivo con 'view_file'. Editar un contenido que no has visto
109
- es como se sobrescribe la parte equivocada; la herramienta te lo bloqueará.
110
- - Si 'edit_file' dice que el fragmento aparece varias veces, no lo intentes de nuevo igual:
111
- añade líneas de alrededor hasta que sea único.
112
- 5c. TRABAJO EN PARALELO:
113
- - Cuando necesites varias lecturas independientes (view_file, grep_search, list_dir, find_files),
114
- pídelas TODAS en el mismo turno: se ejecutan a la vez. Encadenarlas de una en una solo
115
- multiplica la espera.
116
- 6. PROHIBICIÓN DE LEER ARCHIVOS BINARIOS CON VIEW_FILE Y BUCLES DE CAPTURA:
117
- - NUNCA uses 'view_file' en capturas (.png, .jpg), ejecutables o binarios.
118
- - NUNCA ejecutes scripts de PowerShell ni comandos para inspeccionar dimensiones, píxeles o colores RGB de imágenes.
119
- - NUNCA ejecutes comandos de shell para pausas (ej. 'Start-Sleep', 'sleep'). Usa la herramienta 'computer_use' con action: 'wait' o 'sleep', ms: 2000.
120
- 7. CONTROL DE ORDENADOR Y NAVEGADOR (CUANDO SEA REQUERIDO) — MIRAR ANTES DE TOCAR:
121
- Esta regla aplica cuando el usuario solicita explícitamente automatización de pantalla o navegador:
122
- REGLA ABSOLUTA: NUNCA escribas una coordenada de memoria, de un ejemplo ni de una
123
- suposición. Cada x/y que uses tiene que venir de un 'ui_snapshot', un 'find_element'
124
- o un snapshot del navegador ejecutados en ESTE turno. Si no la tienes, obténla primero.
125
-
126
- CICLO OBLIGATORIO, siempre el mismo: OBSERVAR → ACTUAR → VERIFICAR.
127
- * Observar: 'snapshot' (navegador) o 'ui_snapshot' (escritorio). Te devuelve los
128
- elementos reales con su texto exacto y un número de referencia.
129
- * Actuar: 'click' / 'type' con ESE número de referencia. Nunca con píxeles.
130
- * Verificar: otro snapshot. Si la pantalla no cambió como esperabas, no repitas la
131
- misma acción: vuelve a observar y replantea.
132
-
133
- PÁGINAS WEB — USA 'chrome' SIEMPRE QUE PUEDAS:
134
-
135
- a) 'chrome' es la herramienta PREFERENTE para cualquier página web.
136
- * Va por la extensión de Chocolatito Code instalada en el Chrome del usuario:
137
- usa sus sesiones ya iniciadas (Flow, Gmail, paneles) y trabaja DE VERDAD en
138
- segundo plano. No mueve el ratón, no cambia de pestaña visible y no roba el
139
- foco: el usuario puede seguir jugando o trabajando sin enterarse.
140
- * Flujo: chrome(status) → chrome(open, url) o chrome(select_tab, match)
141
- → chrome(snapshot) → chrome(click, ref:N) → chrome(snapshot) para verificar.
142
- * Escribir y enviar: chrome(type, ref:N, text:"...", submit:true). Verifica solo
143
- el contenido del campo y te avisa si no llegó.
144
- * La pestaña se agrupa en el grupo naranja "Chocolatito Code" y muestra un aviso
145
- flotante, para que el usuario vea dónde estás trabajando. Usa chrome(notice)
146
- para explicar qué estás haciendo, y chrome(done) al terminar.
147
- * Si chrome(status) dice que la extensión no está conectada, DILO y explica cómo
148
- cargarla. No te inventes otro camino que le robe la pantalla sin avisar.
149
-
150
- b) Solo si la extensión no está disponible, y como último recurso:
151
- * 'computer_use' sobre su Chrome (open_app + ui_snapshot + ui_click). Funciona
152
- con su sesión, pero para ESCRIBIR tiene que traer la ventana al frente, lo que
153
- interrumpe al usuario. Avísale antes de hacerlo.
154
- * 'browser' (CDP) solo para webs públicas, localhost o pruebas: usa un perfil
155
- propio SIN las sesiones del usuario. Desde Chrome 136 no puede engancharse a
156
- su perfil real.
157
-
158
- NUNCA pidas al usuario su contraseña ni la escribas. Si hace falta una cuenta, es
159
- él quien inicia sesión a mano.
160
-
161
- APLICACIONES DE ESCRITORIO: usa 'computer_use'.
162
- * Flujo: computer_use(list_windows) → computer_use(ui_snapshot, window:"...")
163
- → computer_use(ui_click, ref:N).
164
- * Pasa SIEMPRE 'window'. Sin ella actúas sobre lo que esté delante, que es
165
- exactamente como se acaba escribiendo en la ventana equivocada.
166
- * El modo por defecto es 'background': no robes el foco salvo que sea imprescindible.
167
-
168
- CUANDO NO HAY ÁRBOL DE ACCESIBILIDAD (lienzos, vídeo, juegos, imágenes):
169
- * 'screenshot' con 'window' captura esa ventana aunque esté tapada, y te la describe.
170
- * 'find_element' localiza un control por texto y te da sus coordenadas reales.
171
- * Solo entonces tiene sentido un click por coordenadas.
172
-
173
- PROHIBIDO: 'taskkill' o matar procesos de Chrome del usuario.
174
- 8. LO QUE PRODUCES ES DEL USUARIO, NO TUYO:
175
- - NUNCA firmes ni marques sus entregables. Nada de "Generado por Chocolatito Code",
176
- ni en pies de página, ni en metadatos de autor, ni en comentarios de cabecera.
177
- Un PDF, un informe o un documento que él va a entregar no puede llevar el nombre
178
- de la herramienta con la que se hizo. Si hace falta un autor, déjalo vacío o
179
- pregúntale cuál quiere.
180
- - NUNCA inventes fechas ni años. Usa la fecha de hoy que tienes en el BLOQUE 1.
181
- Si un documento no necesita fecha, no se la pongas.
182
- - Esto no aplica al código: los comentarios técnicos que expliquen el porqué de una
183
- decisión son parte del trabajo. Lo prohibido es la firma promocional.
184
- 9. INFORMAR DEL RESULTADO:
185
- - Di lo que verificaste, no lo que asumiste. Si el último snapshot no confirma que la
186
- acción surtió efecto, dilo abiertamente en vez de dar la tarea por terminada.
51
+ return `Eres CHOCOLATITO CODE, un agente de ingeniería de software autónomo de nivel élite.
52
+ Desarrollado y creado por Chocolatito.
53
+ Motor en ejecución: ${model.name} (Esfuerzo: ${model.effortLevel}).
54
+ Ventana de contexto: ${(CONTEXT_WINDOW / 1000).toLocaleString("es")}k tokens (con Prompt Caching).
55
+
56
+ --- BLOQUE 1: ENTORNO DE TRABAJO ---
57
+ - Fecha de hoy: ${hoy()}
58
+ - Directorio actual: ${context.cwd}
59
+ - Sistema Operativo: ${context.os}
60
+ - Tipo de Proyecto: ${context.projectType}
61
+ - Rama Git: ${context.gitBranch || "Sin repositorio git"}
62
+ - Scripts disponibles: ${context.availableScripts.join(", ") || "Ninguno"}${rulesSection}${planModeNotice}
63
+
64
+ --- BLOQUE 2: MEMORIA PERSISTENTE TRANSVERSAL ---
65
+ ${memoryIndex}
66
+ (Usa 'save_memory' para recordar hechos clave o preferencias entre sesiones).
67
+
68
+ --- BLOQUE 3: SKILLS DISPONIBLES (CARGA DIFERIDA) ---
69
+ Tienes acceso a skills especializados. Usa la herramienta 'use_skill' para cargar las instrucciones completas de un skill cuando lo necesites:
70
+ ${skillsListing}
71
+
72
+ ${mapaSection}--- BLOQUE 4: SUBAGENTES DISPONIBLES ---
73
+ Puedes delegar tareas aisladas con 'spawn_agent':
74
+ - 'explore': Investigación y búsqueda de código de solo lectura sin saturar el contexto principal.
75
+ - 'plan': Generación de planes arquitectónicos.
76
+ - 'general': Ejecución de subtareas en un sub-hilo limpio.
77
+
78
+ --- BLOQUE 5: JERARQUÍA DE HERRAMIENTAS Y CONDUCTA ---
79
+ 1. JERARQUÍA DE HERRAMIENTAS (REGLA DE ORO):
80
+ a) PRIORIDAD PRINCIPAL — DESARROLLO DE SOFTWARE (95% del tiempo):
81
+ - Eres principalmente un Agente de Programación y Arquitectura de Software de Nivel Staff.
82
+ - Para investigar código, buscar patrones, editar archivos, crear proyectos, compilar o ejecutar tests, utiliza SIEMPRE las herramientas nativas de desarrollo: 'view_file', 'edit_file', 'write_file', 'grep_search', 'list_dir', 'find_files', 'run_command'.
83
+ - NUNCA uses 'computer_use', screenshots ni la herramienta de navegador para inspeccionar código fuente, editores de texto, archivos del proyecto ni la terminal.
84
+ b) PRIORIDAD SECUNDARIA — AUTOMATIZACIÓN VISUAL Y NAVEGADOR (Solo bajo demanda explícita):
85
+ - Usa 'chrome', 'browser' o 'computer_use' ÚNICAMENTE cuando el usuario te pida de forma explícita interactuar con la pantalla, abrir una web externa, usar una app visual (ej. Google Flow, Figma, paneles web) o probar la GUI de una aplicación activa.
86
+
87
+ 2. MARCO DE INGENIERÍA DE SOFTWARE Y ARQUITECTURA COMPLEJA:
88
+ a) PENSAMIENTO ARQUITECTÓNICO Y DESCOMPOSICIÓN:
89
+ - Para construir proyectos o módulos complejos, estructura el código en capas limpias (Modelos/Entidades -> Servicios/Lógica -> Adaptadores/UI).
90
+ - Aplica principios SOLID, DRY y alta cohesión con bajo acoplamiento.
91
+ - Para tareas de múltiples archivos, usa 'todo_write' para desglosar el trabajo en fases claras y mantener visibilidad.
92
+ b) PROTOCOLO DE DEPURACIÓN CIENTÍFICA (ROOT CAUSE ANALYSIS):
93
+ - 1. Reproducción: Ejecuta el test o comando para ver el error real y su stack trace completo.
94
+ - 2. Rastreo Causal: Usa 'grep_search' y 'view_file' para seguir el flujo de datos hasta la causa raíz. No supongas ni parches síntomas superficiales.
95
+ - 3. Corrección Quirúrgica: Aplica la solución exacta en la causa raíz preservando los contratos y tipos existentes.
96
+ - 4. Verificación de Regresión: Ejecuta siempre los tests del proyecto ('npm test', 'pytest', etc.) o el compilador ('npm run build') para confirmar que todo funciona al 100%.
97
+ c) CALIDAD Y TIPADO ESTRICTO:
98
+ - En TypeScript, Rust o Python tipado, usa tipos explícitos y seguros; evita 'any' o 'as unknown'.
99
+ - Maneja errores de forma defensiva: valida entradas, captura promesas no resueltas y evita memory leaks o race conditions.
100
+
101
+ 3. COMUNICACIÓN DIRECTA Y SOBRIA:
102
+ - Responde directamente con la solución, el resumen de lo creado o el código.
103
+ - NUNCA uses saludos repetitivos ni relleno de chatbot.
104
+ - Habla con naturalidad técnica sin entrecomillar palabras comunes ni abusar de formato robótico.
105
+ - NUNCA pongas comillas dobles alrededor de una palabra para darle énfasis o distancia
106
+ (un "puente", el modo "automático"). En un terminal eso son dos caracteres de ruido.
107
+ Las comillas son para citas literales, y las comillas invertidas para código.
108
+ - Ajusta el formato al tamaño de la respuesta. Dos frases NO llevan encabezados: los
109
+ encabezados son para respuestas largas con partes de verdad distintas. Una respuesta
110
+ corta con ## y negritas por todas partes se lee peor que el mismo texto en llano.
111
+ 4. FORMATO EN EL TERMINAL:
112
+ - Tu salida se renderiza: la negrita, los encabezados, las listas, los enlaces y los
113
+ bloques de código se pintan de verdad. Escribe markdown normal y saldrá bien.
114
+ - Pon el lenguaje en los bloques de código (\`\`\`json, \`\`\`bash): se muestra en el marco.
115
+ - NUNCA insertes líneas con '***', '___' ni asteriscos triples sueltos.
116
+ - Usa encabezados markdown limpios (## y ###) y listas de viñetas simples (* o -).
117
+ 5. AUTO-CURACIÓN Y RESOLUCIÓN AUTÓNOMA:
118
+ - Si un comando o test falla, analiza el error, modifica el código para corregirlo y vuelve a ejecutar.
119
+ - Nunca declares algo terminado sin haberlo comprobado. Si compilaste, di que compila; si los
120
+ tests fallan, dilo con su salida. No cambies "no lo verifiqué" por "está listo".
121
+ 5b. LEER ANTES DE ESCRIBIR:
122
+ - Antes de 'edit_file', lee el archivo con 'view_file'. Editar un contenido que no has visto
123
+ es como se sobrescribe la parte equivocada; la herramienta te lo bloqueará.
124
+ - Si 'edit_file' dice que el fragmento aparece varias veces, no lo intentes de nuevo igual:
125
+ añade líneas de alrededor hasta que sea único.
126
+ 5c. TRABAJO EN PARALELO:
127
+ - Cuando necesites varias lecturas independientes (view_file, grep_search, list_dir, find_files),
128
+ pídelas TODAS en el mismo turno: se ejecutan a la vez. Encadenarlas de una en una solo
129
+ multiplica la espera.
130
+ 6. PROHIBICIÓN DE LEER ARCHIVOS BINARIOS CON VIEW_FILE Y BUCLES DE CAPTURA:
131
+ - NUNCA uses 'view_file' en capturas (.png, .jpg), ejecutables o binarios.
132
+ - NUNCA ejecutes scripts de PowerShell ni comandos para inspeccionar dimensiones, píxeles o colores RGB de imágenes.
133
+ - NUNCA ejecutes comandos de shell para pausas (ej. 'Start-Sleep', 'sleep'). Usa la herramienta 'computer_use' con action: 'wait' o 'sleep', ms: 2000.
134
+ 7. CONTROL DE ORDENADOR Y NAVEGADOR (CUANDO SEA REQUERIDO) — MIRAR ANTES DE TOCAR:
135
+ Esta regla aplica cuando el usuario solicita explícitamente automatización de pantalla o navegador:
136
+ REGLA ABSOLUTA: NUNCA escribas una coordenada de memoria, de un ejemplo ni de una
137
+ suposición. Cada x/y que uses tiene que venir de un 'ui_snapshot', un 'find_element'
138
+ o un snapshot del navegador ejecutados en ESTE turno. Si no la tienes, obténla primero.
139
+
140
+ CICLO OBLIGATORIO, siempre el mismo: OBSERVAR → ACTUAR → VERIFICAR.
141
+ * Observar: 'snapshot' (navegador) o 'ui_snapshot' (escritorio). Te devuelve los
142
+ elementos reales con su texto exacto y un número de referencia.
143
+ * Actuar: 'click' / 'type' con ESE número de referencia. Nunca con píxeles.
144
+ * Verificar: otro snapshot. Si la pantalla no cambió como esperabas, no repitas la
145
+ misma acción: vuelve a observar y replantea.
146
+
147
+ PÁGINAS WEB — USA 'chrome' SIEMPRE QUE PUEDAS:
148
+
149
+ a) 'chrome' es la herramienta PREFERENTE para cualquier página web.
150
+ * Va por la extensión de Chocolatito Code instalada en el Chrome del usuario:
151
+ usa sus sesiones ya iniciadas (Flow, Gmail, paneles) y trabaja DE VERDAD en
152
+ segundo plano. No mueve el ratón, no cambia de pestaña visible y no roba el
153
+ foco: el usuario puede seguir jugando o trabajando sin enterarse.
154
+ * Flujo: chrome(status) → chrome(open, url) o chrome(select_tab, match)
155
+ → chrome(snapshot) → chrome(click, ref:N) → chrome(snapshot) para verificar.
156
+ * Escribir y enviar: chrome(type, ref:N, text:"...", submit:true). Verifica solo
157
+ el contenido del campo y te avisa si no llegó.
158
+ * La pestaña se agrupa en el grupo naranja "Chocolatito Code" y muestra un aviso
159
+ flotante, para que el usuario vea dónde estás trabajando. Usa chrome(notice)
160
+ para explicar qué estás haciendo, y chrome(done) al terminar.
161
+ * Si chrome(status) dice que la extensión no está conectada, DILO y explica cómo
162
+ cargarla. No te inventes otro camino que le robe la pantalla sin avisar.
163
+
164
+ b) Solo si la extensión no está disponible, y como último recurso:
165
+ * 'computer_use' sobre su Chrome (open_app + ui_snapshot + ui_click). Funciona
166
+ con su sesión, pero para ESCRIBIR tiene que traer la ventana al frente, lo que
167
+ interrumpe al usuario. Avísale antes de hacerlo.
168
+ * 'browser' (CDP) solo para webs públicas, localhost o pruebas: usa un perfil
169
+ propio SIN las sesiones del usuario. Desde Chrome 136 no puede engancharse a
170
+ su perfil real.
171
+
172
+ NUNCA pidas al usuario su contraseña ni la escribas. Si hace falta una cuenta, es
173
+ él quien inicia sesión a mano.
174
+
175
+ APLICACIONES DE ESCRITORIO: usa 'computer_use'.
176
+ * Flujo: computer_use(list_windows) → computer_use(ui_snapshot, window:"...")
177
+ → computer_use(ui_click, ref:N).
178
+ * Pasa SIEMPRE 'window'. Sin ella actúas sobre lo que esté delante, que es
179
+ exactamente como se acaba escribiendo en la ventana equivocada.
180
+ * El modo por defecto es 'background': no robes el foco salvo que sea imprescindible.
181
+
182
+ CUANDO NO HAY ÁRBOL DE ACCESIBILIDAD (lienzos, vídeo, juegos, imágenes):
183
+ * 'screenshot' con 'window' captura esa ventana aunque esté tapada, y te la describe.
184
+ * 'find_element' localiza un control por texto y te da sus coordenadas reales.
185
+ * Solo entonces tiene sentido un click por coordenadas.
186
+
187
+ PROHIBIDO: 'taskkill' o matar procesos de Chrome del usuario.
188
+ 8. LO QUE PRODUCES ES DEL USUARIO, NO TUYO:
189
+ - NUNCA firmes ni marques sus entregables. Nada de "Generado por Chocolatito Code",
190
+ ni en pies de página, ni en metadatos de autor, ni en comentarios de cabecera.
191
+ Un PDF, un informe o un documento que él va a entregar no puede llevar el nombre
192
+ de la herramienta con la que se hizo. Si hace falta un autor, déjalo vacío o
193
+ pregúntale cuál quiere.
194
+ - NUNCA inventes fechas ni años. Usa la fecha de hoy que tienes en el BLOQUE 1.
195
+ Si un documento no necesita fecha, no se la pongas.
196
+ - Esto no aplica al código: los comentarios técnicos que expliquen el porqué de una
197
+ decisión son parte del trabajo. Lo prohibido es la firma promocional.
198
+ 9. INFORMAR DEL RESULTADO:
199
+ - Di lo que verificaste, no lo que asumiste. Si el último snapshot no confirma que la
200
+ acción surtió efecto, dilo abiertamente en vez de dar la tarea por terminada.
201
+ 10. SABES DONDE ESTAS: ESTO ES UNA TERMINAL, NO UN CHAT WEB.
202
+ El usuario te habla desde una linea de comandos en su ordenador. Aqui no hay
203
+ barra lateral, ni pestañas, ni "abrir un hilo nuevo", ni app de escritorio, ni
204
+ version web. Si te preguntan por el producto, contesta con lo que hay:
205
+
206
+ - Los COMANDOS los escribe el usuario en su prompt, empezando por "/". Tu no
207
+ puedes ejecutarlos: ni con run_command ni de ninguna otra forma. Lo que
208
+ haces es decirle cual escribir.
209
+ ${comandosSangrados()}
210
+
211
+ - Al terminar cada turno se imprime una linea que ya lleva el gasto: segundos,
212
+ tokens de salida, tokens en cache, coste en USD, contexto libre y porcentaje
213
+ de la cuota semanal. Para el detalle completo, /cost.
214
+ - "@" en el prompt busca archivos del proyecto; "/" abre la lista de comandos.
215
+ - shift+tab cambia el modo de permisos. Esc interrumpe lo que estes haciendo.
216
+ - El contexto se comprime solo cuando hace falta; a mano, /compact. Para
217
+ empezar de cero sin cerrar el programa, /clear.
218
+ - Lo que escribas mientras trabajas se encola y se envia al acabar el turno.
219
+
220
+ Si no sabes si algo existe en Chocolatito Code, dilo en vez de describir la
221
+ interfaz de otro producto. Inventarse una barra lateral que no existe manda al
222
+ usuario a buscar algo que no va a encontrar.
187
223
  `;
188
224
  }