chocolatito-code 1.1.0 → 1.3.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/huecos.d.ts +58 -0
- package/dist/agent/huecos.js +81 -0
- package/dist/agent/loop.js +218 -41
- package/dist/agent/salidaDelAgente.d.ts +43 -0
- package/dist/agent/salidaDelAgente.js +39 -0
- package/dist/agent/toolGate.js +6 -1
- package/dist/config/engine.d.ts +21 -1
- package/dist/config/engine.js +22 -1
- 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 +153 -7
- package/dist/tools/computerUse.d.ts +1 -1
- package/dist/tools/computerUse.js +183 -2
- package/dist/tools/definitions.js +9 -2
- package/dist/tools/grepSearch.js +111 -23
- package/dist/tools/runner.js +1 -1
- package/dist/tools/toolDefsComputer.js +6 -2
- package/dist/tools/visionBridge.js +60 -3
- package/dist/tools/win/hostScript.js +154 -0
- package/dist/tools/writeFile.d.ts +16 -1
- package/dist/tools/writeFile.js +22 -1
- package/dist/ui/interrupt.d.ts +10 -0
- package/dist/ui/interrupt.js +53 -4
- 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/pieFijo.d.ts +56 -0
- package/dist/ui/pieFijo.js +309 -0
- package/dist/ui/prompt.d.ts +5 -0
- package/dist/ui/prompt.js +109 -16
- package/dist/ui/reasoningStream.js +22 -12
- package/dist/ui/salida.d.ts +10 -0
- package/dist/ui/salida.js +50 -0
- package/dist/ui/spinner.d.ts +15 -8
- package/dist/ui/spinner.js +80 -42
- package/extension/background.js +191 -14
- 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/ORIGEN.md +48 -0
- package/extension/iconos/logotipo.png +0 -0
- package/extension/manifest.json +27 -4
- 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.
|
|
@@ -664,7 +747,13 @@ async function typeTrusted(tabId, frameId, ref, text, submit, replace) {
|
|
|
664
747
|
async function capturar(tabId) {
|
|
665
748
|
try {
|
|
666
749
|
return await withTrustedTab(tabId, async (target) => {
|
|
667
|
-
|
|
750
|
+
// JPEG y no PNG, y no es cosmetico. Una captura en PNG de un monitor
|
|
751
|
+
// normal son 1,3 MB, que en base64 se convierten en 1,75 MB dentro de la
|
|
752
|
+
// peticion al modelo de vision. Eso no llega: la peticion se muere por el
|
|
753
|
+
// camino, no vuelve nada, y se lleva por delante uno de los cuatro huecos
|
|
754
|
+
// de la licencia durante minutos. En JPEG al 70 la misma captura ocupa
|
|
755
|
+
// unos 120 KB y se lee igual de bien.
|
|
756
|
+
const res = await cdp(target, "Page.captureScreenshot", { format: "jpeg", quality: 70, captureBeyondViewport: false });
|
|
668
757
|
return { data: res.data, via: "cdp" };
|
|
669
758
|
});
|
|
670
759
|
} catch (err) {
|
|
@@ -679,9 +768,9 @@ async function capturar(tabId) {
|
|
|
679
768
|
if (activa && activa.id !== tabId) previaId = activa.id;
|
|
680
769
|
if (!tab.active) await chrome.tabs.update(tabId, { active: true });
|
|
681
770
|
await sleep(250);
|
|
682
|
-
const dataUrl = await chrome.tabs.captureVisibleTab(windowId, { format: "
|
|
771
|
+
const dataUrl = await chrome.tabs.captureVisibleTab(windowId, { format: "jpeg", quality: 70 });
|
|
683
772
|
return {
|
|
684
|
-
data: String(dataUrl).replace(/^data:image
|
|
773
|
+
data: String(dataUrl).replace(/^data:image\/\w+;base64,/, ""),
|
|
685
774
|
via: "tabs",
|
|
686
775
|
warning: "el depurador no estaba disponible (¿DevTools abierto?): captura por la via normal",
|
|
687
776
|
};
|
|
@@ -732,6 +821,7 @@ async function handle(msg, st) {
|
|
|
732
821
|
await esperarCarga(tab.id, msg.timeout || 30000);
|
|
733
822
|
await groupTab(tab.id, st);
|
|
734
823
|
await banner(tab.id, msg.note || "pestana abierta");
|
|
824
|
+
await setTabOverlay(tab.id, true);
|
|
735
825
|
const info = await chrome.tabs.get(tab.id);
|
|
736
826
|
return { tabId: tab.id, title: info.title || "", url: info.url || "" };
|
|
737
827
|
}
|
|
@@ -743,12 +833,18 @@ async function handle(msg, st) {
|
|
|
743
833
|
(t) => (t.url || "").toLowerCase().includes(needle) || (t.title || "").toLowerCase().includes(needle)
|
|
744
834
|
);
|
|
745
835
|
if (!hit) throw new Error('ninguna pestana contiene "' + msg.match + '"');
|
|
836
|
+
if (st.workingTabId && st.workingTabId !== hit.id) {
|
|
837
|
+
const vieja = st.workingTabId;
|
|
838
|
+
st.workingTabId = null;
|
|
839
|
+
await setTabOverlay(vieja, false);
|
|
840
|
+
}
|
|
746
841
|
st.workingTabId = hit.id;
|
|
747
842
|
// Elegirla es un acto explicito del agente: a partir de aqui es suya y la
|
|
748
843
|
// puede cerrar. Mirarla (readTab) nunca da ese derecho.
|
|
749
844
|
st.propias.add(hit.id);
|
|
750
845
|
await groupTab(hit.id, st);
|
|
751
846
|
await banner(hit.id, "pestana seleccionada");
|
|
847
|
+
await setTabOverlay(hit.id, true);
|
|
752
848
|
return { tabId: hit.id, title: hit.title || "", url: hit.url || "" };
|
|
753
849
|
}
|
|
754
850
|
|
|
@@ -770,13 +866,17 @@ async function handle(msg, st) {
|
|
|
770
866
|
await groupTab(tabId, st);
|
|
771
867
|
const info = await chrome.tabs.get(tabId);
|
|
772
868
|
await banner(tabId, "navegando a " + (info.url || "").slice(0, 60));
|
|
869
|
+
await setTabOverlay(tabId, true);
|
|
773
870
|
return { tabId, title: info.title || "", url: info.url || "" };
|
|
774
871
|
}
|
|
775
872
|
|
|
776
873
|
if (cmd === "snapshot") {
|
|
777
874
|
const { tabId, own } = await readTab(msg.tabId, st);
|
|
778
875
|
// Solo se adopta como pestana de trabajo si ya era nuestra o vino con tabId.
|
|
779
|
-
if (own)
|
|
876
|
+
if (own) {
|
|
877
|
+
st.workingTabId = tabId;
|
|
878
|
+
await setTabOverlay(tabId, true);
|
|
879
|
+
}
|
|
780
880
|
await banner(tabId, "leyendo la pagina");
|
|
781
881
|
st.refIndex.clear();
|
|
782
882
|
|
|
@@ -839,6 +939,9 @@ async function handle(msg, st) {
|
|
|
839
939
|
const tabId = loc ? loc.tabId : await actTab(msg.tabId, st);
|
|
840
940
|
const frameId = loc ? loc.frameId : 0;
|
|
841
941
|
st.workingTabId = tabId;
|
|
942
|
+
await setTabOverlay(tabId, true);
|
|
943
|
+
|
|
944
|
+
const tiempoInicioAccion = Date.now() - 50;
|
|
842
945
|
|
|
843
946
|
if (cmd === "click") await banner(tabId, "pulsando un elemento");
|
|
844
947
|
if (cmd === "type") await banner(tabId, "escribiendo");
|
|
@@ -882,7 +985,24 @@ async function handle(msg, st) {
|
|
|
882
985
|
// Se deja respirar a la pagina para que reaccione antes del siguiente paso.
|
|
883
986
|
await sleep(msg.settle === undefined ? 600 : msg.settle);
|
|
884
987
|
const info = await chrome.tabs.get(tabId);
|
|
885
|
-
|
|
988
|
+
|
|
989
|
+
// D8: Detectar si esta acción inició o completó alguna descarga de archivo
|
|
990
|
+
const descarga = await detectarDescarga(tiempoInicioAccion);
|
|
991
|
+
if (descarga) {
|
|
992
|
+
res.download = descarga;
|
|
993
|
+
res.label = (res.label ? res.label + " " : "") + `[Descarga: ${descarga.filename || descarga.url}]`;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
return {
|
|
997
|
+
tabId,
|
|
998
|
+
title: info.title || "",
|
|
999
|
+
url: info.url || "",
|
|
1000
|
+
label: res.label,
|
|
1001
|
+
value: res.value,
|
|
1002
|
+
via: res.via,
|
|
1003
|
+
warning: res.warning,
|
|
1004
|
+
download: res.download,
|
|
1005
|
+
};
|
|
886
1006
|
}
|
|
887
1007
|
|
|
888
1008
|
if (cmd === "getText") {
|
|
@@ -903,7 +1023,23 @@ async function handle(msg, st) {
|
|
|
903
1023
|
|
|
904
1024
|
if (cmd === "waitFor") {
|
|
905
1025
|
const { tabId } = await readTab(msg.tabId, st);
|
|
906
|
-
const
|
|
1026
|
+
const timeout = msg.timeout || 20000;
|
|
1027
|
+
const deadline = Date.now() + timeout;
|
|
1028
|
+
|
|
1029
|
+
// D3: Espera hasta que aparezca un selector CSS en el DOM
|
|
1030
|
+
if (msg.selector || msg.css) {
|
|
1031
|
+
const sel = msg.selector || msg.css;
|
|
1032
|
+
while (Date.now() < deadline) {
|
|
1033
|
+
const res = await tell(tabId, 0, { cmd: "waitForCondition", selector: sel });
|
|
1034
|
+
if (res && res.ok && res.conditionMet) {
|
|
1035
|
+
return { ok: true, found: true, selector: sel };
|
|
1036
|
+
}
|
|
1037
|
+
await sleep(300);
|
|
1038
|
+
}
|
|
1039
|
+
return { ok: false, found: false, error: `Tiempo agotado esperando el selector "${sel}"` };
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// D3: Espera hasta que aparezca un texto concreto
|
|
907
1043
|
if (msg.text) {
|
|
908
1044
|
while (Date.now() < deadline) {
|
|
909
1045
|
const res = await tell(tabId, 0, { cmd: "getText" });
|
|
@@ -912,8 +1048,22 @@ async function handle(msg, st) {
|
|
|
912
1048
|
}
|
|
913
1049
|
return { found: false };
|
|
914
1050
|
}
|
|
915
|
-
|
|
916
|
-
|
|
1051
|
+
|
|
1052
|
+
// D3: Espera hasta que la pantalla "esté quieta" (sin mutaciones de DOM durante quietMs)
|
|
1053
|
+
if (msg.stable || msg.quiet || msg.condition === "stable") {
|
|
1054
|
+
while (Date.now() < deadline) {
|
|
1055
|
+
const res = await tell(tabId, 0, { cmd: "waitForCondition", stable: true, quietMs: msg.quietMs || 400 });
|
|
1056
|
+
if (res && res.ok && res.conditionMet) {
|
|
1057
|
+
return { ok: true, stable: true };
|
|
1058
|
+
}
|
|
1059
|
+
await sleep(250);
|
|
1060
|
+
}
|
|
1061
|
+
return { ok: true, stable: true, warning: "Tiempo límite alcanzado esperando quietud" };
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const ms = msg.ms || 2000;
|
|
1065
|
+
await sleep(ms);
|
|
1066
|
+
return { waited: ms };
|
|
917
1067
|
}
|
|
918
1068
|
|
|
919
1069
|
if (cmd === "screenshot") {
|
|
@@ -950,9 +1100,12 @@ async function handle(msg, st) {
|
|
|
950
1100
|
}
|
|
951
1101
|
|
|
952
1102
|
if (ids.length === 0) return { closed: 0 };
|
|
953
|
-
await chrome.tabs.remove(ids);
|
|
954
|
-
for (const id of ids) st.propias.delete(id);
|
|
955
1103
|
if (ids.includes(st.workingTabId)) st.workingTabId = null;
|
|
1104
|
+
for (const id of ids) st.propias.delete(id);
|
|
1105
|
+
await chrome.tabs.remove(ids);
|
|
1106
|
+
for (const id of ids) {
|
|
1107
|
+
await setTabOverlay(id, false);
|
|
1108
|
+
}
|
|
956
1109
|
return { closed: ids.length };
|
|
957
1110
|
}
|
|
958
1111
|
|
|
@@ -962,16 +1115,17 @@ async function handle(msg, st) {
|
|
|
962
1115
|
// no era de nadie, y el usuario tenia que deshacerlo a mano.
|
|
963
1116
|
const aSoltar = new Set(st.propias);
|
|
964
1117
|
if (st.workingTabId) aSoltar.add(st.workingTabId);
|
|
1118
|
+
st.groupId = null;
|
|
1119
|
+
st.workingTabId = null;
|
|
1120
|
+
st.propias.clear();
|
|
1121
|
+
st.refIndex.clear();
|
|
965
1122
|
for (const id of aSoltar) {
|
|
966
1123
|
try {
|
|
967
1124
|
await chrome.tabs.ungroup(id);
|
|
968
1125
|
} catch (_) {}
|
|
969
1126
|
await banner(id, null);
|
|
1127
|
+
await setTabOverlay(id, false);
|
|
970
1128
|
}
|
|
971
|
-
st.groupId = null;
|
|
972
|
-
st.workingTabId = null;
|
|
973
|
-
st.propias.clear();
|
|
974
|
-
st.refIndex.clear();
|
|
975
1129
|
return { ok: true, soltadas: aSoltar.size };
|
|
976
1130
|
}
|
|
977
1131
|
|
|
@@ -983,6 +1137,7 @@ async function handle(msg, st) {
|
|
|
983
1137
|
// with id") en vez de decir que ya no hay donde trabajar.
|
|
984
1138
|
try {
|
|
985
1139
|
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
1140
|
+
setTabOverlay(tabId, false);
|
|
986
1141
|
for (const link of links.values()) {
|
|
987
1142
|
if (!link.state) continue;
|
|
988
1143
|
link.state.propias.delete(tabId);
|
|
@@ -992,4 +1147,26 @@ try {
|
|
|
992
1147
|
});
|
|
993
1148
|
} catch (_) {}
|
|
994
1149
|
|
|
1150
|
+
// Al navegar o recargar la página, se reinyecta el content script y el overlay
|
|
1151
|
+
// se apaga. Si la pestaña sigue siendo de trabajo para alguna sesión, se vuelve a activar (B4).
|
|
1152
|
+
try {
|
|
1153
|
+
if (typeof chrome !== "undefined" && chrome.webNavigation && chrome.webNavigation.onCompleted && typeof chrome.webNavigation.onCompleted.addListener === "function") {
|
|
1154
|
+
chrome.webNavigation.onCompleted.addListener((details) => {
|
|
1155
|
+
if (details && details.frameId === 0 && tabEnUso(details.tabId)) {
|
|
1156
|
+
setTabOverlay(details.tabId, true);
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
} catch (_) {}
|
|
1161
|
+
|
|
1162
|
+
try {
|
|
1163
|
+
if (typeof chrome !== "undefined" && chrome.tabs && chrome.tabs.onUpdated && typeof chrome.tabs.onUpdated.addListener === "function") {
|
|
1164
|
+
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
|
|
1165
|
+
if (changeInfo && changeInfo.status === "complete" && tabEnUso(tabId)) {
|
|
1166
|
+
setTabOverlay(tabId, true);
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
} catch (_) {}
|
|
1171
|
+
|
|
995
1172
|
for (const p of WS_PORTS) connect(p);
|