chocolatito-code 1.6.12 → 1.6.13

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.
@@ -0,0 +1,41 @@
1
+ /**
2
+ * LA CAJA NEGRA DE LOS CORTES
3
+ *
4
+ * DE DONDE SALE
5
+ *
6
+ * Cinco respuestas seguidas cortadas a mitad de frase, y de todas ellas no
7
+ * quedo NADA con lo que trabajar: el aviso salia por pantalla, el usuario
8
+ * seguia tecleando y el rastro se perdia con el scroll. Al dia siguiente, la
9
+ * unica pregunta que importaba -"corta siempre por el mismo sitio?"- no se
10
+ * podia contestar.
11
+ *
12
+ * Aqui se apunta lo justo para poder contestarla la proxima vez, que es un
13
+ * renglon por corte:
14
+ *
15
+ * 2026-09-12T14:02:11Z modelo=deepseek-chat ms=1840 trozos=37
16
+ * texto=214 razonamiento=0 aviso=motor
17
+ *
18
+ * Con eso se distingue lo que hoy no se distingue:
19
+ *
20
+ * - Siempre a los mismos milisegundos -> algo lo esta cortando por tiempo.
21
+ * - Siempre en los mismos bytes -> un limite de tamano por el camino.
22
+ * - Numeros dispares -> el motor de arriba, que va a rachas.
23
+ * - `aviso=motor` -> lo dijo el proxy: el corte viene del motor.
24
+ * - `aviso=mudo` -> nadie dijo nada: se murio el proxy por el camino.
25
+ *
26
+ * NO se guarda ni una palabra de la conversacion. Solo cuentas: tamanos,
27
+ * tiempos y el nombre del motor. Un diagnostico no vale una fuga.
28
+ */
29
+ export interface Corte {
30
+ modelo: string;
31
+ ms: number;
32
+ trozos: number;
33
+ bytesTexto: number;
34
+ bytesRazonamiento: number;
35
+ /** "motor" si el proxy lo anuncio; "mudo" si el stream murio sin decir nada. */
36
+ aviso: "motor" | "mudo";
37
+ }
38
+ export declare function rutaDelRegistro(home?: string): string;
39
+ export declare function apuntarCorte(corte: Corte, home?: string): string | null;
40
+ /** Lo apuntado, de lo mas nuevo a lo mas viejo. Para /cortes. */
41
+ export declare function leerCortes(limite?: number, home?: string): string[];
@@ -0,0 +1,52 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ export function rutaDelRegistro(home = os.homedir()) {
5
+ return path.join(home, ".chocolatito", "cortes.log");
6
+ }
7
+ /** Cuantos cortes se guardan. Mas atras que esto ya no dice nada nuevo. */
8
+ const MAXIMO_DE_LINEAS = 200;
9
+ export function apuntarCorte(corte, home) {
10
+ const ruta = rutaDelRegistro(home);
11
+ const linea = [
12
+ new Date().toISOString(),
13
+ `modelo=${corte.modelo}`,
14
+ `ms=${corte.ms}`,
15
+ `trozos=${corte.trozos}`,
16
+ `texto=${corte.bytesTexto}`,
17
+ `razonamiento=${corte.bytesRazonamiento}`,
18
+ `aviso=${corte.aviso}`,
19
+ ].join(" ") + "\n";
20
+ try {
21
+ fs.mkdirSync(path.dirname(ruta), { recursive: true });
22
+ let previo = "";
23
+ try {
24
+ previo = fs.readFileSync(ruta, "utf-8");
25
+ }
26
+ catch { }
27
+ // Se recorta al vuelo: un registro que crece sin fin acaba siendo otro
28
+ // problema, y aqui lo viejo no aporta.
29
+ const lineas = (previo + linea).split("\n").filter((l) => l.trim().length > 0);
30
+ const recortado = lineas.slice(-MAXIMO_DE_LINEAS).join("\n") + "\n";
31
+ fs.writeFileSync(ruta, recortado, "utf-8");
32
+ return ruta;
33
+ }
34
+ catch {
35
+ // Si no se puede escribir, no pasa nada: esto es un extra, no el aviso.
36
+ return null;
37
+ }
38
+ }
39
+ /** Lo apuntado, de lo mas nuevo a lo mas viejo. Para /cortes. */
40
+ export function leerCortes(limite = 20, home) {
41
+ try {
42
+ return fs
43
+ .readFileSync(rutaDelRegistro(home), "utf-8")
44
+ .split("\n")
45
+ .filter((l) => l.trim().length > 0)
46
+ .slice(-limite)
47
+ .reverse();
48
+ }
49
+ catch {
50
+ return [];
51
+ }
52
+ }
@@ -10,6 +10,7 @@ import { canRunInParallel } from "../tools/safety.js";
10
10
  import { createEngineClient, explicarErrorDelMotor, motivoDeParadaDelError, } from "../config/engine.js";
