chocolatito-code 1.6.12 → 1.6.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/COMPUTER-USE.md +37 -0
  2. package/README.md +9 -2
  3. package/dist/agent/cortes.d.ts +41 -0
  4. package/dist/agent/cortes.js +52 -0
  5. package/dist/agent/loop.js +35 -0
  6. package/dist/config/engine.js +7 -0
  7. package/dist/config/permissions.js +4 -0
  8. package/dist/index.js +147 -24
  9. package/dist/prompts/systemPrompt.js +15 -11
  10. package/dist/servidor/archivos.d.ts +69 -0
  11. package/dist/servidor/archivos.js +156 -0
  12. package/dist/servidor/fondo.d.ts +85 -0
  13. package/dist/servidor/fondo.js +134 -0
  14. package/dist/servidor/pagina.js +266 -2
  15. package/dist/servidor/puente.d.ts +13 -0
  16. package/dist/servidor/puente.js +11 -0
  17. package/dist/servidor/servidor.d.ts +6 -0
  18. package/dist/servidor/servidor.js +129 -6
  19. package/dist/tools/computerUse.d.ts +11 -1
  20. package/dist/tools/computerUse.js +376 -34
  21. package/dist/tools/toolDefsComputer.js +39 -14
  22. package/dist/tools/visionBridge.d.ts +4 -2
  23. package/dist/tools/visionBridge.js +40 -15
  24. package/dist/tools/win/hostScript.js +300 -29
  25. package/dist/ui/comandos.js +1 -0
  26. package/dist/ui/ink/App.js +23 -3
  27. package/dist/ui/ink/Prompt.d.ts +5 -1
  28. package/dist/ui/ink/Prompt.js +92 -4
  29. package/dist/ui/ink/control.d.ts +2 -0
  30. package/dist/ui/ink/control.js +15 -0
  31. package/dist/ui/ink/montarApp.d.ts +0 -19
  32. package/dist/ui/ink/montarApp.js +3 -0
  33. package/dist/ui/permissionPrompt.js +44 -9
  34. package/dist/ui/pieFijo.js +5 -0
  35. package/dist/ui/prompt.js +6 -0
  36. package/dist/ui/renderer.d.ts +1 -0
  37. package/dist/ui/renderer.js +27 -2
  38. package/dist/ui/selector.js +5 -0
  39. package/dist/ui/spinner.d.ts +1 -0
  40. package/dist/ui/spinner.js +6 -0
  41. package/extension/manifest.json +1 -1
  42. package/package.json +1 -1
package/COMPUTER-USE.md CHANGED
@@ -482,3 +482,40 @@ Es ambiguo y no voy a elegir por ti: usa list_windows y pasa un titulo mas concr
482
482
 
483
483
  Elegir a ciegas entre candidatos empatados no es una comodidad: es cómo se acaba
484
484
  mandando una pulsación destructiva a la ventana equivocada.
