chocolatito-code 1.0.0 → 1.1.0

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 (102) hide show
  1. package/README.md +89 -0
  2. package/dist/agent/compaction.d.ts +65 -0
  3. package/dist/agent/compaction.js +134 -0
  4. package/dist/agent/context.d.ts +3 -0
  5. package/dist/agent/context.js +35 -0
  6. package/dist/agent/loop.d.ts +91 -2
  7. package/dist/agent/loop.js +536 -78
  8. package/dist/agent/loopDetector.d.ts +57 -0
  9. package/dist/agent/loopDetector.js +0 -0
  10. package/dist/agent/mentions.d.ts +13 -0
  11. package/dist/agent/mentions.js +49 -0
  12. package/dist/agent/repoMap.d.ts +117 -0
  13. package/dist/agent/repoMap.js +560 -0
  14. package/dist/agent/result.d.ts +64 -0
  15. package/dist/agent/result.js +48 -0
  16. package/dist/agent/retry.d.ts +35 -0
  17. package/dist/agent/retry.js +123 -4
  18. package/dist/agent/subagent.js +62 -15
  19. package/dist/agent/syntaxValidator.js +288 -52
  20. package/dist/agent/toolGate.d.ts +58 -0
  21. package/dist/agent/toolGate.js +111 -0
  22. package/dist/agent/tracker.js +4 -7
  23. package/dist/agent/undoManager.d.ts +44 -11
  24. package/dist/agent/undoManager.js +95 -24
  25. package/dist/agent/usageMeter.d.ts +56 -0
  26. package/dist/agent/usageMeter.js +51 -0
  27. package/dist/agent/verifier.d.ts +49 -0
  28. package/dist/agent/verifier.js +158 -0
  29. package/dist/config/constants.js +14 -0
  30. package/dist/config/engine.d.ts +19 -0
  31. package/dist/config/engine.js +35 -3
  32. package/dist/config/license.d.ts +5 -0
  33. package/dist/config/license.js +107 -9
  34. package/dist/config/limits.d.ts +28 -0
  35. package/dist/config/limits.js +51 -0
  36. package/dist/config/permissions.d.ts +76 -1
  37. package/dist/config/permissions.js +379 -23
  38. package/dist/config/quota.js +24 -1
  39. package/dist/config/updater.d.ts +68 -0
  40. package/dist/config/updater.js +215 -0
  41. package/dist/hooks/manager.js +159 -14
  42. package/dist/index.js +222 -34
  43. package/dist/mcp/client.d.ts +22 -0
  44. package/dist/mcp/client.js +93 -11
  45. package/dist/prompts/systemPrompt.js +12 -1
  46. package/dist/sessions/manager.d.ts +12 -0
  47. package/dist/sessions/manager.js +33 -5
  48. package/dist/sessions/resume.d.ts +36 -0
  49. package/dist/sessions/resume.js +43 -0
  50. package/dist/skills/manager.d.ts +1 -0
  51. package/dist/skills/manager.js +9 -0
  52. package/dist/tools/binary.d.ts +46 -0
  53. package/dist/tools/binary.js +100 -0
  54. package/dist/tools/browserCdp.js +13 -1
  55. package/dist/tools/browserExtension.d.ts +5 -0
  56. package/dist/tools/browserExtension.js +202 -18
  57. package/dist/tools/browserSession.d.ts +23 -0
  58. package/dist/tools/browserSession.js +28 -0
  59. package/dist/tools/computerUse.js +33 -1
  60. package/dist/tools/createImage.d.ts +11 -0
  61. package/dist/tools/createImage.js +13 -1
  62. package/dist/tools/definitions.js +9 -3
  63. package/dist/tools/editDiagnosis.d.ts +54 -0
  64. package/dist/tools/editDiagnosis.js +142 -0
  65. package/dist/tools/editFile.d.ts +1 -0
  66. package/dist/tools/editFile.js +118 -62
  67. package/dist/tools/findFiles.d.ts +21 -0
  68. package/dist/tools/findFiles.js +75 -6
  69. package/dist/tools/getSystemInfo.js +15 -7
  70. package/dist/tools/gitTools.d.ts +24 -0
  71. package/dist/tools/gitTools.js +87 -26
  72. package/dist/tools/moveCopyFile.d.ts +18 -1
  73. package/dist/tools/moveCopyFile.js +74 -4
  74. package/dist/tools/patchCascade.d.ts +57 -0
  75. package/dist/tools/patchCascade.js +338 -0
  76. package/dist/tools/runCommand.js +213 -25
  77. package/dist/tools/runner.js +57 -4
  78. package/dist/tools/toolDefsComputer.js +12 -2
  79. package/dist/tools/truncator.d.ts +27 -1
  80. package/dist/tools/truncator.js +117 -11
  81. package/dist/tools/viewFile.js +15 -11
  82. package/dist/tools/visionBridge.js +7 -0
  83. package/dist/tools/webSearch.d.ts +21 -0
  84. package/dist/tools/webSearch.js +125 -14
  85. package/dist/tools/writeFile.js +18 -1
  86. package/dist/ui/interrupt.d.ts +4 -1
  87. package/dist/ui/interrupt.js +56 -5
  88. package/dist/ui/keyboardGuard.d.ts +55 -0
  89. package/dist/ui/keyboardGuard.js +65 -0
  90. package/dist/ui/loopPrompt.d.ts +17 -0
  91. package/dist/ui/loopPrompt.js +59 -0
  92. package/dist/ui/permissionPrompt.js +84 -2
  93. package/dist/ui/prompt.d.ts +35 -0
  94. package/dist/ui/prompt.js +345 -125
  95. package/dist/ui/reasoningStream.d.ts +8 -0
  96. package/dist/ui/reasoningStream.js +99 -7
  97. package/extension/README.md +95 -74
  98. package/extension/background.js +598 -178
  99. package/extension/content.js +92 -9
  100. package/extension/manifest.json +2 -1
  101. package/extension/popup.js +28 -12
  102. package/package.json +1 -1
