chocolatito-code 1.6.6 → 1.6.8
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/README.md +437 -341
- package/dist/agent/context.d.ts +6 -0
- package/dist/agent/context.js +69 -2
- package/dist/agent/loop.js +8 -0
- package/dist/agent/toolGate.js +13 -7
- package/dist/agent/verifier.js +2 -1
- package/dist/config/permissions.d.ts +17 -0
- package/dist/config/permissions.js +47 -7
- package/dist/config/plataforma.d.ts +62 -0
- package/dist/config/plataforma.js +109 -0
- package/dist/config/updater.js +4 -1
- package/dist/hooks/manager.js +2 -1
- package/dist/index.js +164 -39
- package/dist/memory/manager.d.ts +15 -5
- package/dist/memory/manager.js +146 -29
- package/dist/servidor/captura.d.ts +36 -0
- package/dist/servidor/captura.js +74 -0
- package/dist/servidor/pagina.d.ts +27 -0
- package/dist/servidor/pagina.js +268 -0
- package/dist/servidor/puente.d.ts +34 -0
- package/dist/servidor/puente.js +115 -0
- package/dist/servidor/servidor.d.ts +61 -0
- package/dist/servidor/servidor.js +249 -0
- package/dist/sessions/manager.d.ts +8 -0
- package/dist/sessions/manager.js +44 -0
- package/dist/sessions/resume.d.ts +12 -0
- package/dist/sessions/resume.js +10 -0
- package/dist/tools/backgroundTask.d.ts +64 -0
- package/dist/tools/backgroundTask.js +264 -0
- package/dist/tools/browserExtension.d.ts +2 -1
- package/dist/tools/browserExtension.js +48 -0
- package/dist/tools/computerUse.js +51 -9
- package/dist/tools/definitions.js +93 -1
- package/dist/tools/gitAudit.d.ts +41 -0
- package/dist/tools/gitAudit.js +282 -0
- package/dist/tools/runCommand.js +5 -2
- package/dist/tools/runner.js +36 -2
- package/dist/tools/safety.d.ts +1 -0
- package/dist/tools/safety.js +3 -0
- package/dist/tools/todoTool.d.ts +2 -0
- package/dist/tools/todoTool.js +29 -0
- package/dist/tools/toolDefsComputer.js +9 -0
- package/dist/tools/win/hostScript.js +7 -2
- package/dist/ui/comandos.js +2 -0
- package/dist/ui/ink/App.d.ts +0 -23
- package/dist/ui/ink/App.js +86 -57
- package/dist/ui/ink/Prompt.d.ts +16 -6
- package/dist/ui/ink/Prompt.js +37 -62
- package/dist/ui/ink/montarApp.d.ts +34 -0
- package/dist/ui/ink/montarApp.js +93 -1
- package/dist/ui/ink/prestamo.d.ts +63 -0
- package/dist/ui/ink/prestamo.js +92 -0
- package/dist/ui/ink/teclado.d.ts +48 -0
- package/dist/ui/ink/teclado.js +96 -0
- package/dist/ui/ink/transcripcion.d.ts +114 -0
- package/dist/ui/ink/transcripcion.js +180 -0
- package/dist/ui/interrupt.js +4 -1
- package/dist/ui/loopPrompt.d.ts +6 -7
- package/dist/ui/loopPrompt.js +14 -1
- package/dist/ui/marco.d.ts +3 -0
- package/dist/ui/marco.js +22 -3
- package/dist/ui/pantalla.d.ts +45 -17
- package/dist/ui/pantalla.js +150 -92
- package/dist/ui/permissionPrompt.d.ts +19 -17
- package/dist/ui/permissionPrompt.js +44 -1
- package/dist/ui/pieFijo.d.ts +1 -1
- package/dist/ui/pieFijo.js +27 -5
- package/dist/ui/renderer.js +8 -0
- package/dist/ui/selector.d.ts +6 -4
- package/dist/ui/selector.js +14 -1
- package/extension/background.js +80 -0
- package/extension/content.js +106 -0
- package/extension/manifest.json +3 -2
- package/package.json +3 -2
- package/dist/ui/historial.d.ts +0 -49
- package/dist/ui/historial.js +0 -92
package/dist/index.js
CHANGED
|
@@ -15,8 +15,11 @@ import { codigoDeSalida, EXIT_OK, EXIT_FALLO, EXIT_USO, EXIT_LICENCIA } from "./
|
|
|
15
15
|
import { showBanner, animateStartupBanner, showHelp, showEffortMenu } from "./ui/banner.js";
|
|
16
16
|
import { fijarMotor } from "./ui/motorActual.js";
|
|
17
17
|
import { elegirDeLista } from "./ui/selector.js";
|
|
18
|
-
import { entrarEnPantallaFija, pantallaFijaPedida } from "./ui/pantalla.js";
|
|
19
|
-
import {
|
|
18
|
+
import { entrarEnPantallaFija, pantallaFijaPedida, salirDePantallaFija } from "./ui/pantalla.js";
|
|
19
|
+
import { iniciarServidorWeb } from "./servidor/servidor.js";
|
|
20
|
+
import { iniciarCaptura } from "./servidor/captura.js";
|
|
21
|
+
import { entregarEntrada, esperarEntradaWeb, marcarServirActivo } from "./servidor/puente.js";
|
|
22
|
+
import { montarArbol, desmontarArbol } from "./ui/ink/montarApp.js";
|
|
20
23
|
import { reafirmarModoCrudo } from "./ui/keyboardGuard.js";
|
|
21
24
|
import { askInteractivePrompt, SLASH_COMMANDS } from "./ui/prompt.js";
|
|
22
25
|
import { changeDir } from "./tools/changeDir.js";
|
|
@@ -27,7 +30,8 @@ import { SkillManager } from "./skills/manager.js";
|
|
|
27
30
|
import { MemoryManager } from "./memory/manager.js";
|
|
28
31
|
import { HookManager } from "./hooks/manager.js";
|
|
29
32
|
import { SessionManager } from "./sessions/manager.js";
|
|
30
|
-
import { prepararRestauracion } from "./sessions/resume.js";
|
|
33
|
+
import { prepararRestauracion, formatearOpcionesDeSesion } from "./sessions/resume.js";
|
|
34
|
+
import { auditGitDiff, formatAuditReport } from "./tools/gitAudit.js";
|
|
31
35
|
import { globalUndoManager } from "./agent/undoManager.js";
|
|
32
36
|
import { setAutoApprove } from "./ui/permissionPrompt.js";
|
|
33
37
|
import { onModeChange, getMode, modeAnnouncement } from "./ui/modes.js";
|
|
@@ -76,6 +80,9 @@ function handleFlags(argv) {
|
|
|
76
80
|
"",
|
|
77
81
|
"OPCIONES",
|
|
78
82
|
" -y, --yes Aprueba las acciones sin preguntar (util en CI).",
|
|
83
|
+
" --servir Abre la sesion en el navegador (solo esta maquina).",
|
|
84
|
+
" --servir-red Igual, pero accesible desde tu red local (movil, tablet).",
|
|
85
|
+
" --puerto N Puerto para --servir (por defecto 4700).",
|
|
79
86
|
" -v, --version Muestra la version instalada.",
|
|
80
87
|
" -h, --help Muestra esta ayuda.",
|
|
81
88
|
"",
|
|
@@ -89,6 +96,7 @@ function handleFlags(argv) {
|
|
|
89
96
|
"CONFIGURACION",
|
|
90
97
|
" API key: variable CHOCOLATITO_API_KEY, un .env del proyecto o ~/.chocolatitorc.",
|
|
91
98
|
" Reglas del proyecto: CHOCOLATITO.md. Permisos y hooks: .chocolatito/settings.json.",
|
|
99
|
+
" Interfaz clasica (sin la caja pegada abajo): variable CHOCOLATITO_PANTALLA=normal.",
|
|
92
100
|
"",
|
|
93
101
|
"CODIGOS DE SALIDA (modo orden unica)",
|
|
94
102
|
" 0 La tarea se completo.",
|
|
@@ -136,9 +144,29 @@ function instalarRedDeSeguridad() {
|
|
|
136
144
|
});
|
|
137
145
|
// Una excepcion no capturada si es terminal, pero al menos se sale con la
|
|
138
146
|
// terminal usable y con un mensaje legible en vez de una traza cruda.
|
|
147
|
+
//
|
|
148
|
+
// NO SE USA `console.log` AQUI, Y ES A PROPOSITO
|
|
149
|
+
//
|
|
150
|
+
// Con el arbol montado la consola esta interceptada: un `console.log` va a la
|
|
151
|
+
// transcripcion, que la pinta el arbol... en el proximo dibujado, que ya no
|
|
152
|
+
// llega porque la linea siguiente cierra el proceso. El mensaje se escribe en
|
|
153
|
+
// un sitio que nadie va a leer nunca.
|
|
154
|
+
//
|
|
155
|
+
// Es exactamente el fallo que impidio diagnosticar la pantalla en negro dos
|
|
156
|
+
// veces: el programa se cerraba "sin decir nada" y si lo decia, pero a una
|
|
157
|
+
// pantalla que se iba con el. Aqui se suelta el arbol primero -que devuelve la
|
|
158
|
+
// consola de verdad- y se escribe directo a stderr, que no lo intercepta nadie.
|
|
139
159
|
process.on("uncaughtException", (err) => {
|
|
140
160
|
restaurarTerminal();
|
|
141
|
-
|
|
161
|
+
try {
|
|
162
|
+
desmontarArbol();
|
|
163
|
+
}
|
|
164
|
+
catch { }
|
|
165
|
+
try {
|
|
166
|
+
salirDePantallaFija();
|
|
167
|
+
}
|
|
168
|
+
catch { }
|
|
169
|
+
process.stderr.write(chalk.red(`\n✖ Error inesperado: ${err?.message || String(err)}\n`) +
|
|
142
170
|
chalk.gray(`${(err?.stack || "").split("\n").slice(1, 4).join("\n")}\n`));
|
|
143
171
|
process.exit(EXIT_FALLO);
|
|
144
172
|
});
|
|
@@ -190,8 +218,8 @@ async function main() {
|
|
|
190
218
|
// errores raros dentro de una dependencia. Ver config/nodeVersion.ts.
|
|
191
219
|
const nodeViejo = comprobarNode();
|
|
192
220
|
if (nodeViejo) {
|
|
193
|
-
console.error(chalk.red(`
|
|
194
|
-
${nodeViejo}
|
|
221
|
+
console.error(chalk.red(`
|
|
222
|
+
${nodeViejo}
|
|
195
223
|
`));
|
|
196
224
|
process.exit(EXIT_USO);
|
|
197
225
|
}
|
|
@@ -201,7 +229,16 @@ ${nodeViejo}
|
|
|
201
229
|
instalarRedDeSeguridad();
|
|
202
230
|
// --yes / -y aprueba las acciones sin preguntar. Util en modo comando unico y CI.
|
|
203
231
|
const autoYes = rawArgs.includes("--yes") || rawArgs.includes("-y");
|
|
204
|
-
|
|
232
|
+
// --servir abre la sesion en el navegador; --servir-red ademas la saca a la
|
|
233
|
+
// red local, que es lo que hace falta para entrar desde el movil.
|
|
234
|
+
const enRed = rawArgs.includes("--servir-red");
|
|
235
|
+
const quiereServir = enRed || rawArgs.includes("--servir");
|
|
236
|
+
const posPuerto = rawArgs.indexOf("--puerto");
|
|
237
|
+
const puertoWeb = posPuerto >= 0 ? Number(rawArgs[posPuerto + 1]) || 0 : 0;
|
|
238
|
+
// Ojo: lo que sobra de argv se convierte en la orden a ejecutar. Una bandera
|
|
239
|
+
// que no se filtre aqui no es una bandera ignorada, es una orden al modelo.
|
|
240
|
+
const banderas = new Set(["--yes", "-y", "--servir", "--servir-red", "--puerto"]);
|
|
241
|
+
const args = rawArgs.filter((a, i) => !banderas.has(a) && !(posPuerto >= 0 && i === posPuerto + 1 && puertoWeb > 0));
|
|
205
242
|
const singlePrompt = args.join(" ").trim();
|
|
206
243
|
// Los errores de uso se resuelven ANTES de la licencia y de la API key: no
|
|
207
244
|
// dependen de estar suscrito, y en CI hay que poder distinguir "lo has
|
|
@@ -213,7 +250,7 @@ ${nodeViejo}
|
|
|
213
250
|
}
|
|
214
251
|
// Sin orden y sin terminal no hay a quien preguntar: el REPL moria en el
|
|
215
252
|
// primer prompt y salia con 0, que en CI se lee como "todo bien".
|
|
216
|
-
if (!singlePrompt && !process.stdin.isTTY) {
|
|
253
|
+
if (!singlePrompt && !process.stdin.isTTY && !quiereServir) {
|
|
217
254
|
console.error(chalk.red("\n✖ La entrada no es una terminal y no se dio ninguna orden.") +
|
|
218
255
|
chalk.gray('\n En scripts y CI usa: chocolatito --yes "haz X".\n'));
|
|
219
256
|
process.exit(EXIT_USO);
|
|
@@ -233,20 +270,10 @@ ${nodeViejo}
|
|
|
233
270
|
: await ensureApiKey();
|
|
234
271
|
const context = await getProjectContext(process.cwd());
|
|
235
272
|
const skillManager = new SkillManager(context.cwd);
|
|
236
|
-
const memoryManager = new MemoryManager();
|
|
273
|
+
const memoryManager = new MemoryManager(context.cwd);
|
|
237
274
|
const hookManager = new HookManager(context.cwd);
|
|
238
275
|
const sessionManager = new SessionManager();
|
|
239
276
|
const sessionId = `session_${Date.now()}`;
|
|
240
|
-
// La barra de abajo solo puede quedarse clavada si el programa toma la
|
|
241
|
-
// pantalla entera. Se pide con CHOCOLATITO_PANTALLA=fija y se paga con el
|
|
242
|
-
// historial del terminal: ver ui/pantalla.ts.
|
|
243
|
-
if (pantallaFijaPedida()) {
|
|
244
|
-
entrarEnPantallaFija();
|
|
245
|
-
// Entrar y empezar a dibujar son la misma decision: entrar sin montar el
|
|
246
|
-
// arbol deja una consola en blanco de la que solo se sale cerrando la
|
|
247
|
-
// ventana.
|
|
248
|
-
montarArbol();
|
|
249
|
-
}
|
|
250
277
|
let currentModel = DEFAULT_ESPECTRO;
|
|
251
278
|
// La barra de estado lo lee de aqui. Fijarlo al arrancar y no solo al
|
|
252
279
|
// cambiarlo: si no, hasta el primer /effort la barra no dice nada.
|
|
@@ -309,7 +336,9 @@ ${nodeViejo}
|
|
|
309
336
|
SLASH_COMMANDS.splice(1, 0, { name: "/resume", description: "Reanudar una sesión anterior guardada", args: "[id]" });
|
|
310
337
|
}
|
|
311
338
|
// Single-command mode: chocolatito "crea index.html"
|
|
312
|
-
|
|
339
|
+
// Con --servir no: ahi la orden es solo la PRIMERA de una sesion que sigue
|
|
340
|
+
// viva esperando al navegador, no una tarea suelta que termina y cierra.
|
|
341
|
+
if (singlePrompt && !quiereServir) {
|
|
313
342
|
showBanner(context.cwd, currentModel, "idle");
|
|
314
343
|
renderUserPrompt(singlePrompt);
|
|
315
344
|
// El modo no interactivo pasa por los mismos hooks que el REPL: un veto que
|
|
@@ -350,6 +379,67 @@ ${nodeViejo}
|
|
|
350
379
|
chalk.gray(" shift+tab cambia el modo de permisos · puedes escribir mientras trabaja y se encola\n\n"));
|
|
351
380
|
if (getMode() !== "default")
|
|
352
381
|
console.log(` ${modeAnnouncement()}\n`);
|
|
382
|
+
// EL ARBOL SE MONTA AQUI Y NO ANTES
|
|
383
|
+
//
|
|
384
|
+
// Va de serie. CHOCOLATITO_PANTALLA=normal devuelve la clasica. Ver ui/pantalla.ts.
|
|
385
|
+
//
|
|
386
|
+
// Estaba montado mucho mas arriba, antes del zorro. Y el zorro se dibuja con
|
|
387
|
+
// animacion: mueve el cursor y repinta. Dos cosas moviendo el cursor a la vez
|
|
388
|
+
// descuadran la cuenta de filas de Ink desde el primer fotograma, y lo que se
|
|
389
|
+
// ve es "sale una barra y lo demas negro".
|
|
390
|
+
//
|
|
391
|
+
// Aqui no queda nada por animar: el zorro ya salio, y lo que venga sale por el
|
|
392
|
+
// arbol o por el parche de console de Ink.
|
|
393
|
+
//
|
|
394
|
+
// Y el arbol solo se monta si se entro de VERDAD: sin terminal -salida a un
|
|
395
|
+
// archivo, un script, CI- no se entra, e Ink sin terminal escribe su marco
|
|
396
|
+
// como texto suelto. La salida del programa saldria con la caja de escribir
|
|
397
|
+
// metida dentro.
|
|
398
|
+
if (pantallaFijaPedida() && entrarEnPantallaFija()) {
|
|
399
|
+
// Entrar y empezar a dibujar son la misma decision: desviar el dibujo sin
|
|
400
|
+
// que haya nadie pintandolo deja la consola muda.
|
|
401
|
+
montarArbol();
|
|
402
|
+
}
|
|
403
|
+
// EL SERVIDOR WEB, SI SE PIDIO
|
|
404
|
+
//
|
|
405
|
+
// Va DESPUES de montar el arbol, y el orden no es casual: ui/pantalla.ts ya
|
|
406
|
+
// reemplazo los cuatro canales de console para mandarlos a la transcripcion, y
|
|
407
|
+
// la captura tiene que envolver ESA version. Al reves, pantalla envolveria a la
|
|
408
|
+
// captura y al navegador no llegaria nada. Ver servidor/captura.ts.
|
|
409
|
+
if (quiereServir) {
|
|
410
|
+
try {
|
|
411
|
+
iniciarCaptura();
|
|
412
|
+
const web = await iniciarServidorWeb({ puerto: puertoWeb || undefined, enRed });
|
|
413
|
+
marcarServirActivo(true);
|
|
414
|
+
console.log(chalk.hex("#e8833a")("\n 🦊 Sesión abierta en el navegador\n"));
|
|
415
|
+
console.log(` ${chalk.bold("Aquí:")} ${chalk.cyan(web.url)}`);
|
|
416
|
+
for (const dir of web.urlsDeRed) {
|
|
417
|
+
console.log(` ${chalk.bold("Del móvil:")} ${chalk.cyan(dir)}`);
|
|
418
|
+
}
|
|
419
|
+
if (!enRed) {
|
|
420
|
+
console.log(chalk.gray("\n Solo desde esta máquina. Para entrar desde el móvil: --servir-red"));
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
console.log(chalk.yellow("\n ⚠ Abierto a tu red local.") +
|
|
424
|
+
chalk.gray(" Cualquiera que tenga el enlace COMPLETO (con el token)" +
|
|
425
|
+
"\n puede usar esta sesión. No lo pegues en ningún sitio compartido."));
|
|
426
|
+
}
|
|
427
|
+
console.log(chalk.gray("\n Lo que escribas aquí ya no se lee: manda el navegador y este terminal es el monitor.\n"));
|
|
428
|
+
// Una orden en la linea de comandos con --servir es la PRIMERA del turno,
|
|
429
|
+
// no una tarea suelta: entra por la misma puerta que lo que llegue del movil.
|
|
430
|
+
if (singlePrompt)
|
|
431
|
+
entregarEntrada(singlePrompt);
|
|
432
|
+
}
|
|
433
|
+
catch (err) {
|
|
434
|
+
const ocupado = err?.code === "EADDRINUSE";
|
|
435
|
+
const motivo = ocupado ? "el puerto ya está ocupado" : err?.message || String(err);
|
|
436
|
+
console.error(chalk.red("\n✖ No se pudo abrir la sesión web: " + motivo));
|
|
437
|
+
if (ocupado)
|
|
438
|
+
console.error(chalk.gray(" Prueba con otro: chocolatito --servir --puerto 4701"));
|
|
439
|
+
console.error("");
|
|
440
|
+
process.exit(EXIT_USO);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
353
443
|
// Lo que el usuario escribio mientras el agente trabajaba entra como siguiente
|
|
354
444
|
// orden, sin tener que volver a teclearlo.
|
|
355
445
|
let pendiente = null;
|
|
@@ -372,6 +462,15 @@ ${nodeViejo}
|
|
|
372
462
|
stopYaForzado = false;
|
|
373
463
|
renderUserPrompt(userInput);
|
|
374
464
|
}
|
|
465
|
+
else if (quiereServir) {
|
|
466
|
+
// Manda el navegador. No se corre tambien el prompt del teclado a ver cual
|
|
467
|
+
// contesta primero: askInteractivePrompt se queda con el TTY prestado y no
|
|
468
|
+
// sabe cancelarse a medias, y dos entradas peleando por el mismo terminal
|
|
469
|
+
// es exactamente lo que dejo la pantalla en negro dos veces.
|
|
470
|
+
userInput = await esperarEntradaWeb();
|
|
471
|
+
stopYaForzado = false;
|
|
472
|
+
renderUserPrompt(userInput);
|
|
473
|
+
}
|
|
375
474
|
else {
|
|
376
475
|
try {
|
|
377
476
|
userInput = await askInteractivePrompt(isPlanMode ? chalk.cyan("plan ❯") : "❯", agent.currentCwd);
|
|
@@ -421,15 +520,15 @@ ${nodeViejo}
|
|
|
421
520
|
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
422
521
|
.map((m) => `### 👤 ${m.role === "user" ? "Usuario" : "Chocolatito"}\n\n${typeof m.content === "string" ? m.content : JSON.stringify(m.content, null, 2)}\n`)
|
|
423
522
|
.join("\n---\n\n");
|
|
424
|
-
const mdContent = `# 🦊 Bitácora de Sesión — Chocolatito Code
|
|
425
|
-
**Fecha:** ${new Date().toLocaleString()}
|
|
426
|
-
**Directorio:** \`${agent.currentCwd}\`
|
|
427
|
-
**Motor:** ${currentModel.name}
|
|
428
|
-
**Inversión Total:** ${agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel).formattedCost}
|
|
429
|
-
|
|
430
|
-
---
|
|
431
|
-
|
|
432
|
-
${sessionData}
|
|
523
|
+
const mdContent = `# 🦊 Bitácora de Sesión — Chocolatito Code
|
|
524
|
+
**Fecha:** ${new Date().toLocaleString()}
|
|
525
|
+
**Directorio:** \`${agent.currentCwd}\`
|
|
526
|
+
**Motor:** ${currentModel.name}
|
|
527
|
+
**Inversión Total:** ${agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel).formattedCost}
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
${sessionData}
|
|
433
532
|
`;
|
|
434
533
|
fs.writeFileSync(exportPath, mdContent, "utf-8");
|
|
435
534
|
console.log(chalk.green(`\n✔ Bitácora exportada exitosamente en: ${chalk.bold.white(exportPath)}\n`));
|
|
@@ -440,7 +539,27 @@ ${sessionData}
|
|
|
440
539
|
continue;
|
|
441
540
|
}
|
|
442
541
|
if (trimmed.startsWith("/resume")) {
|
|
443
|
-
|
|
542
|
+
let targetId = trimmed.slice(7).trim();
|
|
543
|
+
if (!targetId) {
|
|
544
|
+
const detailedSessions = sessionManager.listSessionsDetailed();
|
|
545
|
+
if (detailedSessions.length === 0) {
|
|
546
|
+
console.log(chalk.yellow("\n⚠ No hay sesiones guardadas previas.\n"));
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (process.stdin.isTTY) {
|
|
550
|
+
const opciones = formatearOpcionesDeSesion(detailedSessions);
|
|
551
|
+
const elegido = await elegirDeLista("REANUDAR SESIÓN — CHOCOLATITO CODE", opciones);
|
|
552
|
+
if (elegido === null) {
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
targetId = elegido;
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
const latest = sessionManager.loadLatestSession();
|
|
559
|
+
if (latest)
|
|
560
|
+
targetId = latest.id;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
444
563
|
const loaded = targetId ? sessionManager.loadSession(targetId) : sessionManager.loadLatestSession();
|
|
445
564
|
if (loaded) {
|
|
446
565
|
// Se restaura la conversacion, no el entorno. El array guardado trae en
|
|
@@ -523,12 +642,12 @@ ${sessionData}
|
|
|
523
642
|
}
|
|
524
643
|
console.log(chalk.bold.hex("#A855F7")("\n🎯 INICIANDO MODO META AUTÓNOMA (GOAL MODE):"));
|
|
525
644
|
console.log(chalk.gray(` Objetivo: "${goalText}"\n`));
|
|
526
|
-
const autonomousPrompt = `[MODO META AUTÓNOMA ACTIVADO]
|
|
527
|
-
Tu objetivo principal es: "${goalText}"
|
|
528
|
-
Directivas de ejecución:
|
|
529
|
-
1. Formula un plan de ejecución mental paso a paso.
|
|
530
|
-
2. Ejecuta cada paso utilizando las herramientas correspondientes (creación, edición, ejecución, verificación).
|
|
531
|
-
3. Si algo falla o hay errores, autocorregir inmediatamente y volver a probar.
|
|
645
|
+
const autonomousPrompt = `[MODO META AUTÓNOMA ACTIVADO]
|
|
646
|
+
Tu objetivo principal es: "${goalText}"
|
|
647
|
+
Directivas de ejecución:
|
|
648
|
+
1. Formula un plan de ejecución mental paso a paso.
|
|
649
|
+
2. Ejecuta cada paso utilizando las herramientas correspondientes (creación, edición, ejecución, verificación).
|
|
650
|
+
3. Si algo falla o hay errores, autocorregir inmediatamente y volver a probar.
|
|
532
651
|
4. No te detengas hasta verificar que el objetivo esté 100% completado con éxito.`;
|
|
533
652
|
renderUserPrompt(`/goal ${goalText}`);
|
|
534
653
|
await agent.runTask(autonomousPrompt);
|
|
@@ -554,6 +673,11 @@ Directivas de ejecución:
|
|
|
554
673
|
console.log(diffOut ? chalk.gray(diffOut) : chalk.green("✔ Sin cambios pendientes.") + "\n");
|
|
555
674
|
continue;
|
|
556
675
|
}
|
|
676
|
+
if (trimmed === "/audit" || trimmed === "/review" || trimmed.startsWith("/audit ") || trimmed.startsWith("/review ")) {
|
|
677
|
+
const report = await auditGitDiff(agent.currentCwd);
|
|
678
|
+
console.log(formatAuditReport(report));
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
557
681
|
if (trimmed.startsWith("/commit")) {
|
|
558
682
|
const msg = trimmed.slice(7).trim();
|
|
559
683
|
if (!msg) {
|
|
@@ -570,6 +694,7 @@ Directivas de ejecución:
|
|
|
570
694
|
const res = await changeDir(target, agent.currentCwd);
|
|
571
695
|
if (res.success) {
|
|
572
696
|
agent.currentCwd = res.newCwd;
|
|
697
|
+
memoryManager.setCwd(agent.currentCwd);
|
|
573
698
|
const refreshedContext = await getProjectContext(agent.currentCwd);
|
|
574
699
|
systemPrompt = buildSystemPrompt(refreshedContext, currentModel, skillManager, memoryManager, isPlanMode);
|
|
575
700
|
agent.clearHistory(systemPrompt);
|
|
@@ -632,12 +757,12 @@ Directivas de ejecución:
|
|
|
632
757
|
const refreshedContext = await getProjectContext(agent.currentCwd);
|
|
633
758
|
const updatedPrompt = buildSystemPrompt(refreshedContext, currentModel, skillManager, memoryManager, isPlanMode);
|
|
634
759
|
agent.setEspectroModel(currentModel, updatedPrompt);
|
|
635
|
-
console.log(chalk.green(`
|
|
760
|
+
console.log(chalk.green(`
|
|
636
761
|
✔ Nivel actualizado a `) +
|
|
637
762
|
chalk.bold.hex("#A855F7")(currentModel.name) +
|
|
638
763
|
chalk.gray(` (${currentModel.effortLevel})`) +
|
|
639
|
-
`
|
|
640
|
-
${chalk.italic.gray(currentModel.description)}
|
|
764
|
+
`
|
|
765
|
+
${chalk.italic.gray(currentModel.description)}
|
|
641
766
|
`);
|
|
642
767
|
// EL TECLADO SE QUEDABA TRABADO AL CAMBIAR DE MOTOR
|
|
643
768
|
//
|
package/dist/memory/manager.d.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
export type MemoryScope = "project" | "global";
|
|
1
2
|
export declare class MemoryManager {
|
|
2
|
-
private
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
private cwd;
|
|
4
|
+
constructor(cwd?: string);
|
|
5
|
+
setCwd(cwd: string): void;
|
|
6
|
+
getCwd(): string;
|
|
7
|
+
private getGlobalDir;
|
|
8
|
+
private getProjectDir;
|
|
9
|
+
private isSamePath;
|
|
10
|
+
private isProjectCwdValid;
|
|
11
|
+
private resolveScope;
|
|
12
|
+
private getDirForScope;
|
|
5
13
|
private ensureDirectory;
|
|
14
|
+
private sanitizeTopic;
|
|
15
|
+
private readIndexEntries;
|
|
6
16
|
getMemoryIndex(): string;
|
|
7
|
-
saveMemory(topic: string, fact: string): string;
|
|
8
|
-
readMemory(topic: string): string;
|
|
17
|
+
saveMemory(topic: string, fact: string, scope?: MemoryScope): string;
|
|
18
|
+
readMemory(topic: string, scope?: MemoryScope): string;
|
|
9
19
|
}
|
package/dist/memory/manager.js
CHANGED
|
@@ -2,35 +2,118 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
export class MemoryManager {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
this.memoryDir = path.join(os.homedir(), ".chocolatito", "memory");
|
|
9
|
-
this.memoryIndexPath = path.join(this.memoryDir, "MEMORY.md");
|
|
10
|
-
this.ensureDirectory();
|
|
5
|
+
cwd;
|
|
6
|
+
constructor(cwd = process.cwd()) {
|
|
7
|
+
this.cwd = path.resolve(cwd || process.cwd());
|
|
11
8
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
setCwd(cwd) {
|
|
10
|
+
this.cwd = path.resolve(cwd || process.cwd());
|
|
11
|
+
}
|
|
12
|
+
getCwd() {
|
|
13
|
+
return this.cwd;
|
|
14
|
+
}
|
|
15
|
+
getGlobalDir() {
|
|
16
|
+
return path.join(os.homedir(), ".chocolatito", "memory");
|
|
17
|
+
}
|
|
18
|
+
getProjectDir() {
|
|
19
|
+
return path.join(this.cwd, ".chocolatito", "memory");
|
|
20
|
+
}
|
|
21
|
+
isSamePath(p1, p2) {
|
|
22
|
+
const r1 = path.resolve(p1);
|
|
23
|
+
const r2 = path.resolve(p2);
|
|
24
|
+
if (process.platform === "win32") {
|
|
25
|
+
return r1.toLowerCase() === r2.toLowerCase();
|
|
19
26
|
}
|
|
27
|
+
return r1 === r2;
|
|
20
28
|
}
|
|
21
|
-
|
|
22
|
-
this.
|
|
29
|
+
isProjectCwdValid() {
|
|
30
|
+
if (!this.cwd || typeof this.cwd !== "string")
|
|
31
|
+
return false;
|
|
23
32
|
try {
|
|
24
|
-
|
|
33
|
+
if (this.isSamePath(this.cwd, os.homedir())) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
25
37
|
}
|
|
26
38
|
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
resolveScope(scope) {
|
|
43
|
+
if (scope === "project" || scope === "global") {
|
|
44
|
+
return scope;
|
|
45
|
+
}
|
|
46
|
+
return this.isProjectCwdValid() ? "project" : "global";
|
|
47
|
+
}
|
|
48
|
+
getDirForScope(scope) {
|
|
49
|
+
return scope === "project" ? this.getProjectDir() : this.getGlobalDir();
|
|
50
|
+
}
|
|
51
|
+
ensureDirectory(scope) {
|
|
52
|
+
const dir = this.getDirForScope(scope);
|
|
53
|
+
if (!fs.existsSync(dir)) {
|
|
54
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
return dir;
|
|
57
|
+
}
|
|
58
|
+
sanitizeTopic(topic) {
|
|
59
|
+
return topic.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
60
|
+
}
|
|
61
|
+
readIndexEntries(dir) {
|
|
62
|
+
if (!fs.existsSync(dir))
|
|
63
|
+
return null;
|
|
64
|
+
const indexPath = path.join(dir, "MEMORY.md");
|
|
65
|
+
if (fs.existsSync(indexPath)) {
|
|
66
|
+
const content = fs.readFileSync(indexPath, "utf-8").trim();
|
|
67
|
+
const lines = content
|
|
68
|
+
.split("\n")
|
|
69
|
+
.map((l) => l.trim())
|
|
70
|
+
.filter((l) => l.length > 0 && (l.startsWith("-") || l.startsWith("*")));
|
|
71
|
+
if (lines.length > 0) {
|
|
72
|
+
return lines.join("\n");
|
|
73
|
+
}
|
|
74
|
+
if (content.length > 0) {
|
|
75
|
+
return content;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const files = fs
|
|
80
|
+
.readdirSync(dir)
|
|
81
|
+
.filter((f) => f.endsWith(".md") && f !== "MEMORY.md");
|
|
82
|
+
if (files.length > 0) {
|
|
83
|
+
return files
|
|
84
|
+
.map((f) => {
|
|
85
|
+
const topic = f.replace(/\.md$/, "");
|
|
86
|
+
return `- [${topic}](./${f}): Registro de ${topic}.`;
|
|
87
|
+
})
|
|
88
|
+
.join("\n");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch { }
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
getMemoryIndex() {
|
|
95
|
+
const isProjectValid = this.isProjectCwdValid();
|
|
96
|
+
const projectEntries = isProjectValid ? this.readIndexEntries(this.getProjectDir()) : null;
|
|
97
|
+
const globalEntries = this.readIndexEntries(this.getGlobalDir());
|
|
98
|
+
const hasProject = Boolean(projectEntries);
|
|
99
|
+
const hasGlobal = Boolean(globalEntries);
|
|
100
|
+
if (!hasProject && !hasGlobal) {
|
|
27
101
|
return "Sin memoria previa.";
|
|
28
102
|
}
|
|
103
|
+
const projectSection = hasProject
|
|
104
|
+
? projectEntries
|
|
105
|
+
: "Sin notas locales para este proyecto.";
|
|
106
|
+
const globalSection = hasGlobal
|
|
107
|
+
? globalEntries
|
|
108
|
+
: "Sin preferencias globales.";
|
|
109
|
+
return `Memoria del Proyecto (.chocolatito/memory/):\n${projectSection}\n\nPreferencias Globales (~/.chocolatito/memory/):\n${globalSection}`;
|
|
29
110
|
}
|
|
30
|
-
saveMemory(topic, fact) {
|
|
31
|
-
this.
|
|
32
|
-
const
|
|
33
|
-
const
|
|
111
|
+
saveMemory(topic, fact, scope) {
|
|
112
|
+
const resolvedScope = this.resolveScope(scope);
|
|
113
|
+
const dir = this.ensureDirectory(resolvedScope);
|
|
114
|
+
const sanitized = this.sanitizeTopic(topic);
|
|
115
|
+
const filePath = path.join(dir, `${sanitized}.md`);
|
|
116
|
+
const indexPath = path.join(dir, "MEMORY.md");
|
|
34
117
|
const timestamp = new Date().toISOString().split("T")[0];
|
|
35
118
|
const entry = `\n- [${timestamp}] ${fact}`;
|
|
36
119
|
if (fs.existsSync(filePath)) {
|
|
@@ -40,19 +123,53 @@ export class MemoryManager {
|
|
|
40
123
|
fs.writeFileSync(filePath, `# Memoria: ${topic}\n${entry}\n`, "utf-8");
|
|
41
124
|
}
|
|
42
125
|
// Update MEMORY.md index if not already present
|
|
43
|
-
|
|
126
|
+
let indexContent = "";
|
|
127
|
+
if (fs.existsSync(indexPath)) {
|
|
128
|
+
indexContent = fs.readFileSync(indexPath, "utf-8");
|
|
129
|
+
}
|
|
44
130
|
if (!indexContent.includes(`${sanitized}.md`)) {
|
|
45
131
|
const link = `- [${topic}](./${sanitized}.md): Registro de ${topic}.\n`;
|
|
46
|
-
fs.appendFileSync(
|
|
132
|
+
fs.appendFileSync(indexPath, link, "utf-8");
|
|
133
|
+
}
|
|
134
|
+
if (resolvedScope === "project") {
|
|
135
|
+
return `✔ Hecho guardado en memoria local de proyecto (.chocolatito/memory/${sanitized}.md).`;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
return `✔ Hecho guardado en memoria global (~/.chocolatito/memory/${sanitized}.md).`;
|
|
47
139
|
}
|
|
48
|
-
return `✔ Hecho guardado en memoria persistente (~/.chocolatito/memory/${sanitized}.md).`;
|
|
49
140
|
}
|
|
50
|
-
readMemory(topic) {
|
|
51
|
-
this.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
141
|
+
readMemory(topic, scope) {
|
|
142
|
+
const sanitized = this.sanitizeTopic(topic);
|
|
143
|
+
if (scope === "project") {
|
|
144
|
+
const pPath = path.join(this.getProjectDir(), `${sanitized}.md`);
|
|
145
|
+
if (fs.existsSync(pPath)) {
|
|
146
|
+
return fs.readFileSync(pPath, "utf-8");
|
|
147
|
+
}
|
|
148
|
+
return `No hay memoria registrada para el tema "${topic}" en el proyecto.`;
|
|
149
|
+
}
|
|
150
|
+
if (scope === "global") {
|
|
151
|
+
const gPath = path.join(this.getGlobalDir(), `${sanitized}.md`);
|
|
152
|
+
if (fs.existsSync(gPath)) {
|
|
153
|
+
return fs.readFileSync(gPath, "utf-8");
|
|
154
|
+
}
|
|
155
|
+
return `No hay memoria registrada para el tema "${topic}" en la memoria global.`;
|
|
156
|
+
}
|
|
157
|
+
// Sin scope especificado: buscar primero en proyecto, luego en global
|
|
158
|
+
const sameDir = this.isSamePath(this.getProjectDir(), this.getGlobalDir());
|
|
159
|
+
const pPath = path.join(this.getProjectDir(), `${sanitized}.md`);
|
|
160
|
+
const gPath = path.join(this.getGlobalDir(), `${sanitized}.md`);
|
|
161
|
+
const pExists = this.isProjectCwdValid() && fs.existsSync(pPath);
|
|
162
|
+
const gExists = !sameDir && fs.existsSync(gPath);
|
|
163
|
+
if (pExists && gExists) {
|
|
164
|
+
const pContent = fs.readFileSync(pPath, "utf-8").trim();
|
|
165
|
+
const gContent = fs.readFileSync(gPath, "utf-8").trim();
|
|
166
|
+
return `[Memoria de Proyecto]\n${pContent}\n\n[Memoria Global]\n${gContent}`;
|
|
167
|
+
}
|
|
168
|
+
if (pExists) {
|
|
169
|
+
return fs.readFileSync(pPath, "utf-8");
|
|
170
|
+
}
|
|
171
|
+
if (gExists || (sameDir && fs.existsSync(gPath))) {
|
|
172
|
+
return fs.readFileSync(gPath, "utf-8");
|
|
56
173
|
}
|
|
57
174
|
return `No hay memoria registrada para el tema "${topic}".`;
|
|
58
175
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LO QUE SE ESCRIBE, COPIADO PARA EL NAVEGADOR
|
|
3
|
+
*
|
|
4
|
+
* No se redirige nada: se COPIA. El terminal sigue recibiendo exactamente lo
|
|
5
|
+
* mismo que antes, porque quien arranca `--servir` normalmente lo deja abierto
|
|
6
|
+
* en su PC y lo mira desde ahi tambien. Si esto desviara la salida, el que esta
|
|
7
|
+
* delante de la maquina se quedaria sin ver su propia sesion.
|
|
8
|
+
*
|
|
9
|
+
* POR QUE SE ENVUELVE `console` Y NO `process.stdout`
|
|
10
|
+
*
|
|
11
|
+
* Ya esta decidido en ui/salida.ts y ui/pantalla.ts, y por las razones buenas:
|
|
12
|
+
* sustituir process.stdout hace desaparecer pruebas del informe de `node --test`
|
|
13
|
+
* sin que fallen. Aqui se envuelve console, que es otro objeto y no tiene ese
|
|
14
|
+
* problema.
|
|
15
|
+
*
|
|
16
|
+
* EL ORDEN IMPORTA, Y ES LA UNICA TRAMPA DE ESTE ARCHIVO
|
|
17
|
+
*
|
|
18
|
+
* ui/pantalla.ts:144 ya reemplaza los cuatro canales para mandarlos a la
|
|
19
|
+
* transcripcion de Ink. Esto tiene que envolverse DESPUES de eso: asi lo que se
|
|
20
|
+
* llama es la version de pantalla (el terminal sigue pintando) y de paso se
|
|
21
|
+
* copia. Al reves, pantalla envolveria a esto y la copia no se enteraria de
|
|
22
|
+
* nada. Por eso en index.ts la captura arranca despues de entrarEnPantallaFija.
|
|
23
|
+
*/
|
|
24
|
+
export interface LineaWeb {
|
|
25
|
+
/** Estable y creciente. El navegador lo usa para no repetir lineas al reconectar. */
|
|
26
|
+
id: number;
|
|
27
|
+
texto: string;
|
|
28
|
+
}
|
|
29
|
+
/** Mete una linea propia (avisos del servidor, eco de lo que llega del movil). */
|
|
30
|
+
export declare function anotarWeb(texto: string): void;
|
|
31
|
+
export declare function iniciarCaptura(): void;
|
|
32
|
+
export declare function detenerCaptura(): void;
|
|
33
|
+
export declare function historialWeb(desde?: number): LineaWeb[];
|
|
34
|
+
export declare function alEscribirWeb(cb: (linea: LineaWeb) => void): () => void;
|
|
35
|
+
/** Solo para pruebas: deja el modulo como recien cargado. */
|
|
36
|
+
export declare function vaciarCaptura(): void;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { format } from "node:util";
|
|
2
|
+
const CANALES = ["log", "error", "warn", "info"];
|
|
3
|
+
/**
|
|
4
|
+
* Tope del historial. Es para el que abre el movil a mitad de sesion y quiere
|
|
5
|
+
* contexto, no un registro completo: eso ya lo tiene el terminal de verdad.
|
|
6
|
+
*/
|
|
7
|
+
const MAX_LINEAS = 1_000;
|
|
8
|
+
let lineas = [];
|
|
9
|
+
let siguienteId = 1;
|
|
10
|
+
const oyentes = new Set();
|
|
11
|
+
const previos = new Map();
|
|
12
|
+
/** Reentrada: si un oyente escribe por consola, no se vuelve a capturar. */
|
|
13
|
+
let dentro = false;
|
|
14
|
+
function guardar(texto) {
|
|
15
|
+
for (const trozo of texto.replace(/\r\n/g, "\n").split("\n")) {
|
|
16
|
+
const linea = { id: siguienteId++, texto: trozo };
|
|
17
|
+
lineas.push(linea);
|
|
18
|
+
if (lineas.length > MAX_LINEAS)
|
|
19
|
+
lineas = lineas.slice(-MAX_LINEAS);
|
|
20
|
+
for (const cb of oyentes) {
|
|
21
|
+
try {
|
|
22
|
+
cb(linea);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Un cliente que se cae no puede tumbar la salida de la sesion.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Mete una linea propia (avisos del servidor, eco de lo que llega del movil). */
|
|
31
|
+
export function anotarWeb(texto) {
|
|
32
|
+
guardar(texto);
|
|
33
|
+
}
|
|
34
|
+
export function iniciarCaptura() {
|
|
35
|
+
if (previos.size)
|
|
36
|
+
return;
|
|
37
|
+
for (const nombre of CANALES) {
|
|
38
|
+
const previo = console[nombre].bind(console);
|
|
39
|
+
previos.set(nombre, previo);
|
|
40
|
+
console[nombre] = (...args) => {
|
|
41
|
+
previo(...args);
|
|
42
|
+
if (dentro)
|
|
43
|
+
return;
|
|
44
|
+
dentro = true;
|
|
45
|
+
try {
|
|
46
|
+
guardar(format(...args));
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
dentro = false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function detenerCaptura() {
|
|
55
|
+
for (const nombre of CANALES) {
|
|
56
|
+
const previo = previos.get(nombre);
|
|
57
|
+
if (previo)
|
|
58
|
+
console[nombre] = previo;
|
|
59
|
+
}
|
|
60
|
+
previos.clear();
|
|
61
|
+
}
|
|
62
|
+
export function historialWeb(desde = 0) {
|
|
63
|
+
return desde ? lineas.filter((l) => l.id > desde) : lineas.slice();
|
|
64
|
+
}
|
|
65
|
+
export function alEscribirWeb(cb) {
|
|
66
|
+
oyentes.add(cb);
|
|
67
|
+
return () => oyentes.delete(cb);
|
|
68
|
+
}
|
|
69
|
+
/** Solo para pruebas: deja el modulo como recien cargado. */
|
|
70
|
+
export function vaciarCaptura() {
|
|
71
|
+
lineas = [];
|
|
72
|
+
siguienteId = 1;
|
|
73
|
+
oyentes.clear();
|
|
74
|
+
}
|