11
11
  import { anotarHuecosOcupados, esperarAlHueco, liberarHuecos, mensajeDeEspera, segundosDeEspera, } from "./huecos.js";
12
12
  import { MarkdownStream } from "../ui/markdownStream.js";
13
+ import { apuntarCorte } from "./cortes.js";
13
14
  import { checkQuota, recordUsage, mensajeDeCorte, quotaBadge } from "../config/quota.js";
14
15
  import { ReasoningStream } from "../ui/reasoningStream.js";
15
16
  import { PermissionManager, commandSignature, isDangerousCommand } from "../config/permissions.js";
@@ -630,6 +631,10 @@ export class AgentLoop {
630
631
  * respuesta de un motor que no mande esa marca.
631
632
  */
632
633
  let llegoElFinal = false;
634
+ // Para la caja negra: sin estos dos numeros, un corte no se puede
635
+ // comparar con el siguiente, y comparar es lo unico que da un patron.
636
+ let trozos = 0;
637
+ const empezoEn = Date.now();
633
638
  const rawToolCalls = [];
634
639
  const reasoningStream = new ReasoningStream();
635
640
  // Por `salida`, no por stdout: mientras se escribe la respuesta el spinner
@@ -641,6 +646,7 @@ export class AgentLoop {
641
646
  // fijo, asi que la respuesta sale por encima sin taparlo y el contador
642
647
  // de segundos sigue a la vista mientras el modelo habla, que es
643
648
  // exactamente cuando uno quiere saber cuanto lleva.
649
+ trozos++;
644
650
  const delta = chunk.choices?.[0]?.delta;
645
651
  // Handle real-time reasoning stream
646
652
  if (delta?.reasoning_content) {
@@ -714,6 +720,23 @@ export class AgentLoop {
714
720
  }
715
721
  if (esDemasiadasEnCurso(streamErr))
716
722
  anotarHuecosOcupados(streamErr?.error?.retry_after_s);
723
+ if (String(streamErr?.error?.code ?? "") === "motor_corto_la_respuesta") {
724
+ apuntarCorte({
725
+ modelo: this.currentEspectro.underlyingModel,
726
+ ms: Date.now() - empezoEn,
727
+ trozos,
728
+ bytesTexto: Buffer.byteLength(content, "utf8"),
729
+ bytesRazonamiento: Buffer.byteLength(reasoning, "utf8"),
730
+ aviso: "motor",
731
+ });
732
+ // Lo que llego es trabajo util aunque este a medias.
733
+ if (content.trim()) {
734
+ this.messages.push({
735
+ role: "assistant",
736
+ content: `${content}\n\n[Aqui se corto: el motor cerro la respuesta sin terminarla.]`,
737
+ });
738
+ }
739
+ }
717
740
  const claroStream = explicarErrorDelMotor(streamErr);
718
741
  if (claroStream) {
719
742
  salida.linea(chalk.yellow(`
@@ -807,11 +830,23 @@ export class AgentLoop {
807
830
  : reasoning.trim()
808
831
  ? "Se corto antes de escribir nada: solo llego el razonamiento."
809
832
  : "Se corto antes de que llegara nada.";
833
+ // Se apunta antes de hablar: si el usuario vuelve a teclear, el aviso se
834
+ // pierde con el scroll, pero el renglon se queda.
835
+ const registro = apuntarCorte({
836
+ modelo: this.currentEspectro.underlyingModel,
837
+ ms: Date.now() - empezoEn,
838
+ trozos,
839
+ bytesTexto: Buffer.byteLength(content, "utf8"),
840
+ bytesRazonamiento: Buffer.byteLength(reasoning, "utf8"),
841
+ aviso: "mudo",
842
+ });
810
843
  const aviso = "La respuesta se corto: la conexion con el motor se cerro sin decir por que (no llego " +
811
844
  `ni el motivo de fin ni el recuento del gasto). ${hastaDonde} ` +
812
845
  "No lo reintento solo porque cada intento ocupa uno de los cuatro huecos de tu licencia " +
813
846
  "durante varios minutos; vuelve a pedirmelo y sigo.";
814
847
  salida.linea(chalk.yellow(`\n ⚠ ${aviso}\n`));
848
+ if (registro)
849
+ salida.linea(chalk.gray(` (apuntado en ${registro} — /cortes para verlos)\n`));
815
850
  return parada("error-motor", aviso);
816
851
  }
817
852
  // El modelo se quedo sin presupuesto de salida y no llego a pedir nada.
@@ -125,6 +125,13 @@ export function explicarErrorDelMotor(err) {
125
125
  cuando +
126
126
  " No es un problema de tu conexion: si interrumpiste una tarea, su hueco se libera solo.");
127
127
  }
128
+ // El proxy avisa cuando el motor cierra la respuesta sin terminarla. Es el
129
+ // unico caso en el que se puede decir de QUIEN fue el corte: el cliente, desde
130
+ // fuera, solo ve un cuerpo que se acaba, igual que uno que termino bien.
131
+ if (String(err?.error?.code ?? "") === "motor_corto_la_respuesta") {
132
+ return ((mensaje || "El motor cerro la respuesta antes de terminarla.") +
133
+ " Lo que llego se queda arriba; vuelve a pedirlo y normalmente sale a la primera.");
134
+ }
128
135
  if (tipo === "cuota") {
129
136
  return mensaje || "Has agotado la cuota de uso incluida en tu suscripción.";
130
137
  }
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ import { SessionManager } from "./sessions/manager.js";
33
33
  import { prepararRestauracion, formatearOpcionesDeSesion } from "./sessions/resume.js";
34
34
  import { auditGitDiff, formatAuditReport } from "./tools/gitAudit.js";
35
35
  import { instalarKit, listarKits, quitarKit, crearKit } from "./kits/manager.js";
36
+ import { leerCortes, rutaDelRegistro } from "./agent/cortes.js";
36
37
  import { globalUndoManager } from "./agent/undoManager.js";
37
38
  import { setAutoApprove } from "./ui/permissionPrompt.js";
38
39
  import { onModeChange, getMode, modeAnnouncement } from "./ui/modes.js";
@@ -616,6 +617,23 @@ ${sessionData}
616
617
  console.log("");
617
618
  continue;
618
619
  }
620
+ if (trimmed === "/cortes") {
621
+ const lineas = leerCortes(20);
622
+ console.log(chalk.bold.hex("#D97757")("\n✂ RESPUESTAS QUE SE CORTARON"));
623
+ if (lineas.length === 0) {
624
+ console.log(chalk.gray(" Ninguna. Bien.\n"));
625
+ }
626
+ else {
627
+ console.log(chalk.gray(` ${rutaDelRegistro()}\n`));
628
+ for (const l of lineas)
629
+ console.log(" " + chalk.gray(l));
630
+ console.log(chalk.gray("\n aviso=motor -> lo dijo el proxy: el corte viene del motor de arriba." +
631
+ "\n aviso=mudo -> nadie dijo nada: se cayo la conexion o el proxy por el camino." +
632
+ "\n Si ms y trozos salen casi iguales en todos, algo corta por tiempo o tamano;" +
633
+ "\n si salen dispares, es el motor y va a rachas.\n"));
634
+ }
635
+ continue;
636
+ }
619
637
  if (trimmed === "/kit" || trimmed.startsWith("/kit ")) {
620
638
  const partes = trimmed.split(/s+/).slice(1);
621
639
  const accion = (partes[0] || "").toLowerCase();
@@ -32,6 +32,7 @@ export const SLASH_COMMANDS = [
32
32
  { name: "/skills", description: "Ver skills disponibles y cómo usarlos" },
33
33
  { name: "/kit", description: "Montar tu Chocolatito: instalar, crear o quitar kits (skills + reglas + MCP)", args: "[instalar|crear|quitar] [nombre]" },
34
34
  { name: "/aprende", description: "Guardar lo que acabamos de hacer como skill reutilizable", args: "[en pocas palabras]" },
35
+ { name: "/cortes", description: "Ver las respuestas que se cortaron y de dónde vino el corte" },
35
36
  { name: "/memory", description: "Consultar la memoria persistente transversal (~/.chocolatito/memory/)" },
36
37
  { name: "/effort", description: "Regular potencia del motor (1.0, 1.5, 2.0, 2.5)", args: "[nivel]" },
37
38
  { name: "/goal", description: "Ejecutar meta compleja autónoma con auto-corrección", args: "<meta>" },
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 3,
3
3
  "minimum_chrome_version": "102",
4
4
  "name": "Chocolatito Code",
5
- "version": "1.6.12",
5
+ "version": "1.6.13",
6
6
  "description": "Deja que Chocolatito Code trabaje dentro de tu Chrome, en segundo plano y con tus sesiones ya iniciadas.",
7
7
  "permissions": [
8
8
  "tabs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chocolatito-code",
3
- "version": "1.6.12",
3
+ "version": "1.6.13",
4
4
  "description": "Agente autónomo de programación para la terminal, con control real del ordenador y del navegador. Desarrollado por Chocolatito.",
5
5
  "type": "module",
6
6
  "bin": {