package/README.md CHANGED
@@ -41,6 +41,50 @@ Para cambiar de nivel dentro de la sesión, escribe:
41
41
 
42
42
  Las lecturas independientes lanzadas en el mismo turno se ejecutan **en paralelo**.
43
43
 
44
+ ## 🗺️ Mapa del repositorio
45
+
46
+ Al arrancar, el agente recibe un índice de los símbolos del proyecto ordenado por **cuánto los
47
+ usa el resto del código** (un PageRank sobre el grafo de referencias, con presupuesto de 4.000
48
+ tokens). Sirve para que sepa qué existe y dónde mirar sin gastar turnos en `grep_search`
49
+ buscando a ciegas.
50
+
51
+ Son nombres y números de línea, no contenido: antes de editar sigue leyendo el archivo.
52
+
53
+ Lo que escribes también cuenta. Si dices *"arregla calcularImpuesto"* o *"falla en loop.ts"*, se
54
+ detecta la referencia aunque no pongas `@` y se le pasan las rutas —solo las rutas, no el
55
+ contenido, que cuesta más contexto del que ahorra.
56
+
57
+ ## ✅ Verificación automática tras editar
58
+
59
+ Cuando un turno escribe en disco, Chocolatito puede correr el verificador del proyecto y
60
+ devolverle el error al agente **en el mismo turno**, en vez de dar el trabajo por bueno y que el
61
+ fallo aparezca tres turnos después.
62
+
63
+ Viene **apagada**: correr un comando del proyecto es ejecutar código, y eso se pide. Para
64
+ activarla, en `.chocolatito/settings.json` (del proyecto o global en `~/.chocolatito/`):
65
+
66
+ ```json
67
+ {
68
+ "verify": { "auto": true },
69
+ "limits": { "maxIterations": 60, "subagentIterations": 20 }
70
+ }
71
+ ```
72
+
73
+ También con variables de entorno: `CHOCOLATITO_AUTO_VERIFY=1`, `CHOCOLATITO_VERIFY_CMD`,
74
+ `CHOCOLATITO_MAX_ITERATIONS`, `CHOCOLATITO_SUBAGENT_ITERATIONS`.
75
+
76
+ El comando se detecta solo, en este orden: `npm run typecheck` → `lint` → `check` → `build`, o
77
+ `npx tsc --noEmit`, `cargo check`, `go build ./...`. **Nunca los tests**: un `npm test` puede
78
+ levantar servidores o tardar minutos, y verificar no puede significar eso. Con `verify.command`
79
+ pones el tuyo. Si falla, el agente tiene tres intentos para arreglarlo; al cuarto para y te lo
80
+ dice.
81
+
82
+ ## ↻ Cuando el agente se atasca
83
+
84
+ Si repite tres veces la misma llamada con el mismo resultado, se para y pregunta: le dejas
85
+ seguir, o le dices que cambie de enfoque. Sin terminal (CI, `chocolatito "haz X"`) se corta
86
+ solo, para que un bucle que nadie está mirando no se coma la cuota.
87
+
44
88
  ## 🔌 MCP (Model Context Protocol)
