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.
- package/COMPUTER-USE.md +37 -0
- package/README.md +9 -2
- package/dist/agent/cortes.d.ts +41 -0
- package/dist/agent/cortes.js +52 -0
- package/dist/agent/loop.js +35 -0
- package/dist/config/engine.js +7 -0
- package/dist/config/permissions.js +4 -0
- package/dist/index.js +147 -24
- package/dist/prompts/systemPrompt.js +15 -11
- package/dist/servidor/archivos.d.ts +69 -0
- package/dist/servidor/archivos.js +156 -0
- package/dist/servidor/fondo.d.ts +85 -0
- package/dist/servidor/fondo.js +134 -0
- package/dist/servidor/pagina.js +266 -2
- package/dist/servidor/puente.d.ts +13 -0
- package/dist/servidor/puente.js +11 -0
- package/dist/servidor/servidor.d.ts +6 -0
- package/dist/servidor/servidor.js +129 -6
- package/dist/tools/computerUse.d.ts +11 -1
- package/dist/tools/computerUse.js +376 -34
- package/dist/tools/toolDefsComputer.js +39 -14
- package/dist/tools/visionBridge.d.ts +4 -2
- package/dist/tools/visionBridge.js +40 -15
- package/dist/tools/win/hostScript.js +300 -29
- package/dist/ui/comandos.js +1 -0
- package/dist/ui/ink/App.js +23 -3
- package/dist/ui/ink/Prompt.d.ts +5 -1
- package/dist/ui/ink/Prompt.js +92 -4
- package/dist/ui/ink/control.d.ts +2 -0
- package/dist/ui/ink/control.js +15 -0
- package/dist/ui/ink/montarApp.d.ts +0 -19
- package/dist/ui/ink/montarApp.js +3 -0
- package/dist/ui/permissionPrompt.js +44 -9
- package/dist/ui/pieFijo.js +5 -0
- package/dist/ui/prompt.js +6 -0
- package/dist/ui/renderer.d.ts +1 -0
- package/dist/ui/renderer.js +27 -2
- package/dist/ui/selector.js +5 -0
- package/dist/ui/spinner.d.ts +1 -0
- package/dist/ui/spinner.js +6 -0
- package/extension/manifest.json +1 -1
- 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
|
+
}
|
package/dist/agent/loop.js
CHANGED
|
@@ -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.
|
package/dist/config/engine.js
CHANGED
|
@@ -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
|
@@ -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([
|
|
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({
|
|
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 ?
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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;
|