chocolatito-code 1.3.0 → 1.3.2
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/dist/agent/loop.js +49 -13
- package/dist/config/quota.d.ts +11 -0
- package/dist/config/quota.js +18 -0
- package/dist/index.js +76 -38
- package/dist/prompts/systemPrompt.js +186 -150
- package/dist/ui/banner.d.ts +31 -0
- package/dist/ui/banner.js +45 -0
- package/dist/ui/comandos.d.ts +33 -0
- package/dist/ui/comandos.js +49 -0
- package/dist/ui/informeDeUso.d.ts +17 -0
- package/dist/ui/informeDeUso.js +81 -0
- package/dist/ui/marco.d.ts +35 -0
- package/dist/ui/marco.js +193 -0
- package/dist/ui/modes.d.ts +9 -1
- package/dist/ui/modes.js +11 -3
- package/dist/ui/motorActual.d.ts +28 -0
- package/dist/ui/motorActual.js +34 -0
- package/dist/ui/prompt.d.ts +4 -41
- package/dist/ui/prompt.js +42 -165
- package/dist/ui/renderer.d.ts +13 -0
- package/dist/ui/renderer.js +41 -12
- package/dist/ui/selector.d.ts +37 -0
- package/dist/ui/selector.js +81 -0
- package/dist/ui/spinner.d.ts +33 -5
- package/dist/ui/spinner.js +70 -44
- package/extension/manifest.json +60 -60
- package/package.json +67 -67
package/dist/agent/loop.js
CHANGED
|
@@ -3,7 +3,7 @@ import process from "node:process";
|
|
|
3
3
|
import { getAllTools } from "../tools/definitions.js";
|
|
4
4
|
import { executeToolCall } from "../tools/runner.js";
|
|
5
5
|
import { TokenTracker } from "./tracker.js";
|
|
6
|
-
import { renderToolStart, renderToolSuccess, renderToolError, renderDiff, renderFooter, renderError, } from "../ui/renderer.js";
|
|
6
|
+
import { renderToolStart, renderToolSuccess, renderToolError, renderDiff, renderFooter, renderError, formatToolName, } from "../ui/renderer.js";
|
|
7
7
|
import { CONTEXT_WINDOW, MAX_OUTPUT_TOKENS, COMPACT_THRESHOLD_TOKENS, } from "../config/constants.js";
|
|
8
8
|
import { withRetry, isContextOverflow, esDemasiadasEnCurso } from "./retry.js";
|
|
9
9
|
import { canRunInParallel } from "../tools/safety.js";
|
|
@@ -14,7 +14,7 @@ import { checkQuota, recordUsage, mensajeDeCorte, quotaBadge } from "../config/q
|
|
|
14
14
|
import { ReasoningStream } from "../ui/reasoningStream.js";
|
|
15
15
|
import { PermissionManager, commandSignature, isDangerousCommand } from "../config/permissions.js";
|
|
16
16
|
import { askToolPermission } from "../ui/permissionPrompt.js";
|
|
17
|
-
import { DynamicSpinner } from "../ui/spinner.js";
|
|
17
|
+
import { DynamicSpinner, conSpinner } from "../ui/spinner.js";
|
|
18
18
|
import { globalUndoManager } from "./undoManager.js";
|
|
19
19
|
import { salida } from "./salidaDelAgente.js";
|
|
20
20
|
import { InterruptWatcher, isAbortError } from "../ui/interrupt.js";
|
|
@@ -727,7 +727,20 @@ export class AgentLoop {
|
|
|
727
727
|
// huecos de la licencia durante minutos, y los cortes vienen en rachas:
|
|
728
728
|
// reintentar es justo como se pasa de perder un hueco a perder los cuatro.
|
|
729
729
|
// Mas vale que el usuario reescriba una linea.
|
|
730
|
-
|
|
730
|
+
//
|
|
731
|
+
// NO SE EXIGE QUE HAYA LLEGADO TEXTO. La primera version pedia contenido o
|
|
732
|
+
// herramientas, y se le escapaba el caso mas comun: el corte que pasa
|
|
733
|
+
// DESPUES del razonamiento y ANTES de la primera palabra. Eso caia en la
|
|
734
|
+
// rama de "respuesta vacia", que si reintenta, y volvia a gastar huecos:
|
|
735
|
+
//
|
|
736
|
+
// 💭 penso 2s
|
|
737
|
+
// ⚠ El motor no devolvio nada. Reintentando una vez…
|
|
738
|
+
//
|
|
739
|
+
// Sin marca de final, el stream se corto. Da igual cuanto habia llegado.
|
|
740
|
+
// Una respuesta vacia DE VERDAD trae su finish_reason: el motor cerro el
|
|
741
|
+
// turno en regla y simplemente no dijo nada, y eso si merece un reintento
|
|
742
|
+
// porque la conexion estaba bien.
|
|
743
|
+
if (!llegoElFinal) {
|
|
731
744
|
watcher.stop();
|
|
732
745
|
// Lo que llego se guarda, pero MARCADO. Sin la marca, en el turno
|
|
733
746
|
// siguiente el modelo lee su propia frase a medias como algo que decidio
|
|
@@ -738,11 +751,20 @@ export class AgentLoop {
|
|
|
738
751
|
content: `${content}\n\n[Aqui se corto: la conexion con el motor se cerro a mitad de la respuesta.]`,
|
|
739
752
|
});
|
|
740
753
|
}
|
|
754
|
+
// Se dice HASTA DONDE llego, y no por cortesia: es el unico dato que
|
|
755
|
+
// distingue un corte antes de empezar de uno a mitad de frase, y en un
|
|
756
|
+
// pegado del usuario es lo unico con lo que se puede diagnosticar.
|
|
741
757
|
// Las llamadas a herramienta de un stream cortado NO se ejecutan: sus
|
|
742
758
|
// argumentos estan tan a medias como el texto.
|
|
743
|
-
const
|
|
744
|
-
"Lo que llego se queda arriba.
|
|
745
|
-
|
|
759
|
+
const hastaDonde = content.trim()
|
|
760
|
+
? "Lo que llego se queda arriba."
|
|
761
|
+
: reasoning.trim()
|
|
762
|
+
? "Se corto antes de escribir nada: solo llego el razonamiento."
|
|
763
|
+
: "Se corto antes de que llegara nada.";
|
|
764
|
+
const aviso = "La respuesta se corto: la conexion con el motor se cerro sin decir por que (no llego " +
|
|
765
|
+
`ni el motivo de fin ni el recuento del gasto). ${hastaDonde} ` +
|
|
766
|
+
"No lo reintento solo porque cada intento ocupa uno de los cuatro huecos de tu licencia " +
|
|
767
|
+
"durante varios minutos; vuelve a pedirmelo y sigo.";
|
|
746
768
|
salida.linea(chalk.yellow(`\n ⚠ ${aviso}\n`));
|
|
747
769
|
return parada("error-motor", aviso);
|
|
748
770
|
}
|
|
@@ -845,10 +867,14 @@ export class AgentLoop {
|
|
|
845
867
|
for (const { tc, args } of autorizadas) {
|
|
846
868
|
renderToolStart(tc.function.name, getToolSummary(tc.function.name, args));
|
|
847
869
|
}
|
|
848
|
-
|
|
870
|
+
// Un solo spinner para el lote entero: uno por herramienta se pisarian
|
|
871
|
+
// entre ellos y el pie parpadearia tantas veces como llamadas haya.
|
|
872
|
+
const settled = await conSpinner(autorizadas.length === 1
|
|
873
|
+
? formatToolName(autorizadas[0].tc.function.name)
|
|
874
|
+
: `${autorizadas.length} herramientas a la vez`, () => Promise.all(autorizadas.map(async ({ tc, args, pos }) => {
|
|
849
875
|
const r = await executeToolCall(tc.function.name, args, this.currentCwd, this.apiKey, this.currentEspectro.underlyingModel);
|
|
850
876
|
return { tc, args, pos, result: r.result };
|
|
851
|
-
}));
|
|
877
|
+
})));
|
|
852
878
|
for (const { tc, args, pos, result } of settled) {
|
|
853
879
|
renderToolSuccess(tc.function.name, result);
|
|
854
880
|
const aviso = await notificarPostHerramienta(tc.function.name, args, result, this.currentCwd);
|
|
@@ -969,7 +995,10 @@ export class AgentLoop {
|
|
|
969
995
|
}
|
|
970
996
|
const summary = getToolSummary(fnName, fnArgs);
|
|
971
997
|
renderToolStart(fnName, summary);
|
|
972
|
-
|
|
998
|
+
// El pie se queda puesto mientras dure la herramienta. Un run_command
|
|
999
|
+
// de veinte segundos dejaba la pantalla muda: ni cronometro, ni forma
|
|
1000
|
+
// de saber que seguia vivo, ni donde escribir para encolar algo.
|
|
1001
|
+
const { result, diffText, newCwd } = await conSpinner(formatToolName(fnName), () => executeToolCall(fnName, fnArgs, this.currentCwd, this.apiKey, this.currentEspectro.underlyingModel));
|
|
973
1002
|
if (newCwd) {
|
|
974
1003
|
this.currentCwd = newCwd;
|
|
975
1004
|
}
|
|
@@ -1027,6 +1056,12 @@ export class AgentLoop {
|
|
|
1027
1056
|
// codigo no distingue "el modelo termino" de "no vino nada". Sin
|
|
1028
1057
|
// herramientas y sin texto, el bucle daba el turno por hecho, imprimia el
|
|
1029
1058
|
// pie y volvia al prompt.
|
|
1059
|
+
//
|
|
1060
|
+
// Aqui solo se llega con la marca de final puesta: un stream cortado sale
|
|
1061
|
+
// mucho antes, por su propia rama. Asi que esto es lo que parece -el motor
|
|
1062
|
+
// cerro el turno en regla y no dijo nada- y no un corte disfrazado. Esa
|
|
1063
|
+
// separacion es la que hace que reintentar aqui sea razonable: la conexion
|
|
1064
|
+
// estaba bien, y por eso el reintento no es tirar un hueco a la basura.
|
|
1030
1065
|
if (!content.trim()) {
|
|
1031
1066
|
respuestasVacias++;
|
|
1032
1067
|
// Se reintenta UNA vez, no mas, y aqui la moderacion importa: cada
|
|
@@ -1034,13 +1069,14 @@ export class AgentLoop {
|
|
|
1034
1069
|
// cuatro. Insistir con un motor que no contesta es la forma mas rapida
|
|
1035
1070
|
// de quedarse sin ninguno.
|
|
1036
1071
|
if (respuestasVacias > MAX_RESPUESTAS_VACIAS) {
|
|
1037
|
-
const aviso = `El motor
|
|
1038
|
-
"No es
|
|
1039
|
-
"Espera un momento y reintenta, o
|
|
1072
|
+
const aviso = `El motor cerro el turno en regla ${respuestasVacias} veces seguidas sin una sola ` +
|
|
1073
|
+
"palabra. No es un corte de conexion -eso se avisa aparte y con otras palabras-: la " +
|
|
1074
|
+
"peticion llego y volvio entera, solo que vacia. Espera un momento y reintenta, o " +
|
|
1075
|
+
"/clear si venias de una conversacion larga.";
|
|
1040
1076
|
salida.linea(chalk.yellow(`\n ⚠ ${aviso}\n`));
|
|
1041
1077
|
return parada("error-motor", aviso);
|
|
1042
1078
|
}
|
|
1043
|
-
salida.linea(chalk.gray("\n ⚠ El motor
|
|
1079
|
+
salida.linea(chalk.gray("\n ⚠ El motor cerro el turno sin decir nada. Reintentando una vez…\n"));
|
|
1044
1080
|
this.messages.push({
|
|
1045
1081
|
role: "user",
|
|
1046
1082
|
content: "Tu respuesta anterior llego vacia: ni texto ni llamadas a herramienta. " +
|
package/dist/config/quota.d.ts
CHANGED
|
@@ -50,6 +50,17 @@ export interface QuotaStatus {
|
|
|
50
50
|
config: QuotaConfig;
|
|
51
51
|
}
|
|
52
52
|
export declare function checkQuota(ahora?: number): QuotaStatus;
|
|
53
|
+
/**
|
|
54
|
+
* Cuando la ventana corta se vacia DEL TODO, si no se gasta nada mas.
|
|
55
|
+
*
|
|
56
|
+
* `seLiberaEn` dice cuando caduca el gasto mas viejo, que es lo que hace falta
|
|
57
|
+
* para saber cuando se puede volver a trabajar tras un corte. Para ensenar el
|
|
58
|
+
* consumo es otra pregunta: cuando vuelve a cero. Y como la ventana es
|
|
59
|
+
* deslizante, eso es cuando caduca el gasto mas RECIENTE.
|
|
60
|
+
*
|
|
61
|
+
* `null` si no hay nada dentro de la ventana: no hay nada que renovar.
|
|
62
|
+
*/
|
|
63
|
+
export declare function ventanaVaciaEn(ahora?: number): Date | null;
|
|
53
64
|
/** Cuanto falta para la hora indicada, en lenguaje llano. */
|
|
54
65
|
export declare function faltaPara(fecha: Date, ahora?: number): string;
|
|
55
66
|
/** Mensaje completo para cuando se agota la cuota. */
|
package/dist/config/quota.js
CHANGED
|
@@ -148,6 +148,24 @@ export function checkQuota(ahora = Date.now()) {
|
|
|
148
148
|
}
|
|
149
149
|
return { allowed: true, spentWindowUsd, spentWeekUsd, config };
|
|
150
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Cuando la ventana corta se vacia DEL TODO, si no se gasta nada mas.
|
|
153
|
+
*
|
|
154
|
+
* `seLiberaEn` dice cuando caduca el gasto mas viejo, que es lo que hace falta
|
|
155
|
+
* para saber cuando se puede volver a trabajar tras un corte. Para ensenar el
|
|
156
|
+
* consumo es otra pregunta: cuando vuelve a cero. Y como la ventana es
|
|
157
|
+
* deslizante, eso es cuando caduca el gasto mas RECIENTE.
|
|
158
|
+
*
|
|
159
|
+
* `null` si no hay nada dentro de la ventana: no hay nada que renovar.
|
|
160
|
+
*/
|
|
161
|
+
export function ventanaVaciaEn(ahora = Date.now()) {
|
|
162
|
+
const ventanaMs = getQuotaConfig().windowHours * 60 * 60 * 1000;
|
|
163
|
+
const dentro = load().filter((e) => e.t >= ahora - ventanaMs);
|
|
164
|
+
if (dentro.length === 0)
|
|
165
|
+
return null;
|
|
166
|
+
const masReciente = dentro.reduce((max, e) => (e.t > max ? e.t : max), dentro[0].t);
|
|
167
|
+
return new Date(masReciente + ventanaMs);
|
|
168
|
+
}
|
|
151
169
|
/** Cuanto falta para la hora indicada, en lenguaje llano. */
|
|
152
170
|
export function faltaPara(fecha, ahora = Date.now()) {
|
|
153
171
|
const ms = fecha.getTime() - ahora;
|
package/dist/index.js
CHANGED
|
@@ -5,13 +5,16 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import { ensureApiKey } from "./config/env.js";
|
|
7
7
|
import { ensureLicense, licenseStatus, deactivate, getStoredLicense } from "./config/license.js";
|
|
8
|
-
import { isManagedMode } from "./config/quota.js";
|
|
8
|
+
import { isManagedMode, checkQuota, ventanaVaciaEn } from "./config/quota.js";
|
|
9
|
+
import { construirInformeDeUso } from "./ui/informeDeUso.js";
|
|
9
10
|
import { getEngineBaseUrl, DEFAULT_ESPECTRO, ESPECTRO_MODELS, RULES_FILE_NAME } from "./config/constants.js";
|
|
10
11
|
import { getProjectContext } from "./agent/context.js";
|
|
11
12
|
import { buildSystemPrompt } from "./prompts/systemPrompt.js";
|
|
12
13
|
import { AgentLoop } from "./agent/loop.js";
|
|
13
14
|
import { codigoDeSalida, EXIT_OK, EXIT_FALLO, EXIT_USO, EXIT_LICENCIA } from "./agent/result.js";
|
|
14
15
|
import { showBanner, animateStartupBanner, showHelp, showEffortMenu } from "./ui/banner.js";
|
|
16
|
+
import { fijarMotor } from "./ui/motorActual.js";
|
|
17
|
+
import { elegirDeLista } from "./ui/selector.js";
|
|
15
18
|
import { askInteractivePrompt, SLASH_COMMANDS } from "./ui/prompt.js";
|
|
16
19
|
import { changeDir } from "./tools/changeDir.js";
|
|
17
20
|
import { gitDiff, gitCommit } from "./tools/gitTools.js";
|
|
@@ -184,8 +187,8 @@ async function main() {
|
|
|
184
187
|
// errores raros dentro de una dependencia. Ver config/nodeVersion.ts.
|
|
185
188
|
const nodeViejo = comprobarNode();
|
|
186
189
|
if (nodeViejo) {
|
|
187
|
-
console.error(chalk.red(`
|
|
188
|
-
${nodeViejo}
|
|
190
|
+
console.error(chalk.red(`
|
|
191
|
+
${nodeViejo}
|
|
189
192
|
`));
|
|
190
193
|
process.exit(EXIT_USO);
|
|
191
194
|
}
|
|
@@ -232,6 +235,9 @@ ${nodeViejo}
|
|
|
232
235
|
const sessionManager = new SessionManager();
|
|
233
236
|
const sessionId = `session_${Date.now()}`;
|
|
234
237
|
let currentModel = DEFAULT_ESPECTRO;
|
|
238
|
+
// La barra de estado lo lee de aqui. Fijarlo al arrancar y no solo al
|
|
239
|
+
// cambiarlo: si no, hasta el primer /effort la barra no dice nada.
|
|
240
|
+
fijarMotor(currentModel.name);
|
|
235
241
|
let isPlanMode = false;
|
|
236
242
|
let systemPrompt = buildSystemPrompt(context, currentModel, skillManager, memoryManager, isPlanMode);
|
|
237
243
|
const agent = new AgentLoop(apiKey, getEngineBaseUrl(), currentModel, systemPrompt);
|
|
@@ -399,15 +405,15 @@ ${nodeViejo}
|
|
|
399
405
|
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
400
406
|
.map((m) => `### 👤 ${m.role === "user" ? "Usuario" : "Chocolatito"}\n\n${typeof m.content === "string" ? m.content : JSON.stringify(m.content, null, 2)}\n`)
|
|
401
407
|
.join("\n---\n\n");
|
|
402
|
-
const mdContent = `# 🦊 Bitácora de Sesión — Chocolatito Code
|
|
403
|
-
**Fecha:** ${new Date().toLocaleString()}
|
|
404
|
-
**Directorio:** \`${agent.currentCwd}\`
|
|
405
|
-
**Motor:** ${currentModel.name}
|
|
406
|
-
**Inversión Total:** ${agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel).formattedCost}
|
|
407
|
-
|
|
408
|
-
---
|
|
409
|
-
|
|
410
|
-
${sessionData}
|
|
408
|
+
const mdContent = `# 🦊 Bitácora de Sesión — Chocolatito Code
|
|
409
|
+
**Fecha:** ${new Date().toLocaleString()}
|
|
410
|
+
**Directorio:** \`${agent.currentCwd}\`
|
|
411
|
+
**Motor:** ${currentModel.name}
|
|
412
|
+
**Inversión Total:** ${agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel).formattedCost}
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
${sessionData}
|
|
411
417
|
`;
|
|
412
418
|
fs.writeFileSync(exportPath, mdContent, "utf-8");
|
|
413
419
|
console.log(chalk.green(`\n✔ Bitácora exportada exitosamente en: ${chalk.bold.white(exportPath)}\n`));
|
|
@@ -428,6 +434,7 @@ ${sessionData}
|
|
|
428
434
|
const restauracion = prepararRestauracion(loaded, { cwd: agent.currentCwd, modelId: currentModel.id });
|
|
429
435
|
agent.currentCwd = restauracion.cwd;
|
|
430
436
|
currentModel = ESPECTRO_MODELS[restauracion.modelId] || currentModel;
|
|
437
|
+
fijarMotor(currentModel.name);
|
|
431
438
|
const refreshedContext = await getProjectContext(agent.currentCwd);
|
|
432
439
|
systemPrompt = buildSystemPrompt(refreshedContext, currentModel, skillManager, memoryManager, isPlanMode);
|
|
433
440
|
agent.messages = [{ role: "system", content: systemPrompt }, ...restauracion.conversacion];
|
|
@@ -500,12 +507,12 @@ ${sessionData}
|
|
|
500
507
|
}
|
|
501
508
|
console.log(chalk.bold.hex("#A855F7")("\n🎯 INICIANDO MODO META AUTÓNOMA (GOAL MODE):"));
|
|
502
509
|
console.log(chalk.gray(` Objetivo: "${goalText}"\n`));
|
|
503
|
-
const autonomousPrompt = `[MODO META AUTÓNOMA ACTIVADO]
|
|
504
|
-
Tu objetivo principal es: "${goalText}"
|
|
505
|
-
Directivas de ejecución:
|
|
506
|
-
1. Formula un plan de ejecución mental paso a paso.
|
|
507
|
-
2. Ejecuta cada paso utilizando las herramientas correspondientes (creación, edición, ejecución, verificación).
|
|
508
|
-
3. Si algo falla o hay errores, autocorregir inmediatamente y volver a probar.
|
|
510
|
+
const autonomousPrompt = `[MODO META AUTÓNOMA ACTIVADO]
|
|
511
|
+
Tu objetivo principal es: "${goalText}"
|
|
512
|
+
Directivas de ejecución:
|
|
513
|
+
1. Formula un plan de ejecución mental paso a paso.
|
|
514
|
+
2. Ejecuta cada paso utilizando las herramientas correspondientes (creación, edición, ejecución, verificación).
|
|
515
|
+
3. Si algo falla o hay errores, autocorregir inmediatamente y volver a probar.
|
|
509
516
|
4. No te detengas hasta verificar que el objetivo esté 100% completado con éxito.`;
|
|
510
517
|
renderUserPrompt(`/goal ${goalText}`);
|
|
511
518
|
await agent.runTask(autonomousPrompt);
|
|
@@ -559,32 +566,63 @@ Directivas de ejecución:
|
|
|
559
566
|
}
|
|
560
567
|
continue;
|
|
561
568
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
569
|
+
// "/cost" sigue funcionando sin anunciarse: el nombre viejo esta en la
|
|
570
|
+
// memoria de los dedos de quien ya lo usaba, y quitarlo de golpe solo
|
|
571
|
+
// ensena un "comando desconocido" a quien no ha hecho nada mal.
|
|
572
|
+
if (trimmed === "/uso" || trimmed === "/cost") {
|
|
573
|
+
// Lo que se ensena es la CUENTA, no la tarifa: quien paga una suscripcion
|
|
574
|
+
// plana no puede hacer nada con "$0.00508", y lo unico que necesita saber
|
|
575
|
+
// es cuanto le queda antes de que esto le pare. Con clave propia es al
|
|
576
|
+
// reves -el dinero es suyo- y el informe lo tiene en cuenta.
|
|
577
|
+
const { formattedCost } = agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel);
|
|
578
|
+
console.log(construirInformeDeUso(checkQuota(), {
|
|
579
|
+
motor: currentModel.name,
|
|
580
|
+
tokensEntrada: agent.tracker.totalPromptTokens,
|
|
581
|
+
tokensSalida: agent.tracker.totalCompletionTokens,
|
|
582
|
+
tokensEnCache: agent.tracker.cachedPromptTokens,
|
|
583
|
+
costeFormateado: formattedCost,
|
|
584
|
+
}, ventanaVaciaEn()));
|
|
570
585
|
continue;
|
|
571
586
|
}
|
|
572
587
|
if (trimmed.startsWith("/effort") || trimmed.startsWith("/espectro") || trimmed === "/model") {
|
|
573
588
|
const parts = trimmed.split(/\s+/);
|
|
574
|
-
|
|
589
|
+
let targetId = parts[1]?.trim();
|
|
590
|
+
// Sin argumento se ABRE UN MENU, no se imprime una lista.
|
|
591
|
+
//
|
|
592
|
+
// Antes se volcaban las cuatro opciones con console.log y una frase al
|
|
593
|
+
// final que decia "selecciona con /effort 2.0". En pantalla se veia una
|
|
594
|
+
// lista, la gente pulsaba las flechas -que es lo unico razonable- y no
|
|
595
|
+
// pasaba nada.
|
|
575
596
|
if (!targetId || !ESPECTRO_MODELS[targetId]) {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
597
|
+
const opciones = Object.values(ESPECTRO_MODELS).map((m) => ({
|
|
598
|
+
id: m.id,
|
|
599
|
+
titulo: `${m.name} ${m.id === currentModel.id ? "· activo" : ""}`.trimEnd(),
|
|
600
|
+
detalle: `Esfuerzo ${m.effortLevel} — ${m.description}`,
|
|
601
|
+
}));
|
|
602
|
+
const indiceActual = Object.values(ESPECTRO_MODELS).findIndex((m) => m.id === currentModel.id);
|
|
603
|
+
const elegido = await elegirDeLista("POTENCIA DEL MOTOR — CHOCOLATITO CODE", opciones, Math.max(0, indiceActual));
|
|
604
|
+
// Sin terminal el menu no puede abrirse: ahi se cae a la lista de
|
|
605
|
+
// siempre, que al menos dice como elegir escribiendolo.
|
|
606
|
+
if (elegido === null) {
|
|
607
|
+
if (!process.stdin.isTTY)
|
|
608
|
+
showEffortMenu(currentModel.id);
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
targetId = elegido;
|
|
587
612
|
}
|
|
613
|
+
currentModel = ESPECTRO_MODELS[targetId];
|
|
614
|
+
// Sin esto la barra de abajo sigue anunciando el motor anterior.
|
|
615
|
+
fijarMotor(currentModel.name);
|
|
616
|
+
const refreshedContext = await getProjectContext(agent.currentCwd);
|
|
617
|
+
const updatedPrompt = buildSystemPrompt(refreshedContext, currentModel, skillManager, memoryManager, isPlanMode);
|
|
618
|
+
agent.setEspectroModel(currentModel, updatedPrompt);
|
|
619
|
+
console.log(chalk.green(`
|
|
620
|
+
✔ Nivel actualizado a `) +
|
|
621
|
+
chalk.bold.hex("#A855F7")(currentModel.name) +
|
|
622
|
+
chalk.gray(` (${currentModel.effortLevel})`) +
|
|
623
|
+
`
|
|
624
|
+
${chalk.italic.gray(currentModel.description)}
|
|
625
|
+
`);
|
|
588
626
|
continue;
|
|
589
627
|
}
|
|
590
628
|
if (trimmed === "/clear") {
|