45
89
 
46
90
  Conecta cualquier servidor MCP y sus herramientas quedan disponibles para el agente.
@@ -150,6 +194,25 @@ Para desarrollar sobre el propio proyecto:
150
194
  npm install && npm run build && npm link
151
195
  ```
152
196
 
197
+ ### Actualizaciones
198
+
199
+ Se instalan solas. Al arrancar se le pregunta al registro de npm si hay una versión más
200
+ nueva (como mucho una vez cada 6 horas, y sin retrasar el arranque: si no hay red, no pasa
201
+ nada). Si la hay, se instala de fondo y entra **al reiniciar** `chocolatito` — nunca a
202
+ mitad de sesión.
203
+
204
+ | | |
205
+ | --- | --- |
206
+ | `/actualizar` | Comprobar e instalar ahora, sin esperar a la siguiente ventana. |
207
+ | `CHOCOLATITO_SIN_ACTUALIZAR=1` | Apagarlo del todo. |
208
+
209
+ Una instalación de desarrollo (`npm link` sobre el repositorio) **no se toca nunca**:
210
+ instalar encima le borraría el trabajo a quien lo está escribiendo. Y no se baja jamás a
211
+ una preversión, aunque el registro la anuncie como la última.
212
+
213
+ Si la carpeta global es de administrador y no hay permiso para escribir, no se insiste: se
214
+ dice una vez el comando (`npm i -g chocolatito-code@latest`) y se sigue trabajando.
215
+
153
216
  ---
154
217
 
155
218
  ## 🚀 Uso Rápido
@@ -169,6 +232,32 @@ chocolatito --help
169
232
  chocolatito --version
170
233
  ```
171
234
 
235
+ ### Códigos de salida
236
+
237
+ En modo orden única el código dice qué pasó de verdad. Antes siempre era `0`: una cuota
238
+ agotada o una licencia caída dejaban el paso del CI en verde sin haber tocado nada.
239
+
240
+ | Código | Qué significa | Qué hacer |
241
+ | :---: | :--- | :--- |
242
+ | `0` | La tarea se completó. | Nada. |
243
+ | `1` | El agente no pudo terminar: el motor falló tras los reintentos, o se agotó el límite de 30 iteraciones. | Reintentar tiene sentido. |
244
+ | `2` | Error de uso: no se dio ninguna orden, o no hay terminal para pedirla. | Arreglar la invocación. |
245
+ | `3` | Cuota agotada. | Esperar a que se libere la ventana; reintentar no la devuelve. |
246
+ | `4` | Licencia o API key no válida. | Revisar `/licencia` o `~/.chocolatitorc`. |
247
+ | `5` | Ya hay 4 peticiones en curso con esta licencia. | Esperar los segundos que dice el mensaje y reintentar. |
248
+ | `130` | Interrumpido con `Esc` (128 + SIGINT, como cualquier otro programa). | Nada. |
249
+
250
+ Lo que no es `0` se explica también por `stderr`, con el motivo del corte. Un ejemplo:
251
+
252
+ ```bash
253
+ chocolatito --yes "ejecuta los tests y arregla lo que falle"
254
+ case $? in
255
+ 0) echo "hecho" ;;
256
+ 3|4) echo "problema de cuenta, no de código: no reintentes" ;;
257
+ *) echo "falló, se puede reintentar" ;;
258
+ esac
259
+ ```
260
+
172
261
  ## 🧪 Pruebas
