chocolatito-code 1.1.0 → 1.2.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.
- package/README.md +66 -0
- package/dist/agent/loop.js +129 -36
- package/dist/agent/salidaDelAgente.d.ts +43 -0
- package/dist/agent/salidaDelAgente.js +53 -0
- package/dist/config/nodeVersion.d.ts +13 -0
- package/dist/config/nodeVersion.js +26 -0
- package/dist/index.js +22 -7
- package/dist/mcp/client.d.ts +28 -0
- package/dist/mcp/client.js +62 -8
- package/dist/mcp/ide.d.ts +76 -0
- package/dist/mcp/ide.js +160 -0
- package/dist/mcp/manager.js +9 -0
- package/dist/tools/browserExtension.js +35 -4
- package/dist/tools/definitions.js +9 -2
- package/dist/tools/grepSearch.js +111 -23
- package/dist/tools/runner.js +1 -1
- package/dist/tools/writeFile.d.ts +16 -1
- package/dist/tools/writeFile.js +22 -1
- package/dist/ui/interrupt.js +3 -2
- package/dist/ui/keyboardGuard.d.ts +21 -0
- package/dist/ui/keyboardGuard.js +30 -1
- package/dist/ui/pegados.d.ts +50 -0
- package/dist/ui/pegados.js +83 -0
- package/dist/ui/prompt.d.ts +5 -0
- package/dist/ui/prompt.js +101 -16
- package/dist/ui/salida.d.ts +10 -0
- package/dist/ui/salida.js +50 -0
- package/extension/background.js +182 -11
- package/extension/content.js +446 -48
- package/extension/fuentes/Chocolatito-Marca.ttf +0 -0
- package/extension/iconos/128.png +0 -0
- package/extension/iconos/16.png +0 -0
- package/extension/iconos/32.png +0 -0
- package/extension/iconos/48.png +0 -0
- package/extension/iconos/logotipo.png +0 -0
- package/extension/manifest.json +26 -3
- package/extension/popup.html +16 -6
- package/package.json +3 -2
package/extension/background.js
CHANGED
|
@@ -195,6 +195,9 @@ function connect(port) {
|
|
|
195
195
|
ws.onclose = () => {
|
|
196
196
|
clearInterval(link.timer);
|
|
197
197
|
if (link.socket === ws) link.socket = null;
|
|
198
|
+
const oldTab = link.state && link.state.workingTabId;
|
|
199
|
+
if (link.state) link.state.workingTabId = null;
|
|
200
|
+
if (oldTab) setTabOverlay(oldTab, false);
|
|
198
201
|
refreshBadge();
|
|
199
202
|
scheduleReconnect(port);
|
|
200
203
|
};
|
|
@@ -399,6 +402,86 @@ async function readTab(tabId, st) {
|
|
|
399
402
|
return { tabId: activa.id, own: false };
|
|
400
403
|
}
|
|
401
404
|
|
|
405
|
+
/**
|
|
406
|
+
* Comprueba si alguna sesión activa está usando la pestaña como pestaña de trabajo (B4).
|
|
407
|
+
* Permite que dos sesiones convivan sin que el cierre de una apague el borde
|
|
408
|
+
* de la pestaña si la otra aún la está utilizando.
|
|
409
|
+
*/
|
|
410
|
+
function tabEnUso(tabId) {
|
|
411
|
+
if (!tabId) return false;
|
|
412
|
+
for (const link of links.values()) {
|
|
413
|
+
if (link.state && link.state.workingTabId === tabId) return true;
|
|
414
|
+
}
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Pide al content script encender o apagar el borde naranja y la insignia (B1-B4).
|
|
420
|
+
* Si se pide apagar pero otra sesión aún usa la pestaña, se mantiene encendido.
|
|
421
|
+
* Si el content script no está inyectado todavía (páginas internas, cargando),
|
|
422
|
+
* se captura el error silenciosamente para no impedir la acción.
|
|
423
|
+
*/
|
|
424
|
+
async function setTabOverlay(tabId, activo) {
|
|
425
|
+
if (!tabId) return;
|
|
426
|
+
if (!activo && tabEnUso(tabId)) return;
|
|
427
|
+
try {
|
|
428
|
+
await chrome.tabs.sendMessage(tabId, { cmd: "overlay", tipo: "overlay", activo: Boolean(activo) });
|
|
429
|
+
} catch (_) {
|
|
430
|
+
// Si la página aún no tiene content script o es una URL vetada, se ignora
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// D8: Detección y reporte de descargas disparadas por acciones del agente
|
|
435
|
+
const descargasRecientes = [];
|
|
436
|
+
try {
|
|
437
|
+
if (typeof chrome !== "undefined" && chrome.downloads && chrome.downloads.onCreated) {
|
|
438
|
+
chrome.downloads.onCreated.addListener((item) => {
|
|
439
|
+
descargasRecientes.push({
|
|
440
|
+
id: item.id,
|
|
441
|
+
url: item.url,
|
|
442
|
+
filename: item.filename || "",
|
|
443
|
+
state: item.state || "in_progress",
|
|
444
|
+
timestamp: Date.now(),
|
|
445
|
+
});
|
|
446
|
+
if (descargasRecientes.length > 20) descargasRecientes.shift();
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
if (typeof chrome !== "undefined" && chrome.downloads && chrome.downloads.onChanged) {
|
|
450
|
+
chrome.downloads.onChanged.addListener((delta) => {
|
|
451
|
+
const item = descargasRecientes.find((d) => d.id === delta.id);
|
|
452
|
+
if (item) {
|
|
453
|
+
if (delta.filename && delta.filename.current) item.filename = delta.filename.current;
|
|
454
|
+
if (delta.state && delta.state.current) item.state = delta.state.current;
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
} catch (_) {}
|
|
459
|
+
|
|
460
|
+
async function detectarDescarga(tiempoInicio) {
|
|
461
|
+
try {
|
|
462
|
+
if (typeof chrome !== "undefined" && chrome.downloads && typeof chrome.downloads.search === "function") {
|
|
463
|
+
const items = await chrome.downloads.search({
|
|
464
|
+
startedAfter: new Date(tiempoInicio).toISOString(),
|
|
465
|
+
limit: 1,
|
|
466
|
+
});
|
|
467
|
+
if (items && items.length > 0) {
|
|
468
|
+
return {
|
|
469
|
+
id: items[0].id,
|
|
470
|
+
filename: items[0].filename || "",
|
|
471
|
+
state: items[0].state || "in_progress",
|
|
472
|
+
url: items[0].url,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
} catch (_) {}
|
|
477
|
+
for (let i = descargasRecientes.length - 1; i >= 0; i--) {
|
|
478
|
+
if (descargasRecientes[i].timestamp >= tiempoInicio) {
|
|
479
|
+
return descargasRecientes[i];
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
|
|
402
485
|
/**
|
|
403
486
|
* Mete la pestana en un grupo visible llamado "Chocolatito Code", para que el
|
|
404
487
|
* usuario vea de un vistazo donde esta trabajando el agente.
|
|
@@ -732,6 +815,7 @@ async function handle(msg, st) {
|
|
|
732
815
|
await esperarCarga(tab.id, msg.timeout || 30000);
|
|
733
816
|
await groupTab(tab.id, st);
|
|
734
817
|
await banner(tab.id, msg.note || "pestana abierta");
|
|
818
|
+
await setTabOverlay(tab.id, true);
|
|
735
819
|
const info = await chrome.tabs.get(tab.id);
|
|
736
820
|
return { tabId: tab.id, title: info.title || "", url: info.url || "" };
|
|
737
821
|
}
|
|
@@ -743,12 +827,18 @@ async function handle(msg, st) {
|
|
|
743
827
|
(t) => (t.url || "").toLowerCase().includes(needle) || (t.title || "").toLowerCase().includes(needle)
|
|
744
828
|
);
|
|
745
829
|
if (!hit) throw new Error('ninguna pestana contiene "' + msg.match + '"');
|
|
830
|
+
if (st.workingTabId && st.workingTabId !== hit.id) {
|
|
831
|
+
const vieja = st.workingTabId;
|
|
832
|
+
st.workingTabId = null;
|
|
833
|
+
await setTabOverlay(vieja, false);
|
|
834
|
+
}
|
|
746
835
|
st.workingTabId = hit.id;
|
|
747
836
|
// Elegirla es un acto explicito del agente: a partir de aqui es suya y la
|
|
748
837
|
// puede cerrar. Mirarla (readTab) nunca da ese derecho.
|
|
749
838
|
st.propias.add(hit.id);
|
|
750
839
|
await groupTab(hit.id, st);
|
|
751
840
|
await banner(hit.id, "pestana seleccionada");
|
|
841
|
+
await setTabOverlay(hit.id, true);
|
|
752
842
|
return { tabId: hit.id, title: hit.title || "", url: hit.url || "" };
|
|
753
843
|
}
|
|
754
844
|
|
|
@@ -770,13 +860,17 @@ async function handle(msg, st) {
|
|
|
770
860
|
await groupTab(tabId, st);
|
|
771
861
|
const info = await chrome.tabs.get(tabId);
|
|
772
862
|
await banner(tabId, "navegando a " + (info.url || "").slice(0, 60));
|
|
863
|
+
await setTabOverlay(tabId, true);
|
|
773
864
|
return { tabId, title: info.title || "", url: info.url || "" };
|
|
774
865
|
}
|
|
775
866
|
|
|
776
867
|
if (cmd === "snapshot") {
|
|
777
868
|
const { tabId, own } = await readTab(msg.tabId, st);
|
|
778
869
|
// Solo se adopta como pestana de trabajo si ya era nuestra o vino con tabId.
|
|
779
|
-
if (own)
|
|
870
|
+
if (own) {
|
|
871
|
+
st.workingTabId = tabId;
|
|
872
|
+
await setTabOverlay(tabId, true);
|
|
873
|
+
}
|
|
780
874
|
await banner(tabId, "leyendo la pagina");
|
|
781
875
|
st.refIndex.clear();
|
|
782
876
|
|
|
@@ -839,6 +933,9 @@ async function handle(msg, st) {
|
|
|
839
933
|
const tabId = loc ? loc.tabId : await actTab(msg.tabId, st);
|
|
840
934
|
const frameId = loc ? loc.frameId : 0;
|
|
841
935
|
st.workingTabId = tabId;
|
|
936
|
+
await setTabOverlay(tabId, true);
|
|
937
|
+
|
|
938
|
+
const tiempoInicioAccion = Date.now() - 50;
|
|
842
939
|
|
|
843
940
|
if (cmd === "click") await banner(tabId, "pulsando un elemento");
|
|
844
941
|
if (cmd === "type") await banner(tabId, "escribiendo");
|
|
@@ -882,7 +979,24 @@ async function handle(msg, st) {
|
|
|
882
979
|
// Se deja respirar a la pagina para que reaccione antes del siguiente paso.
|
|
883
980
|
await sleep(msg.settle === undefined ? 600 : msg.settle);
|
|
884
981
|
const info = await chrome.tabs.get(tabId);
|
|
885
|
-
|
|
982
|
+
|
|
983
|
+
// D8: Detectar si esta acción inició o completó alguna descarga de archivo
|
|
984
|
+
const descarga = await detectarDescarga(tiempoInicioAccion);
|
|
985
|
+
if (descarga) {
|
|
986
|
+
res.download = descarga;
|
|
987
|
+
res.label = (res.label ? res.label + " " : "") + `[Descarga: ${descarga.filename || descarga.url}]`;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
return {
|
|
991
|
+
tabId,
|
|
992
|
+
title: info.title || "",
|
|
993
|
+
url: info.url || "",
|
|
994
|
+
label: res.label,
|
|
995
|
+
value: res.value,
|
|
996
|
+
via: res.via,
|
|
997
|
+
warning: res.warning,
|
|
998
|
+
download: res.download,
|
|
999
|
+
};
|
|
886
1000
|
}
|
|
887
1001
|
|
|
888
1002
|
if (cmd === "getText") {
|
|
@@ -903,7 +1017,23 @@ async function handle(msg, st) {
|
|
|
903
1017
|
|
|
904
1018
|
if (cmd === "waitFor") {
|
|
905
1019
|
const { tabId } = await readTab(msg.tabId, st);
|
|
906
|
-
const
|
|
1020
|
+
const timeout = msg.timeout || 20000;
|
|
1021
|
+
const deadline = Date.now() + timeout;
|
|
1022
|
+
|
|
1023
|
+
// D3: Espera hasta que aparezca un selector CSS en el DOM
|
|
1024
|
+
if (msg.selector || msg.css) {
|
|
1025
|
+
const sel = msg.selector || msg.css;
|
|
1026
|
+
while (Date.now() < deadline) {
|
|
1027
|
+
const res = await tell(tabId, 0, { cmd: "waitForCondition", selector: sel });
|
|
1028
|
+
if (res && res.ok && res.conditionMet) {
|
|
1029
|
+
return { ok: true, found: true, selector: sel };
|
|
1030
|
+
}
|
|
1031
|
+
await sleep(300);
|
|
1032
|
+
}
|
|
1033
|
+
return { ok: false, found: false, error: `Tiempo agotado esperando el selector "${sel}"` };
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// D3: Espera hasta que aparezca un texto concreto
|
|
907
1037
|
if (msg.text) {
|
|
908
1038
|
while (Date.now() < deadline) {
|
|
909
1039
|
const res = await tell(tabId, 0, { cmd: "getText" });
|
|
@@ -912,8 +1042,22 @@ async function handle(msg, st) {
|
|
|
912
1042
|
}
|
|
913
1043
|
return { found: false };
|
|
914
1044
|
}
|
|
915
|
-
|
|
916
|
-
|
|
1045
|
+
|
|
1046
|
+
// D3: Espera hasta que la pantalla "esté quieta" (sin mutaciones de DOM durante quietMs)
|
|
1047
|
+
if (msg.stable || msg.quiet || msg.condition === "stable") {
|
|
1048
|
+
while (Date.now() < deadline) {
|
|
1049
|
+
const res = await tell(tabId, 0, { cmd: "waitForCondition", stable: true, quietMs: msg.quietMs || 400 });
|
|
1050
|
+
if (res && res.ok && res.conditionMet) {
|
|
1051
|
+
return { ok: true, stable: true };
|
|
1052
|
+
}
|
|
1053
|
+
await sleep(250);
|
|
1054
|
+
}
|
|
1055
|
+
return { ok: true, stable: true, warning: "Tiempo límite alcanzado esperando quietud" };
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
const ms = msg.ms || 2000;
|
|
1059
|
+
await sleep(ms);
|
|
1060
|
+
return { waited: ms };
|
|
917
1061
|
}
|
|
918
1062
|
|
|
919
1063
|
if (cmd === "screenshot") {
|
|
@@ -950,9 +1094,12 @@ async function handle(msg, st) {
|
|
|
950
1094
|
}
|
|
951
1095
|
|
|
952
1096
|
if (ids.length === 0) return { closed: 0 };
|
|
953
|
-
await chrome.tabs.remove(ids);
|
|
954
|
-
for (const id of ids) st.propias.delete(id);
|
|
955
1097
|
if (ids.includes(st.workingTabId)) st.workingTabId = null;
|
|
1098
|
+
for (const id of ids) st.propias.delete(id);
|
|
1099
|
+
await chrome.tabs.remove(ids);
|
|
1100
|
+
for (const id of ids) {
|
|
1101
|
+
await setTabOverlay(id, false);
|
|
1102
|
+
}
|
|
956
1103
|
return { closed: ids.length };
|
|
957
1104
|
}
|
|
958
1105
|
|
|
@@ -962,16 +1109,17 @@ async function handle(msg, st) {
|
|
|
962
1109
|
// no era de nadie, y el usuario tenia que deshacerlo a mano.
|
|
963
1110
|
const aSoltar = new Set(st.propias);
|
|
964
1111
|
if (st.workingTabId) aSoltar.add(st.workingTabId);
|
|
1112
|
+
st.groupId = null;
|
|
1113
|
+
st.workingTabId = null;
|
|
1114
|
+
st.propias.clear();
|
|
1115
|
+
st.refIndex.clear();
|
|
965
1116
|
for (const id of aSoltar) {
|
|
966
1117
|
try {
|
|
967
1118
|
await chrome.tabs.ungroup(id);
|
|
968
1119
|
} catch (_) {}
|
|
969
1120
|
await banner(id, null);
|
|
1121
|
+
await setTabOverlay(id, false);
|
|
970
1122
|
}
|
|
971
|
-
st.groupId = null;
|
|
972
|
-
st.workingTabId = null;
|
|
973
|
-
st.propias.clear();
|
|
974
|
-
st.refIndex.clear();
|
|
975
1123
|
return { ok: true, soltadas: aSoltar.size };
|
|
976
1124
|
}
|
|
977
1125
|
|
|
@@ -983,6 +1131,7 @@ async function handle(msg, st) {
|
|
|
983
1131
|
// with id") en vez de decir que ya no hay donde trabajar.
|
|
984
1132
|
try {
|
|
985
1133
|
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
1134
|
+
setTabOverlay(tabId, false);
|
|
986
1135
|
for (const link of links.values()) {
|
|
987
1136
|
if (!link.state) continue;
|
|
988
1137
|
link.state.propias.delete(tabId);
|
|
@@ -992,4 +1141,26 @@ try {
|
|
|
992
1141
|
});
|
|
993
1142
|
} catch (_) {}
|
|
994
1143
|
|
|
1144
|
+
// Al navegar o recargar la página, se reinyecta el content script y el overlay
|
|
1145
|
+
// se apaga. Si la pestaña sigue siendo de trabajo para alguna sesión, se vuelve a activar (B4).
|
|
1146
|
+
try {
|
|
1147
|
+
if (typeof chrome !== "undefined" && chrome.webNavigation && chrome.webNavigation.onCompleted && typeof chrome.webNavigation.onCompleted.addListener === "function") {
|
|
1148
|
+
chrome.webNavigation.onCompleted.addListener((details) => {
|
|
1149
|
+
if (details && details.frameId === 0 && tabEnUso(details.tabId)) {
|
|
1150
|
+
setTabOverlay(details.tabId, true);
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
} catch (_) {}
|
|
1155
|
+
|
|
1156
|
+
try {
|
|
1157
|
+
if (typeof chrome !== "undefined" && chrome.tabs && chrome.tabs.onUpdated && typeof chrome.tabs.onUpdated.addListener === "function") {
|
|
1158
|
+
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
|
|
1159
|
+
if (changeInfo && changeInfo.status === "complete" && tabEnUso(tabId)) {
|
|
1160
|
+
setTabOverlay(tabId, true);
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
} catch (_) {}
|
|
1165
|
+
|
|
995
1166
|
for (const p of WS_PORTS) connect(p);
|