485
+
486
+ ---
487
+
488
+ ## 9. Nivel Google Project Astra & OpenAI Operator: Percepción Multimodal y Flujos Autónomos
489
+
490
+ Para acercar Chocolatito Code al nivel de **Google Project Astra** y **OpenAI Operator**, se incorporaron capacidades avanzadas de percepción multimodal y ejecución compuesta:
491
+
492
+ ### 9.1 Cuadrícula de Coordenadas Visuales (`grid: true`)
493
+ Cuando una interfaz carece de árbol de accesibilidad (lienzos HTML5/WebGL, motores de videojuegos, apps Qt/Flutter o ventanas remotas), los modelos multimodales a menudo sufren desviaciones de 30 a 80 píxeles.
494
+ Al pasar `grid: true` (o en `find_element` visual), el host dibuja una cuadrícula milimétrica semitransparente con marcadores de coordenadas directas `(x,y)` cada 200px. El modelo de visión lee el marcador visual exacto, logrando una precisión de localización superior al 98%.
495
+
496
+ ### 9.2 Encadenamiento de Acciones Compuestas (`action: "sequence"`)
497
+ Reducir los viajes de ida y vuelta al modelo es esencial para la agilidad. En vez de pagar 4 turnos (20–30s) para hacer clic en un buscador, escribir una consulta, presionar Enter y esperar el cambio, se pasa una sola acción:
498
+ ```json
499
+ {
500
+ "action": "sequence",
501
+ "steps": [
502
+ { "action": "ui_click", "ref": 4 },
503
+ { "action": "type", "text": "chocolatito code" },
504
+ { "action": "key", "keys": "Return" },
505
+ { "action": "wait_change" }
506
+ ]
507
+ }
508
+ ```
509
+ Se ejecuta en subsegundos en la máquina local y devuelve el resultado consolidado junto con el estado posterior de la ventana.
510
+
511
+ ### 9.3 Detección de Cambios Visuales (`wait_visual_change`)
512
+ Las aplicaciones que dibujan mediante DirectX, OpenGL, streaming o canvas web no disparan eventos de Windows UI Automation. `wait_visual_change` realiza un muestreo de píxeles de alta velocidad (128x128) y detecta transiciones en la pantalla, ventana o región específica sin consumir CPU.
513
+
514
+ ### 9.4 Inspección de Alta Resolución por Región (`region: [x1, y1, x2, y2]`)
515
+ Permite recortar e inspeccionar un área crítica (iconos de bandeja, barras de estado, mensajes de error diminutos) en resolución nativa 1:1 sin sobrepasar el límite de tamaño de imagen para visión.
516
+
517
+ ### 9.5 Auto-Recuperación Resiliente en `ui_click` y `ui_type`
518
+ Si un control de interfaz personalizada (WinUI 3, WPF o Electron) no implementa `InvokePattern` o `TogglePattern`, `ui_click` no falla: obtiene automáticamente el centroide de su rectángulo delimitador (`ui_rect`) y envía un clic de segundo plano (`bg_click`). Igualmente, `ui_type` apoya con enfoque automático por clic antes de teclear.
519
+
520
+ ### 9.6 Coordenadas Normalizadas (0..1000 y 0.0..1.0)
521
+ Soporte nativo para coordenadas normalizadas en escala 0..1000 estilo OpenAI Operator y Anthropic, adaptándose automáticamente a monitores 1080p, 2K, 4K y configuraciones multimonitor.
package/README.md CHANGED
@@ -94,7 +94,10 @@ teclado.
94
94
 
95
95
  ```bash
96
96
  chocolatito --servir # solo desde esta máquina
97
- chocolatito --servir-red # también desde el móvil o la tablet de tu red
97
+ chocolatito --servir-red # también desde el móvil o la tablet de tu red
98
+ chocolatito --servir-red --fondo # y suelta el terminal: puedes cerrar la ventana
99
+ chocolatito --estado # ¿sigue viva?
100
+ chocolatito --parar # cerrarla
98
101
  ```
99
102
 
100
103
  Imprime un enlace con un token dentro. Ese enlace **es** la llave: quien lo tenga entero
@@ -109,7 +112,11 @@ JavaScript de Node. Por el navegador no hace falta instalar nada.
109
112
  | --- | --- |
110
113
  | `--puerto N` | Otro puerto (por defecto 4700). |
111
114
  | Se reconecta solo | Un móvil pierde la red a cada rato; la sesión no se pierde con ella. |
112
- | Los permisos | Salen en el móvil, con los mismos botones que en el terminal. |
115
+ | Los permisos | Salen en el móvil, con los mismos botones que en el terminal. |
116
+ | Archivos | Arrastra, pega del portapapeles o pulsa `+`. Caen en `entradas/` y el agente los recibe con su ruta. |
117
+ | Imágenes | Lo que el agente exporta se **ve** en la página, no se anuncia con una ruta. |
118
+ | Te avisa | Cuando pide permiso o cuando termina: notificación, vibración y un punto en la pestaña. Solo si no estás mirando la página. |
119
+ | Y te lo llevas | **⇩ Guardar** bajo cada resultado: la pieza, el ZIP o el PDF van al teléfono sin pasar por el ordenador. |
113
120
 
