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.
Files changed (55) hide show
  1. package/README.md +66 -0
  2. package/dist/agent/huecos.d.ts +58 -0
  3. package/dist/agent/huecos.js +81 -0
  4. package/dist/agent/loop.js +218 -41
  5. package/dist/agent/salidaDelAgente.d.ts +43 -0
  6. package/dist/agent/salidaDelAgente.js +39 -0
  7. package/dist/agent/toolGate.js +6 -1
  8. package/dist/config/engine.d.ts +21 -1
  9. package/dist/config/engine.js +22 -1
  10. package/dist/config/nodeVersion.d.ts +13 -0
  11. package/dist/config/nodeVersion.js +26 -0
  12. package/dist/index.js +22 -7
  13. package/dist/mcp/client.d.ts +28 -0
  14. package/dist/mcp/client.js +62 -8
  15. package/dist/mcp/ide.d.ts +76 -0
  16. package/dist/mcp/ide.js +160 -0
  17. package/dist/mcp/manager.js +9 -0
  18. package/dist/tools/browserExtension.js +153 -7
  19. package/dist/tools/computerUse.d.ts +1 -1
  20. package/dist/tools/computerUse.js +183 -2
  21. package/dist/tools/definitions.js +9 -2
  22. package/dist/tools/grepSearch.js +111 -23
  23. package/dist/tools/runner.js +1 -1
  24. package/dist/tools/toolDefsComputer.js +6 -2
  25. package/dist/tools/visionBridge.js +60 -3
  26. package/dist/tools/win/hostScript.js +154 -0
  27. package/dist/tools/writeFile.d.ts +16 -1
  28. package/dist/tools/writeFile.js +22 -1
  29. package/dist/ui/interrupt.d.ts +10 -0
  30. package/dist/ui/interrupt.js +53 -4
  31. package/dist/ui/keyboardGuard.d.ts +21 -0
  32. package/dist/ui/keyboardGuard.js +30 -1
  33. package/dist/ui/pegados.d.ts +50 -0
  34. package/dist/ui/pegados.js +83 -0
  35. package/dist/ui/pieFijo.d.ts +56 -0
  36. package/dist/ui/pieFijo.js +309 -0
  37. package/dist/ui/prompt.d.ts +5 -0
  38. package/dist/ui/prompt.js +109 -16
  39. package/dist/ui/reasoningStream.js +22 -12
  40. package/dist/ui/salida.d.ts +10 -0
  41. package/dist/ui/salida.js +50 -0
  42. package/dist/ui/spinner.d.ts +15 -8
  43. package/dist/ui/spinner.js +80 -42
  44. package/extension/background.js +191 -14
  45. package/extension/content.js +446 -48
  46. package/extension/fuentes/Chocolatito-Marca.ttf +0 -0
  47. package/extension/iconos/128.png +0 -0
  48. package/extension/iconos/16.png +0 -0
  49. package/extension/iconos/32.png +0 -0
  50. package/extension/iconos/48.png +0 -0
  51. package/extension/iconos/ORIGEN.md +48 -0
  52. package/extension/iconos/logotipo.png +0 -0
  53. package/extension/manifest.json +27 -4
  54. package/extension/popup.html +16 -6
  55. package/package.json +3 -2
@@ -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
- const res = await cdp(target, "Page.captureScreenshot", { format: "png", captureBeyondViewport: false });
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: "png" });
771
+ const dataUrl = await chrome.tabs.captureVisibleTab(windowId, { format: "jpeg", quality: 70 });
683
772
  return {
684
- data: String(dataUrl).replace(/^data:image\/png;base64,/, ""),
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) st.workingTabId = tabId;
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
- return { tabId, title: info.title || "", url: info.url || "", label: res.label, value: res.value, via: res.via, warning: res.warning };
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 deadline = Date.now() + (msg.timeout || 20000);
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
- await sleep(msg.ms || 2000);
916
- return { waited: msg.ms || 2000 };
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);