173
262
 
174
263
  ```bash
@@ -0,0 +1,65 @@
1
+ import type OpenAI from "openai";
2
+ /**
3
+ * Recorte del historial sin romper el emparejamiento llamada-respuesta.
4
+ *
5
+ * La compactacion anterior cortaba por posicion fija: `slice(1, -4)` para lo que
6
+ * se resumia y `slice(-4)` para lo que se conservaba. Un turno con cuatro
7
+ * herramientas deja el historial acabado en [assistant(tool_calls), tool, tool,
8
+ * tool, tool]; ahi `slice(-4)` empieza por un `tool` y se queda con cuatro
9
+ * respuestas cuya llamada se ha quedado en la otra mitad. La API responde 400
10
+ * ("tool message without preceding tool_calls") y, como la compactacion vive
11
+ * dentro de un try/catch mudo, el usuario solo veia que la sesion se rompia
12
+ * justo cuando llevaba mucho rato trabajando.
13
+ *
14
+ * Aqui todo se decide por la forma del historial, no por un numero de mensajes.
15
+ */
16
+ export type Mensaje = OpenAI.Chat.ChatCompletionMessageParam;
17
+ /** Un assistant que pide herramientas: obliga a que le sigan sus respuestas. */
18
+ export declare function pideHerramientas(m: Mensaje | undefined): boolean;
19
+ /** Una respuesta de herramienta: no puede existir sin su llamada delante. */
20
+ export declare function esResultado(m: Mensaje | undefined): boolean;
21
+ /**
22
+ * Un corte en el indice `i` es valido si no parte un bloque de herramientas:
23
+ * ni deja una respuesta sin su llamada delante, ni una llamada sin respuestas
24
+ * detras. El indice 0 nunca se toca: es el prompt de sistema.
25
+ */
26
+ export declare function esCorteValido(mensajes: Mensaje[], i: number): boolean;
27
+ /**
28
+ * Indice donde empieza la cola que se conserva.
29
+ *
30
+ * Se apunta a dejar `colaMinima` mensajes recientes y, si ese punto parte un
31
+ * bloque, se retrocede hasta el primer corte limpio (conservar de mas es
32
+ * gratis). Solo si retrocediendo no queda nada que resumir se avanza, que es
33
+ * peor pero sigue siendo valido.
34
+ */
35
+ export declare function corteSeguro(mensajes: Mensaje[], colaMinima: number): number;
36
+ /**
37
+ * Deja un tramo del historial en un estado que la API acepte por si solo.
38
+ *
39
+ * Sirve para el trozo que se manda a resumir: alli las respuestas de
40
+ * herramientas cuya llamada quedo fuera del tramo son basura que provoca un 400,
41
+ * y una llamada cuyas respuestas quedaron fuera, tambien.
42
+ */
43
+ export declare function limpiarHuerfanos(tramo: Mensaje[]): Mensaje[];
44
+ /** Marca que sustituye a una salida de herramienta ya inservible. */
45
+ export declare const SALIDA_DESCARTADA = "[salida descartada al liberar contexto; vuelve a ejecutar la herramienta si necesitas el detalle]";
46
+ /**
47
+ * Vacia las salidas de herramienta mas viejas conservando el mensaje.
48
+ *
49
+ * Es la mitad del historial en cualquier sesion larga (un `view_file` de hace
50
+ * veinte pasos ocupa miles de tokens y ya no dice nada), y borrarlo no cuesta
51
+ * ni una llamada al modelo ni pierde ninguna decision tomada. El mensaje se
52
+ * queda en su sitio con un texto corto: quitarlo dejaria huerfana su llamada.
53
+ */
54
+ export declare function vaciarResultadosAntiguos(mensajes: Mensaje[], conservarUltimos?: number, minimoCaracteres?: number): {
55
+ mensajes: Mensaje[];
56
+ liberados: number;
57
+ };
58
+ /**
59
+ * Tamano aproximado del historial en tokens.
60
+ *
61
+ * No hay tokenizador a mano, asi que se cuenta por caracteres. Sirve para lo
62
+ * unico que se le pide: comparar el historial consigo mismo antes y despues de
63
+ * podarlo. La cifra absoluta buena la da la API en `prompt_tokens`.
64
+ */
65
+ export declare function estimarTokens(mensajes: Mensaje[]): number;
@@ -0,0 +1,134 @@
1
+ /** Un assistant que pide herramientas: obliga a que le sigan sus respuestas. */
2
+ export function pideHerramientas(m) {
3
+ if (!m || m.role !== "assistant")
4
+ return false;
5
+ const tc = m.tool_calls;
6
+ return Array.isArray(tc) && tc.length > 0;
7
+ }
8
+ /** Una respuesta de herramienta: no puede existir sin su llamada delante. */
9
+ export function esResultado(m) {
10
+ return !!m && m.role === "tool";
11
+ }
12
+ /**
13
+ * Un corte en el indice `i` es valido si no parte un bloque de herramientas:
14
+ * ni deja una respuesta sin su llamada delante, ni una llamada sin respuestas
15
+ * detras. El indice 0 nunca se toca: es el prompt de sistema.
16
+ */
17
+ export function esCorteValido(mensajes, i) {
18
+ if (i < 1 || i > mensajes.length)
19
+ return false;
20
+ if (esResultado(mensajes[i]))
21
+ return false;
22
+ if (pideHerramientas(mensajes[i - 1]))
23
+ return false;
24
+ return true;
25
+ }
26
+ /**
27
+ * Indice donde empieza la cola que se conserva.
28
+ *
29
+ * Se apunta a dejar `colaMinima` mensajes recientes y, si ese punto parte un
30
+ * bloque, se retrocede hasta el primer corte limpio (conservar de mas es
31
+ * gratis). Solo si retrocediendo no queda nada que resumir se avanza, que es
32
+ * peor pero sigue siendo valido.
33
+ */
34
+ export function corteSeguro(mensajes, colaMinima) {
35
+ const objetivo = Math.max(1, mensajes.length - Math.max(0, colaMinima));
36
+ for (let i = objetivo; i >= 1; i--) {
37
+ if (esCorteValido(mensajes, i))
38
+ return i;
39
+ }
40
+ for (let i = objetivo + 1; i <= mensajes.length; i++) {
41
+ if (esCorteValido(mensajes, i))
42
+ return i;
43
+ }
44
+ return 1;
45
+ }
46
+ /**
47
+ * Deja un tramo del historial en un estado que la API acepte por si solo.
48
+ *
49
+ * Sirve para el trozo que se manda a resumir: alli las respuestas de
50
+ * herramientas cuya llamada quedo fuera del tramo son basura que provoca un 400,
51
+ * y una llamada cuyas respuestas quedaron fuera, tambien.
52
+ */
53
+ export function limpiarHuerfanos(tramo) {
54
+ const idsRespondidos = new Set();
55
+ for (const m of tramo) {
56
+ if (esResultado(m)) {
57
+ const id = m.tool_call_id;
58
+ if (id)
59
+ idsRespondidos.add(String(id));
60
+ }
61
+ }
62
+ const idsLlamados = new Set();
63
+ const salida = [];
64
+ for (const m of tramo) {
65
+ if (pideHerramientas(m)) {
66
+ const conRespuesta = m.tool_calls.filter((tc) => idsRespondidos.has(String(tc?.id)));
67
+ if (conRespuesta.length === 0) {
68
+ // Sin ninguna respuesta detras no aporta nada y rompe la peticion.
69
+ const texto = m.content;
70
+ if (typeof texto === "string" && texto.trim()) {
71
+ salida.push({ role: "assistant", content: texto });
72
+ }
73
+ continue;
74
+ }
75
+ conRespuesta.forEach((tc) => idsLlamados.add(String(tc.id)));
76
+ salida.push({ ...m, tool_calls: conRespuesta });
77
+ continue;
78
+ }
79
+ if (esResultado(m)) {
80
+ const id = String(m.tool_call_id ?? "");
81
+ if (!idsLlamados.has(id))
82
+ continue;
83
+ salida.push(m);
84
+ continue;
85
+ }
86
+ salida.push(m);
87
+ }
88
+ return salida;
89
+ }
90
+ /** Marca que sustituye a una salida de herramienta ya inservible. */
91
+ export const SALIDA_DESCARTADA = "[salida descartada al liberar contexto; vuelve a ejecutar la herramienta si necesitas el detalle]";
92
+ /**
93
+ * Vacia las salidas de herramienta mas viejas conservando el mensaje.
94
+ *
95
+ * Es la mitad del historial en cualquier sesion larga (un `view_file` de hace
96
+ * veinte pasos ocupa miles de tokens y ya no dice nada), y borrarlo no cuesta
97
+ * ni una llamada al modelo ni pierde ninguna decision tomada. El mensaje se
98
+ * queda en su sitio con un texto corto: quitarlo dejaria huerfana su llamada.
99
+ */
100
+ export function vaciarResultadosAntiguos(mensajes, conservarUltimos = 12, minimoCaracteres = 400) {
101
+ const limite = mensajes.length - Math.max(0, conservarUltimos);
102
+ let liberados = 0;
103
+ const salida = mensajes.map((m, i) => {
104
+ if (i >= limite || !esResultado(m))
105
+ return m;
106
+ const contenido = m.content;
107
+ if (typeof contenido !== "string")
108
+ return m;
109
+ if (contenido.length < minimoCaracteres || contenido === SALIDA_DESCARTADA)
110
+ return m;
111
+ liberados += contenido.length - SALIDA_DESCARTADA.length;
112
+ return { ...m, content: SALIDA_DESCARTADA };
113
+ });
114
+ return { mensajes: salida, liberados };
115
+ }
116
+ /**
117
+ * Tamano aproximado del historial en tokens.
118
+ *
119
+ * No hay tokenizador a mano, asi que se cuenta por caracteres. Sirve para lo
120
+ * unico que se le pide: comparar el historial consigo mismo antes y despues de
121
+ * podarlo. La cifra absoluta buena la da la API en `prompt_tokens`.
122
+ */
123
+ export function estimarTokens(mensajes) {
124
+ let caracteres = 0;
125
+ for (const m of mensajes) {
126
+ try {
127
+ caracteres += JSON.stringify(m)?.length || 0;
128
+ }
129
+ catch {
130
+ caracteres += 0;
131
+ }
132
+ }
133
+ return Math.ceil(caracteres / 4);
134
+ }
@@ -5,5 +5,8 @@ export interface ProjectContext {
5
5
  gitBranch: string | null;
6
6
  projectType: string;
7
7
  availableScripts: string[];
8
+ /** Indice de simbolos del proyecto, ya rankeado y recortado. Ver repoMap.ts. */
9
+ repoMap: string;
8
10
  }
11
+ export declare function olvidarMapa(cwd?: string): void;
9
12
  export declare function getProjectContext(cwd?: string): Promise<ProjectContext>;
@@ -2,6 +2,40 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { execa } from "execa";
4
4
  import { RULES_FILE_NAME } from "../config/constants.js";
5
+ import { construirMapa } from "./repoMap.js";
6
+ /** Tokens que puede ocupar el mapa dentro del prompt de sistema. */
7
+ const PRESUPUESTO_MAPA = 4_000;
8
+ /**
9
+ * El mapa se calcula una vez por directorio.
10
+ *
11
+ * getProjectContext se llama de nuevo en cada cambio de modelo, de modo y de
12
+ * directorio —hasta ocho veces en una sesion corta—, y recorrer el repositorio
13
+ * entero cada vez para obtener exactamente el mismo texto es tiempo regalado.
14
+ */
15
+ const cacheDeMapas = new Map();
16
+ export function olvidarMapa(cwd) {
17
+ if (cwd)
18
+ cacheDeMapas.delete(path.resolve(cwd));
19
+ else
20
+ cacheDeMapas.clear();
21
+ }
22
+ function mapaDe(cwd) {
23
+ const clave = path.resolve(cwd);
24
+ const guardado = cacheDeMapas.get(clave);
25
+ if (guardado !== undefined)
26
+ return guardado;
27
+ let mapa = "";
28
+ try {
29
+ mapa = construirMapa({ cwd: clave, presupuesto: PRESUPUESTO_MAPA });
30
+ }
31
+ catch {
32
+ // Un proyecto raro no puede impedir que arranque el agente: sin mapa se
33
+ // trabaja igual, solo que a ciegas, que es como se trabajaba antes.
34
+ mapa = "";
35
+ }
36
+ cacheDeMapas.set(clave, mapa);
37
+ return mapa;
38
+ }
5
39
  export async function getProjectContext(cwd = process.cwd()) {
6
40
  let rulesContent = null;
7
41
  // 1. Look for CHOCOLATITO.md or CLAUDE.md
@@ -119,5 +153,6 @@ export async function getProjectContext(cwd = process.cwd()) {
119
153
  gitBranch,
120
154
  projectType,
121
155
  availableScripts,
156
+ repoMap: mapaDe(cwd),
122
157
  };
123
158
  }
@@ -1,10 +1,18 @@
1
1
  import OpenAI from "openai";
2
2
  import { TokenTracker } from "./tracker.js";
3
3
  import { EspectroModel } from "../config/constants.js";
4
+ import { ResultadoTarea } from "./result.js";
4
5
  import { PermissionManager } from "../config/permissions.js";
5
6
  import { HookManager } from "../hooks/manager.js";
6
7
  export declare class AgentLoop {
7
8
  private client;
9
+ /**
10
+ * Cliente aparte para resumir el historial. No es cosmetico: la cabecera
11
+ * X-Chocolatito-Route es lo que le permite al proxy saber en que se le va la
12
+ * cuota al usuario, y con un unico cliente las compactaciones —que son las
13
+ * llamadas mas caras que hace el agente— llegaban etiquetadas como "loop".
14
+ */
15
+ private compactClient;
8
16
  private currentEspectro;
9
17
  messages: OpenAI.Chat.ChatCompletionMessageParam[];
10
18
  tracker: TokenTracker;
@@ -14,12 +22,93 @@ export declare class AgentLoop {
14
22
  private apiKey;
15
23
  private baseURL;
16
24
  private maxIterations;
25
+ /**
26
+ * Vigila que el agente no se quede dando vueltas. Vive en la instancia y no
27
+ * en runTask porque el estado tiene que sobrevivir a las iteraciones del
28
+ * while; se reinicia en cada turno nuevo.
29
+ */
30
+ private detectorBucle;
31
+ /**
32
+ * Archivos escritos en el turno. Solo sirve para saber si hay algo que
33
+ * verificar al cerrarlo: si el turno no toco el disco, no hay nada que
34
+ * comprobar y correr el compilador seria tiempo del usuario tirado.
35
+ */
36
+ private archivosTocados;
17
37
  constructor(apiKey: string, baseURL: string, initialModel: EspectroModel, systemPrompt: string);
18
38
  setEspectroModel(model: EspectroModel, updatedSystemPrompt?: string): void;
19
39
  getEspectroModel(): EspectroModel;
20
40
  clearHistory(systemPrompt: string): void;
21
41
  compactHistory(systemPrompt: string): Promise<string>;
42
+ /**
43
+ * Tokens que OCUPA el historial ahora mismo, no los que se han gastado en el
44
+ * turno. Son dos numeros distintos y confundirlos era el fallo de abajo.
45
+ */
22
46
  lastTurnTokens: number;
23
- autoCompactIfNeeded(): Promise<boolean>;
24
- runTask(userPrompt: string): Promise<void>;
47
+ /**
48
+ * Compactacion automatica del contexto.
49
+ *
50
+ * Habia tres cosas mal, y cada una se comia el contexto por su lado:
51
+ *
52
+ * 1. El corte era por posicion fija: slice(1, -4) para lo que se resumia y
53
+ * slice(-4) para lo que se conservaba. Un turno que acaba en un bloque de
54
+ * cuatro herramientas deja la cola empezando por un mensaje "tool" cuya
55
+ * llamada se ha quedado en la mitad resumida. La API contesta 400 y el
56
+ * catch mudo lo tapaba: la sesion se rompia sin motivo visible, y siempre
57
+ * en las sesiones largas, que son las que llevan mas trabajo encima. El
58
+ * corte lo decide ahora la forma del historial, nunca una posicion.
59
+ *
60
+ * 2. El umbral solo se miraba al empezar el turno y sobre el dato del turno
61
+ * ANTERIOR. Un turno de veinte herramientas puede doblar el contexto sin
62
+ * que nadie lo compruebe, y el limite se choca a mitad de camino.
63
+ *
64
+ * 3. Resumir cuesta una llamada al modelo y pierde detalle. Vaciar las
65
+ * salidas de herramienta viejas no cuesta nada y casi siempre basta, asi
66
+ * que va primero.
67
+ */
68
+ autoCompactIfNeeded(tokensActuales?: number): Promise<boolean>;
69
+ /**
70
+ * Verificacion del proyecto al cerrar un turno que escribio en disco.
71
+ *
72
+ * Devuelve el mensaje que hay que reinyectarle al modelo, o null si no hay
73
+ * nada que corregir (o si no toca verificar). El tope de vueltas evita el
74
+ * otro extremo: un agente peleandose solo con el compilador durante veinte
75
+ * iteraciones mientras el usuario mira.
76
+ */
77
+ private verificarTrasEditar;
78
+ /**
79
+ * Comprueba si esta llamada cierra un bucle y, si lo cierra, decide con el
80
+ * usuario que hacer. Devuelve el texto que se le pasa al modelo: el resultado
81
+ * tal cual, o la explicacion del atasco.
82
+ *
83
+ * Se llama DESPUES de ejecutar, no antes, porque la firma incluye el
84
+ * resultado: sin el, un sondeo legitimo (releer un log que cambia) parece un
85
+ * bucle. La tercera llamada repetida ya se ha pagado; lo que se evita son la
86
+ * cuarta y las veintiseis siguientes.
87
+ */
88
+ /** Apunta que este turno ha escrito en disco, para verificarlo al cerrarlo. */
89
+ private apuntarEscritura;
90
+ private vigilarBucle;
91
+ /**
92
+ * Ejecuta un turno completo y CUENTA como acabo.
93
+ *
94
+ * Antes devolvia void: el motivo del corte se imprimia por pantalla y ahi se
95
+ * moria. Quien llamaba no podia distinguir "terminado" de "cuota agotada al
96
+ * primer intento", que es justo lo que necesita el modo no interactivo para
97
+ * salir con un codigo honesto.
98
+ */
99
+ runTask(userPrompt: string): Promise<ResultadoTarea>;
100
+ /**
101
+ * Guarda lo que el usuario acaba de aprobar con "no preguntar mas".
102
+ *
103
+ * Aqui se hacia command.split(" ")[0] y se guardaba solo el binario. Aprobar
104
+ * un "git status" dejaba el patron "git" escrito en .chocolatito/settings.json
105
+ * y, como el allow autoriza por prefijo, a partir de ese momento pasaban sin
106
+ * dialogo "git push --force" y "git reset --hard", en esa sesion y en todas
107
+ * las siguientes. Un solo si de mas y el proyecto se queda sin barrera.
108
+ *
109
+ * Ahora se guarda la firma del comando (binario mas subcomando) y, si el
110
+ * comando es de los destructivos, no se guarda nada: eso se pregunta siempre,
111
+ * y decirlo en voz alta es mejor que escribir un patron que no serviria.
112
+ */
113
+ private recordarPermiso;
25
114
  }