114
121
  **Manda el navegador.** Con `--servir`, lo que escribas en el terminal ya no se lee: el
115
122
  terminal queda de monitor. Es a propósito — dos entradas peleándose por el mismo teclado
@@ -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
  }
@@ -24,6 +24,10 @@ export const OBSERVE_ONLY = {
24
24
  "cursor_position",
25
25
  "wait",
26
26
  "sleep",
27
+ "wait_change",
28
+ "wait_visual_change",
29
+ "read_text",
30
+ "get_text",
27
31
  ]),
28
32
  chrome: new Set([
29
33
  "status",
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ import { reafirmarModoCrudo } from "./ui/keyboardGuard.js";
24
24
  import { askInteractivePrompt, SLASH_COMMANDS } from "./ui/prompt.js";
25
25
  import { changeDir } from "./tools/changeDir.js";
26
26
  import { gitDiff, gitCommit } from "./tools/gitTools.js";
27
- import { renderUserPrompt } from "./ui/renderer.js";
27
+ import { renderUserPrompt, formatColorDiff } from "./ui/renderer.js";
28
28
  import { parseFileMentions, buildPromptWithMentions, notaDeMencionesImplicitas } from "./agent/mentions.js";
29
29
  import { SkillManager } from "./skills/manager.js";
30
30
  import { MemoryManager } from "./memory/manager.js";
@@ -33,6 +33,8 @@ 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 { guardarSesion, lanzarEnFondo, olvidarSesion, pararSesion, sesionViva } from "./servidor/fondo.js";
37
+ import { leerCortes, rutaDelRegistro } from "./agent/cortes.js";
36
38
  import { globalUndoManager } from "./agent/undoManager.js";
37
39
  import { setAutoApprove } from "./ui/permissionPrompt.js";
38
40
  import { onModeChange, getMode, modeAnnouncement } from "./ui/modes.js";
@@ -219,8 +221,8 @@ async function main() {
219
221
  // errores raros dentro de una dependencia. Ver config/nodeVersion.ts.
220
222
  const nodeViejo = comprobarNode();
221
223
  if (nodeViejo) {
222
- console.error(chalk.red(`
223
- ${nodeViejo}
224
+ console.error(chalk.red(`
225
+ ${nodeViejo}
224
226
  `));
225
227
  process.exit(EXIT_USO);
226
228
  }
@@ -234,13 +236,50 @@ ${nodeViejo}
234
236
  // red local, que es lo que hace falta para entrar desde el movil.
235
237
  const enRed = rawArgs.includes("--servir-red");
236
238
  const quiereServir = enRed || rawArgs.includes("--servir");
239
+ // --fondo suelta la sesion del terminal; --parar y --estado la gobiernan
240
+ // desde fuera, sin tener que buscar la ventana que la lanzo.
241
+ const quiereFondo = rawArgs.includes("--fondo");
242
+ const quiereParar = rawArgs.includes("--parar");
243
+ const quiereEstado = rawArgs.includes("--estado");
237
244
  const posPuerto = rawArgs.indexOf("--puerto");
238
245
  const puertoWeb = posPuerto >= 0 ? Number(rawArgs[posPuerto + 1]) || 0 : 0;
239
246
  // Ojo: lo que sobra de argv se convierte en la orden a ejecutar. Una bandera
240
247
  // que no se filtre aqui no es una bandera ignorada, es una orden al modelo.
241
- const banderas = new Set(["--yes", "-y", "--servir", "--servir-red", "--puerto"]);
248
+ const banderas = new Set([
249
+ "--yes",
250
+ "-y",
251
+ "--servir",
252
+ "--servir-red",
253
+ "--puerto",
254
+ "--fondo",
255
+ "--parar",
256
+ "--estado",
257
+ ]);
242
258
  const args = rawArgs.filter((a, i) => !banderas.has(a) && !(posPuerto >= 0 && i === posPuerto + 1 && puertoWeb > 0));
243
259
  const singlePrompt = args.join(" ").trim();
260
+ // ESTAS DOS NO ARRANCAN NADA, y por eso van aqui arriba: montar el agente
261
+ // entero -API key, skills, MCP- para luego solo decir un PID seria hacer
262
+ // esperar al usuario por nada, y ademas pediria la clave a quien solo quiere
263
+ // apagar la sesion de ayer.
264
+ if (quiereParar) {
265
+ const res = await pararSesion();
266
+ console.log(res.ok ? chalk.green(`\n ✔ ${res.mensaje}\n`) : chalk.yellow(`\n ${res.mensaje}\n`));
267
+ process.exit(res.ok ? 0 : 1);
268
+ }
269
+ if (quiereEstado) {
270
+ const viva = sesionViva();
271
+ if (!viva) {
272
+ console.log(chalk.gray("\n No hay ninguna sesion de fondo abierta.\n"));
273
+ process.exit(1);
274
+ }
275
+ console.log(chalk.bold.hex("#e8833a")("\n 🦊 Sesion de fondo abierta"));
276
+ console.log(` ${chalk.bold("Enlace:")} ${chalk.cyan(viva.url)}`);
277
+ console.log(` ${chalk.bold("Carpeta:")} ${chalk.white(viva.carpeta)}`);
278
+ console.log(` ${chalk.bold("Desde:")} ${chalk.white(new Date(viva.desde).toLocaleString())}`);
279
+ console.log(chalk.gray(` pid ${viva.pid} · ${viva.enRed ? "visible en tu red" : "solo esta maquina"}`));
280
+ console.log(chalk.gray("\n Para cerrarla: chocolatito --parar\n"));
281
+ process.exit(0);
282
+ }
244
283
  // Los errores de uso se resuelven ANTES de la licencia y de la API key: no
245
284
  // dependen de estar suscrito, y en CI hay que poder distinguir "lo has
246
285
  // invocado mal" de "el agente no pudo".
@@ -407,11 +446,78 @@ ${nodeViejo}
407
446
  // reemplazo los cuatro canales de console para mandarlos a la transcripcion, y
408
447
  // la captura tiene que envolver ESA version. Al reves, pantalla envolveria a la
409
448
  // captura y al navegador no llegaria nada. Ver servidor/captura.ts.
449
+ // SOLTARSE DEL TERMINAL
450
+ //
451
+ // Se relanza el mismo programa con los mismos argumentos, sin --fondo, y este
452
+ // proceso se va. El hijo no hereda ni la entrada ni la salida: la ventana se
453
+ // puede cerrar y la sesion sigue.
454
+ //
455
+ // El enlace lo imprime el PADRE, pero el token lo genera el HIJO. Asi que en
456
+ // vez de inventar un canal para pasarlo, se espera a que el hijo escriba su
457
+ // archivo de sesion. Eso ademas confirma que arranco de verdad: si el hijo
458
+ // revienta -puerto ocupado, licencia caducada-, el archivo no aparece y se
459
+ // dice que fallo, en lugar de imprimir un enlace a nada.
460
+ if (quiereServir && quiereFondo && !process.env["CHOCOLATITO_EN_FONDO"]) {
461
+ const yaAbierta = sesionViva();
462
+ if (yaAbierta) {
463
+ console.log(chalk.yellow("\n Ya hay una sesion de fondo abierta."));
464
+ console.log(` ${chalk.cyan(yaAbierta.url)}\n`);
465
+ console.log(chalk.gray(" Para cerrarla: chocolatito --parar\n"));
466
+ process.exit(1);
467
+ }
468
+ olvidarSesion();
469
+ const pid = lanzarEnFondo(rawArgs.filter((a) => a !== "--fondo"));
470
+ let viva = null;
471
+ for (let intento = 0; intento < 100 && !viva; intento++) {
472
+ await new Promise((r) => setTimeout(r, 100));
473
+ viva = sesionViva();
474
+ }
475
+ if (!viva) {
476
+ console.log(chalk.red("\n La sesion de fondo no llego a arrancar."));
477
+ console.log(chalk.gray(` Prueba sin --fondo para ver el error (pid ${pid}).\n`));
478
+ process.exit(1);
479
+ }
480
+ console.log(chalk.hex("#e8833a")("\n 🦊 Sesion abierta en segundo plano\n"));
481
+ console.log(` ${chalk.bold("Enlace:")} ${chalk.cyan(viva.url)}`);
482
+ console.log(chalk.gray("\n Ya puedes cerrar esta ventana: la sesion sigue."));
483
+ console.log(chalk.gray(" chocolatito --estado · chocolatito --parar\n"));
484
+ process.exit(0);
485
+ }
410
486
  if (quiereServir) {
411
487
  try {
412
488
  iniciarCaptura();
413
- const web = await iniciarServidorWeb({ puerto: puertoWeb || undefined, enRed });
489
+ const web = await iniciarServidorWeb({
490
+ puerto: puertoWeb || undefined,
491
+ enRed,
492
+ // La carpeta se pregunta en cada peticion, no se copia aqui: con /cd el
493
+ // agente se cambia a mitad de sesion y lo que suba el movil tiene que
494
+ // caer donde se esta trabajando AHORA.
495
+ carpeta: () => agent.currentCwd,
496
+ });
414
497
  marcarServirActivo(true);
498
+ // Solo el que corre suelto deja su ficha: una sesion normal muere con su
499
+ // terminal y anotarla solo serviria para que --estado mintiera despues.
500
+ if (process.env["CHOCOLATITO_EN_FONDO"]) {
501
+ guardarSesion({
502
+ pid: process.pid,
503
+ puerto: web.puerto,
504
+ token: web.token,
505
+ url: web.urlsDeRed[0] || web.url,
506
+ carpeta: agent.currentCwd,
507
+ desde: new Date().toISOString(),
508
+ enRed,
509
+ });
510
+ const limpiar = () => olvidarSesion();
511
+ process.once("exit", limpiar);
512
+ process.once("SIGINT", () => {
513
+ limpiar();
514
+ process.exit(0);
515
+ });
516
+ process.once("SIGTERM", () => {
517
+ limpiar();
518
+ process.exit(0);
519
+ });
520
+ }
415
521
  console.log(chalk.hex("#e8833a")("\n 🦊 Sesión abierta en el navegador\n"));
416
522
  console.log(` ${chalk.bold("Aquí:")} ${chalk.cyan(web.url)}`);
417
523
  for (const dir of web.urlsDeRed) {
@@ -521,15 +627,15 @@ ${nodeViejo}
521
627
  .filter((m) => m.role === "user" || m.role === "assistant")
522
628
  .map((m) => `### 👤 ${m.role === "user" ? "Usuario" : "Chocolatito"}\n\n${typeof m.content === "string" ? m.content : JSON.stringify(m.content, null, 2)}\n`)
523
629
  .join("\n---\n\n");
524
- const mdContent = `# 🦊 Bitácora de Sesión — Chocolatito Code
525
- **Fecha:** ${new Date().toLocaleString()}
526
- **Directorio:** \`${agent.currentCwd}\`
527
- **Motor:** ${currentModel.name}
528
- **Inversión Total:** ${agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel).formattedCost}
529
-
530
- ---
531
-
532
- ${sessionData}
630
+ const mdContent = `# 🦊 Bitácora de Sesión — Chocolatito Code
631
+ **Fecha:** ${new Date().toLocaleString()}
632
+ **Directorio:** \`${agent.currentCwd}\`
633
+ **Motor:** ${currentModel.name}
634
+ **Inversión Total:** ${agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel).formattedCost}
635
+
636
+ ---
637
+
638
+ ${sessionData}
533
639
  `;
534
640
  fs.writeFileSync(exportPath, mdContent, "utf-8");
535
641
  console.log(chalk.green(`\n✔ Bitácora exportada exitosamente en: ${chalk.bold.white(exportPath)}\n`));
@@ -616,6 +722,23 @@ ${sessionData}
616
722
  console.log("");
617
723
  continue;
618
724
  }
725
+ if (trimmed === "/cortes") {
726
+ const lineas = leerCortes(20);
727
+ console.log(chalk.bold.hex("#D97757")("\n✂ RESPUESTAS QUE SE CORTARON"));
728
+ if (lineas.length === 0) {
729
+ console.log(chalk.gray(" Ninguna. Bien.\n"));
730
+ }
731
+ else {
732
+ console.log(chalk.gray(` ${rutaDelRegistro()}\n`));
733
+ for (const l of lineas)
734
+ console.log(" " + chalk.gray(l));
735
+ console.log(chalk.gray("\n aviso=motor -> lo dijo el proxy: el corte viene del motor de arriba." +
736
+ "\n aviso=mudo -> nadie dijo nada: se cayo la conexion o el proxy por el camino." +
737
+ "\n Si ms y trozos salen casi iguales en todos, algo corta por tiempo o tamano;" +
738
+ "\n si salen dispares, es el motor y va a rachas.\n"));
739
+ }
740
+ continue;
741
+ }
619
742
  if (trimmed === "/kit" || trimmed.startsWith("/kit ")) {
620
743
  const partes = trimmed.split(/s+/).slice(1);
621
744
  const accion = (partes[0] || "").toLowerCase();
@@ -728,12 +851,12 @@ ${sessionData}
728
851
  }
729
852
  console.log(chalk.bold.hex("#A855F7")("\n🎯 INICIANDO MODO META AUTÓNOMA (GOAL MODE):"));
730
853
  console.log(chalk.gray(` Objetivo: "${goalText}"\n`));
731
- const autonomousPrompt = `[MODO META AUTÓNOMA ACTIVADO]
732
- Tu objetivo principal es: "${goalText}"
733
- Directivas de ejecución:
734
- 1. Formula un plan de ejecución mental paso a paso.
735
- 2. Ejecuta cada paso utilizando las herramientas correspondientes (creación, edición, ejecución, verificación).
736
- 3. Si algo falla o hay errores, autocorregir inmediatamente y volver a probar.
854
+ const autonomousPrompt = `[MODO META AUTÓNOMA ACTIVADO]
855
+ Tu objetivo principal es: "${goalText}"
856
+ Directivas de ejecución:
857
+ 1. Formula un plan de ejecución mental paso a paso.
858
+ 2. Ejecuta cada paso utilizando las herramientas correspondientes (creación, edición, ejecución, verificación).
859
+ 3. Si algo falla o hay errores, autocorregir inmediatamente y volver a probar.
737
860
  4. No te detengas hasta verificar que el objetivo esté 100% completado con éxito.`;
738
861
  renderUserPrompt(`/goal ${goalText}`);
739
862
  await agent.runTask(autonomousPrompt);
@@ -756,7 +879,7 @@ Directivas de ejecución:
756
879
  if (trimmed === "/diff") {
757
880
  const diffOut = await gitDiff(false, agent.currentCwd);
758
881
  console.log(chalk.bold.hex("#D97757")("\n📝 DIFERENCIAS DE GIT (DIFF):"));
759
- console.log(diffOut ? chalk.gray(diffOut) : chalk.green("✔ Sin cambios pendientes.") + "\n");
882
+ console.log(diffOut ? `${formatColorDiff(diffOut)}\n` : chalk.green("✔ Sin cambios pendientes.") + "\n");
760
883
  continue;
761
884
  }
762
885
  if (trimmed === "/audit" || trimmed === "/review" || trimmed.startsWith("/audit ") || trimmed.startsWith("/review ")) {
@@ -843,12 +966,12 @@ Directivas de ejecución:
843
966
  const refreshedContext = await getProjectContext(agent.currentCwd);
844
967
  const updatedPrompt = buildSystemPrompt(refreshedContext, currentModel, skillManager, memoryManager, isPlanMode);
845
968
  agent.setEspectroModel(currentModel, updatedPrompt);
846
- console.log(chalk.green(`
969
+ console.log(chalk.green(`
847
970
  ✔ Nivel actualizado a `) +
848
971
  chalk.bold.hex("#A855F7")(currentModel.name) +
849
972
  chalk.gray(` (${currentModel.effortLevel})`) +
850
- `
851
- ${chalk.italic.gray(currentModel.description)}
973
+ `
974
+ ${chalk.italic.gray(currentModel.description)}
852
975
  `);
853
976
  // EL TECLADO SE QUEDABA TRABADO AL CAMBIAR DE MOTOR
854
977
  //
@@ -172,17 +172,21 @@ Puedes delegar tareas aisladas con 'spawn_agent':
172
172
  NUNCA pidas al usuario su contraseña ni la escribas. Si hace falta una cuenta, es
173
173
  él quien inicia sesión a mano.
174
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.
175
+ APLICACIONES DE ESCRITORIO: usa 'computer_use' (nivel Astra / OpenAI Operator):
176
+ * Flujo preferente: 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
+ * FLUJOS RÁPIDOS (ACTION CHAINING): Para secuencias interactivas comunes
182
+ (ej. clic en campo -> escribir texto -> pulsar Enter -> esperar resultado),
183
+ usa action: 'sequence' con 'steps'. Ejecuta todo en milisegundos en un solo turno sin latencia.
184
+ * CUANDO NO HAY ÁRBOL DE ACCESIBILIDAD (lienzos, juegos, vídeo, Flutter, interfaces gráficas complejas):
185
+ - Usa 'screenshot' con 'grid: true' para activar la cuadrícula de coordenadas milimétricas y situar controles con exactitud.
186
+ - Usa 'screenshot' con 'region: [x1,y1,x2,y2]' para inspeccionar zonas pequeñas o detalles finos en resolución nativa 1:1.
187
+ - Usa 'wait_visual_change' para esperar animaciones, cargas o cambios visuales cuando la app no emite eventos de accesibilidad.
188
+ - Usa 'find_element' para localizar un control por texto en el árbol o visualmente mediante visión multimodal.
189
+ * COORDENADAS: Soporta píxeles físicos o coordenadas normalizadas (0..1000 con normalized: true). Si 'ui_click' no tiene patrón nativo, se auto-recupera clicando el centro del elemento.
186
190
 
187
191
  PROHIBIDO: 'taskkill' o matar procesos de Chrome del usuario.
188
192
  8. LO QUE PRODUCES ES DEL USUARIO, NO TUYO:
@@ -0,0 +1,69 @@
1
+ /**
2
+ * ARCHIVOS QUE ENTRAN Y ARCHIVOS QUE SALEN
3
+ *
4
+ * DE DONDE SALE
5
+ *
6
+ * La pagina de `--servir` solo sabia texto. Ni un `<input type=file>`, ni una
7
+ * sola `<img>`. Y eso deja el ciclo de trabajo cojo por los dos extremos:
8
+ *
9
+ * - Lo que tienes en el movil -la foto, el video que te mando el cliente, la
10
+ * captura del error- no hay forma de metarlo. Habia que pasarlo antes al PC.
11
+ * - Lo que el agente produce se contesta con la RUTA. En un movil, "lo tienes
12
+ * en C:\Users\...\salida\pieza-01.png" no sirve para nada: no puedes mirarlo,
13
+ * asi que no puedes opinar, asi que no puedes trabajar.
14
+ *
15
+ * LAS DOS REGLAS
16
+ *
17
+ * 1. LO QUE ENTRA CAE EN UN SOLO SITIO: `entradas/` de la carpeta de trabajo.
18
+ * No se deja elegir destino. Un nombre de archivo que llega por la red no
19
+ * es un nombre: es lo que alguien escribio al otro lado, y "..\..\.ssh\id_rsa"
20
+ * tambien es un nombre. Se le quita todo lo que parezca una ruta y se queda
21
+ * el nombre pelado.
22
+ *
23
+ * 2. LO QUE SALE NO PUEDE SALIRSE DE LA CARPETA DE TRABAJO. Se resuelve la
24
+ * ruta de verdad -con enlaces simbolicos ya seguidos- y se comprueba que
25
+ * sigue estando dentro. Sin eso, `?ruta=../../.chocolatitorc` serviria la
26
+ * clave de API por HTTP a quien tenga el enlace.
27
+ *
28
+ * Las dos comprobaciones viven aqui y no en el servidor a proposito: son la
29
+ * clase de cosa que se copia mal cuando se reparte entre tres sitios.
30
+ */
31
+ /** Lo mas grande que se acepta de una vez. Un video de movil cabe de sobra. */
32
+ export declare const LIMITE_DE_SUBIDA: number;
33
+ /** Donde caen los archivos que llegan del navegador. */
34
+ export declare const CARPETA_DE_ENTRADAS = "entradas";
35
+ export declare function tipoDelArchivo(ruta: string): string;
36
+ export declare function esImagen(ruta: string): boolean;
37
+ /**
38
+ * Deja un nombre de archivo en algo que no pueda salirse de su carpeta.
39
+ *
40
+ * No vale con quitar "..": en Windows tambien separa la barra invertida, y
41
+ * "C:algo" es una ruta relativa al disco C. Se parte por los dos separadores y
42
+ * se toma el ultimo trozo, que es lo unico que de verdad es un nombre.
43
+ */
44
+ export declare function nombreSeguro(propuesto: string): string;
45
+ /** Si ya existe, se numera en vez de pisar lo que hubiera. */
46
+ export declare function rutaLibre(carpeta: string, nombre: string): string;
47
+ export interface Guardado {
48
+ ok: boolean;
49
+ ruta?: string;
50
+ error?: string;
51
+ }
52
+ export declare function guardarSubida(cwd: string, nombrePropuesto: string, datos: Buffer): Guardado;
53
+ /**
54
+ * Comprueba que una ruta pedida por el navegador se puede servir.
55
+ *
56
+ * Devuelve la ruta real o null. Null es un 404 siempre, tambien cuando el
57
+ * archivo existe pero esta fuera: decir "existe pero no te lo doy" ya es contar
58
+ * algo del disco de alguien a quien no le hemos preguntado.
59
+ */
60
+ export declare function rutaServible(cwd: string, pedida: string): string | null;
61
+ /**
62
+ * La linea que se le mete a la conversacion cuando llegan archivos.
63
+ *
64
+ * Lleva las rutas COMPLETAS y no los nombres: el modelo tiene que poder
65
+ * abrirlos sin adivinar donde cayeron, y "la foto que acabo de subir" no es una
66
+ * ruta. Y se dice que vienen del navegador, porque cambia lo que hay que hacer
67
+ * con ellos: no son archivos del proyecto, son material que acaba de llegar.
68
+ */
69
+ export declare function avisoDeSubida(rutas: string[]): string;