chocolatito-code 1.6.7 → 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 +99 -6
- 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/marco.d.ts +1 -0
- package/dist/ui/marco.js +4 -2
- package/dist/ui/permissionPrompt.d.ts +9 -0
- package/dist/ui/permissionPrompt.js +30 -0
- package/dist/ui/pieFijo.d.ts +1 -1
- package/dist/ui/pieFijo.js +1 -1
- package/dist/ui/renderer.js +8 -0
- package/extension/background.js +80 -0
- package/extension/content.js +106 -0
- package/extension/manifest.json +61 -60
- package/package.json +3 -2
package/dist/tools/runner.js
CHANGED
|
@@ -25,6 +25,7 @@ import { globalUndoManager } from "../agent/undoManager.js";
|
|
|
25
25
|
import { validateFileSyntax } from "../agent/syntaxValidator.js";
|
|
26
26
|
import { fileTracker } from "../agent/fileTracker.js";
|
|
27
27
|
import { mcpManager } from "../mcp/manager.js";
|
|
28
|
+
import { taskManager } from "./backgroundTask.js";
|
|
28
29
|
import fs from "node:fs";
|
|
29
30
|
import path from "node:path";
|
|
30
31
|
/**
|
|
@@ -82,10 +83,12 @@ export async function executeToolCall(name, args, cwd = process.cwd(), apiKey, m
|
|
|
82
83
|
break;
|
|
83
84
|
}
|
|
84
85
|
case "save_memory":
|
|
85
|
-
|
|
86
|
+
memoryManager.setCwd(cwd);
|
|
87
|
+
rawResult = memoryManager.saveMemory(args.topic, args.fact, args.scope);
|
|
86
88
|
break;
|
|
87
89
|
case "read_memory":
|
|
88
|
-
|
|
90
|
+
memoryManager.setCwd(cwd);
|
|
91
|
+
rawResult = memoryManager.readMemory(args.topic, args.scope);
|
|
89
92
|
break;
|
|
90
93
|
case "spawn_agent": {
|
|
91
94
|
if (!apiKey) {
|
|
@@ -201,6 +204,37 @@ export async function executeToolCall(name, args, cwd = process.cwd(), apiKey, m
|
|
|
201
204
|
case "run_command":
|
|
202
205
|
rawResult = await runCommand(args.command, cwd, args.timeout);
|
|
203
206
|
break;
|
|
207
|
+
case "start_background_task": {
|
|
208
|
+
const res = await taskManager.startTask(args.command, args.cwd || cwd, args.description);
|
|
209
|
+
rawResult = `Tarea iniciada en segundo plano:\n- ID: ${res.taskId}\n- PID: ${res.pid}\n- Estado: ${res.status}\n- Log: ${res.logPath}`;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
case "read_task_output": {
|
|
213
|
+
const res = taskManager.readTaskOutput(args.taskId, args.lines, args.offset);
|
|
214
|
+
if (res.status === "not_found") {
|
|
215
|
+
rawResult = res.output;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
rawResult = `[Tarea ${res.taskId}] Estado: ${res.status} (Exit Code: ${res.exitCode !== null ? res.exitCode : "N/A"})\nTotal de líneas: ${res.totalLines}\n--- Salida ---\n${res.output || "(sin salida aún)"}`;
|
|
219
|
+
}
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
case "list_background_tasks": {
|
|
223
|
+
const tasks = taskManager.listTasks();
|
|
224
|
+
if (tasks.length === 0) {
|
|
225
|
+
rawResult = "No hay tareas en segundo plano registradas.";
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
const lines = tasks.map((t) => `- [${t.taskId}] PID ${t.pid} | ${t.status.toUpperCase()} | Ejecución: ${t.runtimeSeconds}s | Comando: "${t.command}"${t.description ? ` (${t.description})` : ""}${t.exitCode !== null ? ` | Exit code: ${t.exitCode}` : ""}`);
|
|
229
|
+
rawResult = `Tareas en segundo plano (${tasks.length}):\n${lines.join("\n")}`;
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case "stop_background_task": {
|
|
234
|
+
const res = await taskManager.stopTask(args.taskId, args.force);
|
|
235
|
+
rawResult = res.message;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
204
238
|
case "list_dir":
|
|
205
239
|
rawResult = await listDir(args.dirPath || ".", cwd);
|
|
206
240
|
break;
|
package/dist/tools/safety.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* estado. En cambio dos escrituras seguidas SI pueden pisarse, y el orden
|
|
8
8
|
* importa, asi que esas van una detras de otra.
|
|
9
9
|
*/
|
|
10
|
+
export declare const READ_ONLY_TOOLS: Set<string>;
|
|
10
11
|
export declare function isReadOnly(toolName: string): boolean;
|
|
11
12
|
/**
|
|
12
13
|
* Un lote solo se paraleliza si TODAS sus llamadas son de solo lectura y hay
|
package/dist/tools/safety.js
CHANGED
|
@@ -18,7 +18,10 @@ const READ_ONLY = new Set([
|
|
|
18
18
|
"web_search",
|
|
19
19
|
"read_memory",
|
|
20
20
|
"use_skill",
|
|
21
|
+
"read_task_output",
|
|
22
|
+
"list_background_tasks",
|
|
21
23
|
]);
|
|
24
|
+
export const READ_ONLY_TOOLS = READ_ONLY;
|
|
22
25
|
export function isReadOnly(toolName) {
|
|
23
26
|
return READ_ONLY.has(toolName);
|
|
24
27
|
}
|
package/dist/tools/todoTool.d.ts
CHANGED
|
@@ -5,4 +5,6 @@ export interface TaskItem {
|
|
|
5
5
|
}
|
|
6
6
|
export declare function setSessionTasks(tasks: TaskItem[]): string;
|
|
7
7
|
export declare function getSessionTasks(): TaskItem[];
|
|
8
|
+
export declare function renderTaskWidget(tasks?: TaskItem[]): string | null;
|
|
9
|
+
export declare function sincronizarTareasConPie(tasks?: TaskItem[]): void;
|
|
8
10
|
export declare function renderTaskList(tasks: TaskItem[]): string;
|
package/dist/tools/todoTool.js
CHANGED
|
@@ -1,12 +1,41 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import { fijarSeccion, quitarSeccion } from "../ui/pieFijo.js";
|
|
2
3
|
let sessionTasks = [];
|
|
3
4
|
export function setSessionTasks(tasks) {
|
|
4
5
|
sessionTasks = tasks;
|
|
6
|
+
sincronizarTareasConPie(sessionTasks);
|
|
5
7
|
return renderTaskList(sessionTasks);
|
|
6
8
|
}
|
|
7
9
|
export function getSessionTasks() {
|
|
8
10
|
return sessionTasks;
|
|
9
11
|
}
|
|
12
|
+
export function renderTaskWidget(tasks) {
|
|
13
|
+
const list = tasks !== undefined ? tasks : sessionTasks;
|
|
14
|
+
if (!list || list.length === 0)
|
|
15
|
+
return null;
|
|
16
|
+
const total = list.length;
|
|
17
|
+
const completed = list.filter((t) => t.status === "completed").length;
|
|
18
|
+
const inProgress = list.find((t) => t.status === "in_progress");
|
|
19
|
+
return (chalk.hex("#D97757")("⎿") +
|
|
20
|
+
" " +
|
|
21
|
+
chalk.green("✔") +
|
|
22
|
+
` ${completed}/${total} tareas` +
|
|
23
|
+
(inProgress
|
|
24
|
+
? chalk.gray(" · ") + chalk.hex("#D97757")("En curso: ") + chalk.bold.white(inProgress.text)
|
|
25
|
+
: completed === total
|
|
26
|
+
? chalk.green(" · Completado")
|
|
27
|
+
: ""));
|
|
28
|
+
}
|
|
29
|
+
export function sincronizarTareasConPie(tasks) {
|
|
30
|
+
const list = tasks !== undefined ? tasks : sessionTasks;
|
|
31
|
+
const widget = renderTaskWidget(list);
|
|
32
|
+
if (widget) {
|
|
33
|
+
fijarSeccion("tareas", [widget]);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
quitarSeccion("tareas");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
10
39
|
export function renderTaskList(tasks) {
|
|
11
40
|
if (tasks.length === 0)
|
|
12
41
|
return "No hay tareas registradas.";
|
|
@@ -111,6 +111,8 @@ export const COMPUTER_USE_TOOL = {
|
|
|
111
111
|
"scroll",
|
|
112
112
|
"type",
|
|
113
113
|
"key",
|
|
114
|
+
"hotkey",
|
|
115
|
+
"press",
|
|
114
116
|
"wait",
|
|
115
117
|
"wait_change",
|
|
116
118
|
"cursor_position",
|
|
@@ -183,11 +185,14 @@ export const CHROME_TOOL = {
|
|
|
183
185
|
"status", "tabs", "open", "select_tab", "navigate", "snapshot",
|
|
184
186
|
"click", "type", "press", "get_text", "eval", "wait_for",
|
|
185
187
|
"screenshot", "notice", "close_tab", "done",
|
|
188
|
+
"console_logs", "network_errors",
|
|
186
189
|
],
|
|
187
190
|
description: "status: comprueba que la extension esta conectada. tabs: lista las pestanas del usuario. " +
|
|
188
191
|
"open: abre una pestana nueva SIN quitarle la pantalla al usuario. select_tab: elige una ya abierta. " +
|
|
189
192
|
"snapshot: LEE la pagina y numera los elementos. click/type: actuan sobre un ref. " +
|
|
190
193
|
"wait_for: espera un texto o unos ms. screenshot: captura la pestana aunque no este visible y la describe. " +
|
|
194
|
+
"console_logs: obtiene errores y advertencias de consola y excepciones no controladas de la pagina. " +
|
|
195
|
+
"network_errors: obtiene peticiones de red HTTP fallidas (4xx/5xx o errores de conexion). " +
|
|
191
196
|
"notice: cambia el texto del aviso flotante. close_tab: cierra una pestaña que abriste. done: cierra el trabajo y quita el aviso.",
|
|
192
197
|
},
|
|
193
198
|
url: { type: "string", description: "URL para open o navigate." },
|
|
@@ -212,6 +217,10 @@ export const CHROME_TOOL = {
|
|
|
212
217
|
analyze: { type: "boolean", description: "false para no gastar tokens de vision." },
|
|
213
218
|
outputPath: { type: "string", description: "Ruta donde guardar la captura." },
|
|
214
219
|
note: { type: "string", description: "Texto inicial del aviso flotante al abrir la pestana." },
|
|
220
|
+
clear: {
|
|
221
|
+
type: "boolean",
|
|
222
|
+
description: "true para limpiar el buffer tras consultar (en console_logs o network_errors).",
|
|
223
|
+
},
|
|
215
224
|
},
|
|
216
225
|
required: ["action"],
|
|
217
226
|
},
|
|
@@ -283,9 +283,14 @@ public static class Choco
|
|
|
283
283
|
double scale = 1.0;
|
|
284
284
|
int longEdge = Math.Max(w, h);
|
|
285
285
|
if (maxLongEdge > 0 && longEdge > maxLongEdge) scale = (double)maxLongEdge / (double)longEdge;
|
|
286
|
+
ImageFormat fmt = ImageFormat.Png;
|
|
287
|
+
if (path.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase))
|
|
288
|
+
{
|
|
289
|
+
fmt = ImageFormat.Jpeg;
|
|
290
|
+
}
|
|
286
291
|
if (scale >= 0.999)
|
|
287
292
|
{
|
|
288
|
-
bmp.Save(path,
|
|
293
|
+
bmp.Save(path, fmt);
|
|
289
294
|
return 1.0;
|
|
290
295
|
}
|
|
291
296
|
int nw = (int)Math.Round(w * scale), nh = (int)Math.Round(h * scale);
|
|
@@ -294,7 +299,7 @@ public static class Choco
|
|
|
294
299
|
{
|
|
295
300
|
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
|
|
296
301
|
g.DrawImage(bmp, 0, 0, nw, nh);
|
|
297
|
-
dst.Save(path,
|
|
302
|
+
dst.Save(path, fmt);
|
|
298
303
|
}
|
|
299
304
|
return scale;
|
|
300
305
|
}
|
package/dist/ui/comandos.js
CHANGED
|
@@ -35,6 +35,8 @@ export const SLASH_COMMANDS = [
|
|
|
35
35
|
{ name: "/goal", description: "Ejecutar meta compleja autónoma con auto-corrección", args: "<meta>" },
|
|
36
36
|
{ name: "/compact", description: "Comprimir contexto y optimizar memoria de la sesión" },
|
|
37
37
|
{ name: "/diff", description: "Ver diferencias de código (Git diff) del proyecto" },
|
|
38
|
+
{ name: "/audit", description: "Auditar git diff en busca de secretos, logs residuales y sugerir commit", args: "[review]" },
|
|
39
|
+
{ name: "/review", description: "Revisar cambios pendientes de git (alias de /audit)" },
|
|
38
40
|
{ name: "/commit", description: "Crear un commit en Git con mensaje semántico", args: "<mensaje>" },
|
|
39
41
|
{ name: "/cd", description: "Cambiar el directorio de trabajo activo", args: "<ruta>" },
|
|
40
42
|
{ name: "/uso", description: "Ver cuánto llevas consumido de tu cuenta" },
|
package/dist/ui/marco.d.ts
CHANGED
package/dist/ui/marco.js
CHANGED
|
@@ -214,7 +214,9 @@ export function buildPromptFrame(opts) {
|
|
|
214
214
|
// Dos, no una. Con una, la caja seguia leyendose pegada a lo de arriba: "pusiste
|
|
215
215
|
// una linea de espacio, que sea bien pues". Con dos se separa de verdad, y es
|
|
216
216
|
// el mismo aire que hay al arrancar entre el zorro y el aviso.
|
|
217
|
-
const
|
|
217
|
+
const widgetLines = opts.taskWidget && opts.taskWidget.trim().length > 0 ? [opts.taskWidget] : [];
|
|
218
|
+
const widgetRows = widgetLines.reduce((n, l) => n + physicalRows(l, cols), 0);
|
|
219
|
+
const lines = ["", "", ...widgetLines, bar, inputLine, ...dropdownLines, bar, footerLine];
|
|
218
220
|
// 4. Donde queda el cursor, en filas fisicas. Antes se hacia cursorCol =
|
|
219
221
|
// total % cols: aplicaba el modulo pero nunca sumaba la fila, asi que en
|
|
220
222
|
// cuanto la entrada se partia el acento se iba a escribir a otra linea.
|
|
@@ -222,7 +224,7 @@ export function buildPromptFrame(opts) {
|
|
|
222
224
|
const segments = typed.split("\n");
|
|
223
225
|
// La fila de aire de arriba cuenta: sin sumarla, el cursor se dibuja una fila
|
|
224
226
|
// por encima de donde se escribe.
|
|
225
|
-
let cursorRow = 2 + physicalRows(bar, cols);
|
|
227
|
+
let cursorRow = 2 + widgetRows + physicalRows(bar, cols);
|
|
226
228
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
227
229
|
cursorRow += Math.max(1, Math.ceil(((i === 0 ? symbolWidth : 0) + segments[i].length) / cols));
|
|
228
230
|
}
|
|
@@ -26,3 +26,12 @@ export declare function buildPermissionBox(toolName: string, args: Record<string
|
|
|
26
26
|
* tarea tal cual y no cuesta nada. Ver ui/ink/prestamo.ts.
|
|
27
27
|
*/
|
|
28
28
|
export declare function askToolPermission(toolName: string, args: Record<string, any>, cwd?: string): Promise<PermissionPromptResult>;
|
|
29
|
+
/**
|
|
30
|
+
* El mismo contenido del dialogo del terminal, sin color y sin marco, para
|
|
31
|
+
* pintarlo en HTML.
|
|
32
|
+
*
|
|
33
|
+
* Se reutiliza `details()` a proposito: dos sitios decidiendo por separado que
|
|
34
|
+
* hay que enseñar antes de aprobar un borrado acaban discrepando, y el que
|
|
35
|
+
* discrepa siempre es el que menos se mira.
|
|
36
|
+
*/
|
|
37
|
+
export declare function detallesEnTextoPlano(toolName: string, args: Record<string, any>, cwd?: string): string[];
|
|
@@ -4,6 +4,7 @@ import chalk from "chalk";
|
|
|
4
4
|
import { cycleMode, describeMode, getMode, modeAnnouncement } from "./modes.js";
|
|
5
5
|
import { isOutsideProject, isSvgSource } from "../config/permissions.js";
|
|
6
6
|
import { conElTerminalPrestado } from "./ink/prestamo.js";
|
|
7
|
+
import { pedirPermisoWeb } from "../servidor/puente.js";
|
|
7
8
|
/**
|
|
8
9
|
* Aprobacion automatica: se activa con --yes / -y o con la variable de entorno
|
|
9
10
|
* CHOCOLATITO_AUTO_APPROVE. Pensada para modo no interactivo y CI.
|
|
@@ -362,5 +363,34 @@ function details(toolName, args, cwd, width) {
|
|
|
362
363
|
* tarea tal cual y no cuesta nada. Ver ui/ink/prestamo.ts.
|
|
363
364
|
*/
|
|
364
365
|
export function askToolPermission(toolName, args, cwd = process.cwd()) {
|
|
366
|
+
// --yes primero: si ya se aprobo todo a mano, no hay que molestar a nadie ni
|
|
367
|
+
// abrir en el movil un dialogo que se contestaria solo.
|
|
368
|
+
if (autoApprove)
|
|
369
|
+
return Promise.resolve({ approved: true });
|
|
370
|
+
// Con `--servir` y alguien conectado, la pregunta va al navegador. Devuelve
|
|
371
|
+
// null cuando no hay ningun cliente, y entonces esto sigue como siempre: al
|
|
372
|
+
// terminal, que es donde hay un humano. Lo que NO pasa nunca es que se apruebe
|
|
373
|
+
// algo por no tener a quien preguntar.
|
|
374
|
+
const porWeb = pedirPermisoWeb(toolName, detallesEnTextoPlano(toolName, args, cwd), PERMISSION_OPTIONS);
|
|
375
|
+
if (porWeb)
|
|
376
|
+
return porWeb;
|
|
365
377
|
return conElTerminalPrestado(() => askToolPermissionDirecto(toolName, args, cwd));
|
|
366
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* El mismo contenido del dialogo del terminal, sin color y sin marco, para
|
|
381
|
+
* pintarlo en HTML.
|
|
382
|
+
*
|
|
383
|
+
* Se reutiliza `details()` a proposito: dos sitios decidiendo por separado que
|
|
384
|
+
* hay que enseñar antes de aprobar un borrado acaban discrepando, y el que
|
|
385
|
+
* discrepa siempre es el que menos se mira.
|
|
386
|
+
*/
|
|
387
|
+
export function detallesEnTextoPlano(toolName, args, cwd = process.cwd()) {
|
|
388
|
+
const sinColor = (t) => t.replace(/\u001b\[[0-9;]*m/g, "");
|
|
389
|
+
const kind = classify(toolName, args);
|
|
390
|
+
return [
|
|
391
|
+
sinColor(kind.title).trim(),
|
|
392
|
+
...details(toolName, args, cwd, 72)
|
|
393
|
+
.map((d) => sinColor(d.text).trim())
|
|
394
|
+
.filter((t) => t.length > 0),
|
|
395
|
+
];
|
|
396
|
+
}
|
package/dist/ui/pieFijo.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* pintara donde le tocara, dos productores independientes -el razonamiento y el
|
|
4
4
|
* spinner llegan por caminos distintos- se turnarian el sitio y el pie bailaria.
|
|
5
5
|
*/
|
|
6
|
-
declare const ORDEN: readonly ["razonamiento", "spinner", "entrada"];
|
|
6
|
+
declare const ORDEN: readonly ["tareas", "razonamiento", "spinner", "entrada"];
|
|
7
7
|
export type SeccionDelPie = (typeof ORDEN)[number];
|
|
8
8
|
/**
|
|
9
9
|
* Enciende o apaga el pie a mano. Con `null` vuelve a mandar el terminal.
|
package/dist/ui/pieFijo.js
CHANGED
|
@@ -54,7 +54,7 @@ const MOSTRAR_CURSOR = "\x1b[?25h";
|
|
|
54
54
|
* pintara donde le tocara, dos productores independientes -el razonamiento y el
|
|
55
55
|
* spinner llegan por caminos distintos- se turnarian el sitio y el pie bailaria.
|
|
56
56
|
*/
|
|
57
|
-
const ORDEN = ["razonamiento", "spinner", "entrada"];
|
|
57
|
+
const ORDEN = ["tareas", "razonamiento", "spinner", "entrada"];
|
|
58
58
|
const secciones = new Map();
|
|
59
59
|
/**
|
|
60
60
|
* Texto permanente que aun no ha recibido su salto de linea.
|
package/dist/ui/renderer.js
CHANGED
|
@@ -179,6 +179,14 @@ export function formatToolName(name) {
|
|
|
179
179
|
return "Move";
|
|
180
180
|
case "run_command":
|
|
181
181
|
return "Bash";
|
|
182
|
+
case "start_background_task":
|
|
183
|
+
return "BgTask";
|
|
184
|
+
case "read_task_output":
|
|
185
|
+
return "TaskOutput";
|
|
186
|
+
case "list_background_tasks":
|
|
187
|
+
return "ListTasks";
|
|
188
|
+
case "stop_background_task":
|
|
189
|
+
return "StopTask";
|
|
182
190
|
case "list_dir":
|
|
183
191
|
return "ListDir";
|
|
184
192
|
case "grep_search":
|
package/extension/background.js
CHANGED
|
@@ -431,6 +431,68 @@ async function setTabOverlay(tabId, activo) {
|
|
|
431
431
|
}
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
+
// Telemetría de red: errores HTTP (>=400) y fallos de conexión por pestaña
|
|
435
|
+
const MAX_NETWORK_ERRORS = 100;
|
|
436
|
+
/** tabId -> Array<{ method: string, url: string, status?: number, error?: string, timestamp: number }> */
|
|
437
|
+
const networkErrorsByTab = new Map();
|
|
438
|
+
|
|
439
|
+
function registrarErrorRed(tabId, info) {
|
|
440
|
+
if (!tabId || tabId < 0) return;
|
|
441
|
+
let lista = networkErrorsByTab.get(tabId);
|
|
442
|
+
if (!lista) {
|
|
443
|
+
lista = [];
|
|
444
|
+
networkErrorsByTab.set(tabId, lista);
|
|
445
|
+
}
|
|
446
|
+
lista.push({
|
|
447
|
+
method: info.method || "GET",
|
|
448
|
+
url: info.url || "",
|
|
449
|
+
status: info.status !== undefined ? info.status : (info.statusCode !== undefined ? info.statusCode : undefined),
|
|
450
|
+
error: info.error || undefined,
|
|
451
|
+
timestamp: info.timestamp || Date.now(),
|
|
452
|
+
});
|
|
453
|
+
if (lista.length > MAX_NETWORK_ERRORS) {
|
|
454
|
+
lista.shift();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
try {
|
|
459
|
+
if (typeof chrome !== "undefined" && chrome.webRequest && chrome.webRequest.onCompleted && typeof chrome.webRequest.onCompleted.addListener === "function") {
|
|
460
|
+
chrome.webRequest.onCompleted.addListener(
|
|
461
|
+
(details) => {
|
|
462
|
+
if (details && details.tabId > 0 && details.statusCode >= 400) {
|
|
463
|
+
registrarErrorRed(details.tabId, {
|
|
464
|
+
method: details.method,
|
|
465
|
+
url: details.url,
|
|
466
|
+
status: details.statusCode,
|
|
467
|
+
error: `HTTP ${details.statusCode}`,
|
|
468
|
+
timestamp: Math.round(details.timeStamp || Date.now()),
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
{ urls: ["<all_urls>"] }
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
} catch (_) {}
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
if (typeof chrome !== "undefined" && chrome.webRequest && chrome.webRequest.onErrorOccurred && typeof chrome.webRequest.onErrorOccurred.addListener === "function") {
|
|
479
|
+
chrome.webRequest.onErrorOccurred.addListener(
|
|
480
|
+
(details) => {
|
|
481
|
+
if (details && details.tabId > 0) {
|
|
482
|
+
registrarErrorRed(details.tabId, {
|
|
483
|
+
method: details.method,
|
|
484
|
+
url: details.url,
|
|
485
|
+
status: undefined,
|
|
486
|
+
error: details.error || "net::ERR_FAILED",
|
|
487
|
+
timestamp: Math.round(details.timeStamp || Date.now()),
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
{ urls: ["<all_urls>"] }
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
} catch (_) {}
|
|
495
|
+
|
|
434
496
|
// D8: Detección y reporte de descargas disparadas por acciones del agente
|
|
435
497
|
const descargasRecientes = [];
|
|
436
498
|
try {
|
|
@@ -1012,6 +1074,23 @@ async function handle(msg, st) {
|
|
|
1012
1074
|
return res;
|
|
1013
1075
|
}
|
|
1014
1076
|
|
|
1077
|
+
if (cmd === "getConsoleLogs") {
|
|
1078
|
+
const { tabId } = await readTab(msg.tabId, st);
|
|
1079
|
+
const res = await tell(tabId, 0, { cmd: "getConsoleLogs", clear: msg.clear === true });
|
|
1080
|
+
if (!res || !res.ok) throw new Error((res && res.error) || "no se pudieron obtener los logs de consola");
|
|
1081
|
+
return { ok: true, tabId, logs: res.logs || [] };
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
if (cmd === "getNetworkErrors") {
|
|
1085
|
+
const { tabId } = await readTab(msg.tabId, st);
|
|
1086
|
+
const lista = networkErrorsByTab.get(tabId) || [];
|
|
1087
|
+
const errors = [...lista];
|
|
1088
|
+
if (msg.clear === true) {
|
|
1089
|
+
networkErrorsByTab.delete(tabId);
|
|
1090
|
+
}
|
|
1091
|
+
return { ok: true, tabId, errors };
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1015
1094
|
if (cmd === "eval") {
|
|
1016
1095
|
// eval ejecuta JavaScript arbitrario en la pagina: puede pulsar, enviar
|
|
1017
1096
|
// formularios y leerlo todo. Es actuacion, no lectura.
|
|
@@ -1138,6 +1217,7 @@ async function handle(msg, st) {
|
|
|
1138
1217
|
try {
|
|
1139
1218
|
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
1140
1219
|
setTabOverlay(tabId, false);
|
|
1220
|
+
networkErrorsByTab.delete(tabId);
|
|
1141
1221
|
for (const link of links.values()) {
|
|
1142
1222
|
if (!link.state) continue;
|
|
1143
1223
|
link.state.propias.delete(tabId);
|
package/extension/content.js
CHANGED
|
@@ -17,6 +17,103 @@
|
|
|
17
17
|
|
|
18
18
|
const IS_TOP = window.top === window;
|
|
19
19
|
|
|
20
|
+
// ======================================================== telemetría de consola y errores de página
|
|
21
|
+
const MAX_CONSOLE_LOGS = 100;
|
|
22
|
+
const consoleLogsBuffer = [];
|
|
23
|
+
|
|
24
|
+
function addConsoleLog(level, message, stack, url) {
|
|
25
|
+
const entry = {
|
|
26
|
+
timestamp: Date.now(),
|
|
27
|
+
level: level === "warn" ? "warn" : "error",
|
|
28
|
+
message: String(message || ""),
|
|
29
|
+
};
|
|
30
|
+
if (stack) entry.stack = String(stack);
|
|
31
|
+
if (url) entry.url = String(url);
|
|
32
|
+
|
|
33
|
+
consoleLogsBuffer.push(entry);
|
|
34
|
+
if (consoleLogsBuffer.length > MAX_CONSOLE_LOGS) {
|
|
35
|
+
consoleLogsBuffer.shift();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
window.addEventListener("error", (event) => {
|
|
41
|
+
try {
|
|
42
|
+
const msg = event.message || (event.error && event.error.message) || String(event);
|
|
43
|
+
const stack = (event.error && event.error.stack) || undefined;
|
|
44
|
+
const url = event.filename || undefined;
|
|
45
|
+
addConsoleLog("error", msg, stack, url);
|
|
46
|
+
} catch (_) {}
|
|
47
|
+
});
|
|
48
|
+
} catch (_) {}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
window.addEventListener("unhandledrejection", (event) => {
|
|
52
|
+
try {
|
|
53
|
+
const reason = event.reason;
|
|
54
|
+
const msg =
|
|
55
|
+
(reason && (reason.message || (typeof reason === "object" ? JSON.stringify(reason) : String(reason)))) ||
|
|
56
|
+
"Unhandled Promise Rejection";
|
|
57
|
+
const stack = (reason && reason.stack) || undefined;
|
|
58
|
+
addConsoleLog("error", `Unhandled rejection: ${msg}`, stack);
|
|
59
|
+
} catch (_) {}
|
|
60
|
+
});
|
|
61
|
+
} catch (_) {}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const originalConsoleError = console.error;
|
|
65
|
+
console.error = function (...args) {
|
|
66
|
+
try {
|
|
67
|
+
const msg = args
|
|
68
|
+
.map((a) => {
|
|
69
|
+
if (a instanceof Error) return a.message || String(a);
|
|
70
|
+
if (typeof a === "object" && a !== null) {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.stringify(a);
|
|
73
|
+
} catch (_) {
|
|
74
|
+
return String(a);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return String(a);
|
|
78
|
+
})
|
|
79
|
+
.join(" ");
|
|
80
|
+
const errObj = args.find((a) => a instanceof Error);
|
|
81
|
+
const stack = errObj && errObj.stack ? errObj.stack : undefined;
|
|
82
|
+
addConsoleLog("error", msg, stack);
|
|
83
|
+
} catch (_) {}
|
|
84
|
+
if (typeof originalConsoleError === "function") {
|
|
85
|
+
originalConsoleError.apply(console, args);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
} catch (_) {}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const originalConsoleWarn = console.warn;
|
|
92
|
+
console.warn = function (...args) {
|
|
93
|
+
try {
|
|
94
|
+
const msg = args
|
|
95
|
+
.map((a) => {
|
|
96
|
+
if (a instanceof Error) return a.message || String(a);
|
|
97
|
+
if (typeof a === "object" && a !== null) {
|
|
98
|
+
try {
|
|
99
|
+
return JSON.stringify(a);
|
|
100
|
+
} catch (_) {
|
|
101
|
+
return String(a);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return String(a);
|
|
105
|
+
})
|
|
106
|
+
.join(" ");
|
|
107
|
+
const errObj = args.find((a) => a instanceof Error);
|
|
108
|
+
const stack = errObj && errObj.stack ? errObj.stack : undefined;
|
|
109
|
+
addConsoleLog("warn", msg, stack);
|
|
110
|
+
} catch (_) {}
|
|
111
|
+
if (typeof originalConsoleWarn === "function") {
|
|
112
|
+
originalConsoleWarn.apply(console, args);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
} catch (_) {}
|
|
116
|
+
|
|
20
117
|
// ======================================================== overlay visual y borde naranja (#D97757)
|
|
21
118
|
|
|
22
119
|
let overlayHost = null;
|
|
@@ -916,6 +1013,15 @@
|
|
|
916
1013
|
break;
|
|
917
1014
|
}
|
|
918
1015
|
|
|
1016
|
+
case "getConsoleLogs": {
|
|
1017
|
+
const logs = [...consoleLogsBuffer];
|
|
1018
|
+
if (msg.clear === true) {
|
|
1019
|
+
consoleLogsBuffer.length = 0;
|
|
1020
|
+
}
|
|
1021
|
+
sendResponse({ ok: true, logs });
|
|
1022
|
+
break;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
919
1025
|
default:
|
|
920
1026
|
sendResponse({ ok: false, error: "comando desconocido: " + (msg.cmd || msg.tipo) });
|
|
921
1027
|
}
|