@arcadialdev/arcality 2.4.36 → 2.4.48
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/package.json +5 -3
- package/scripts/gen-and-run.mjs +60 -9
- package/src/EvidenceUploader.mjs +45 -0
- package/src/KnowledgeService.ts +13 -5
- package/src/arcalityClient.mjs +53 -9
- package/src/configLoader.mjs +2 -1
- package/src/index.ts +19 -1
- package/src/services/collectiveMemoryService.ts +98 -7
- package/tests/_helpers/agentic-runner.bundle.spec.js +847 -92
|
@@ -422,10 +422,12 @@ var KnowledgeService = class _KnowledgeService {
|
|
|
422
422
|
apiBase;
|
|
423
423
|
apiKey;
|
|
424
424
|
projectId = null;
|
|
425
|
+
orgId = null;
|
|
425
426
|
constructor() {
|
|
426
427
|
this.apiBase = process.env.ARCALITY_API_URL || "https://arcalityqadev.arcadial.lat";
|
|
427
428
|
this.apiKey = process.env.ARCALITY_API_KEY || "";
|
|
428
429
|
this.projectId = process.env.ARCALITY_PROJECT_ID || null;
|
|
430
|
+
this.orgId = process.env.ARCALITY_ORG_ID || null;
|
|
429
431
|
}
|
|
430
432
|
/**
|
|
431
433
|
* Fetch wrapper with a 10-second timeout to prevent hanging.
|
|
@@ -530,10 +532,13 @@ var KnowledgeService = class _KnowledgeService {
|
|
|
530
532
|
return null;
|
|
531
533
|
try {
|
|
532
534
|
const pathUrl = new URL(url).pathname;
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
535
|
+
let contextUrl = `${this.apiBase}/api/v1/portal/context?project_id=${pid}&path=${encodeURIComponent(pathUrl)}`;
|
|
536
|
+
if (this.orgId) {
|
|
537
|
+
contextUrl += `&organization_id=${this.orgId}`;
|
|
538
|
+
}
|
|
539
|
+
const res = await this.fetchWithTimeout(contextUrl, {
|
|
540
|
+
headers: { "x-api-key": this.apiKey }
|
|
541
|
+
});
|
|
537
542
|
if (!res.ok)
|
|
538
543
|
return null;
|
|
539
544
|
return await res.json();
|
|
@@ -558,6 +563,7 @@ var KnowledgeService = class _KnowledgeService {
|
|
|
558
563
|
"x-api-key": this.apiKey
|
|
559
564
|
},
|
|
560
565
|
body: JSON.stringify({
|
|
566
|
+
organization_id: this.orgId,
|
|
561
567
|
project_id: pid,
|
|
562
568
|
rule_type: "UI_UX",
|
|
563
569
|
// Default según especificación
|
|
@@ -585,6 +591,7 @@ var KnowledgeService = class _KnowledgeService {
|
|
|
585
591
|
"x-api-key": this.apiKey
|
|
586
592
|
},
|
|
587
593
|
body: JSON.stringify({
|
|
594
|
+
organization_id: this.orgId,
|
|
588
595
|
project_id: pid,
|
|
589
596
|
field_identifier: identifier,
|
|
590
597
|
field_type: type,
|
|
@@ -611,6 +618,7 @@ var KnowledgeService = class _KnowledgeService {
|
|
|
611
618
|
"x-api-key": this.apiKey
|
|
612
619
|
},
|
|
613
620
|
body: JSON.stringify({
|
|
621
|
+
organization_id: this.orgId,
|
|
614
622
|
project_id: pid,
|
|
615
623
|
doc_type: docType,
|
|
616
624
|
title,
|
|
@@ -635,12 +643,29 @@ var KnowledgeService = class _KnowledgeService {
|
|
|
635
643
|
var getBase = () => process.env.ARCALITY_API_URL ? `${process.env.ARCALITY_API_URL}/api/v1` : null;
|
|
636
644
|
var getKey = () => process.env.ARCALITY_API_KEY || "";
|
|
637
645
|
var getPid = () => process.env.ARCALITY_PROJECT_ID || "";
|
|
646
|
+
var getOrgId = () => process.env.ARCALITY_ORG_ID;
|
|
647
|
+
var getMissionId = () => process.env.ARCALITY_MISSION_ID || EMPTY_GUID;
|
|
638
648
|
var EMPTY_GUID = "00000000-0000-0000-0000-000000000000";
|
|
649
|
+
var configWarningLogged = false;
|
|
639
650
|
function isConfigured() {
|
|
640
651
|
const base = getBase();
|
|
641
652
|
const key = getKey();
|
|
642
653
|
const pid = getPid();
|
|
643
|
-
|
|
654
|
+
const orgId = getOrgId();
|
|
655
|
+
if (!base || !key || !pid || pid === EMPTY_GUID || !orgId) {
|
|
656
|
+
if (!configWarningLogged) {
|
|
657
|
+
const missing = [];
|
|
658
|
+
if (!base)
|
|
659
|
+
missing.push("ARCALITY_API_URL");
|
|
660
|
+
if (!key)
|
|
661
|
+
missing.push("ARCALITY_API_KEY");
|
|
662
|
+
if (!pid || pid === EMPTY_GUID)
|
|
663
|
+
missing.push("ARCALITY_PROJECT_ID");
|
|
664
|
+
if (!orgId)
|
|
665
|
+
missing.push("ARCALITY_ORG_ID");
|
|
666
|
+
console.warn(`[CollectiveMemory] Service disabled. Missing: ${missing.join(", ")}`);
|
|
667
|
+
configWarningLogged = true;
|
|
668
|
+
}
|
|
644
669
|
return false;
|
|
645
670
|
}
|
|
646
671
|
return true;
|
|
@@ -656,6 +681,7 @@ async function pushRule(rule) {
|
|
|
656
681
|
"x-api-key": getKey()
|
|
657
682
|
},
|
|
658
683
|
body: JSON.stringify({
|
|
684
|
+
organization_id: getOrgId(),
|
|
659
685
|
project_id: getPid(),
|
|
660
686
|
rule_type: rule.rule_type,
|
|
661
687
|
title: rule.title.substring(0, 100),
|
|
@@ -690,6 +716,7 @@ async function pushField(field) {
|
|
|
690
716
|
"x-api-key": getKey()
|
|
691
717
|
},
|
|
692
718
|
body: JSON.stringify({
|
|
719
|
+
organization_id: getOrgId(),
|
|
693
720
|
project_id: getPid(),
|
|
694
721
|
page_id: field.page_id ?? null,
|
|
695
722
|
field_identifier: field.field_identifier.substring(0, 100),
|
|
@@ -719,6 +746,7 @@ async function pushKnowledge(knowledge) {
|
|
|
719
746
|
"x-api-key": getKey()
|
|
720
747
|
},
|
|
721
748
|
body: JSON.stringify({
|
|
749
|
+
organization_id: getOrgId(),
|
|
722
750
|
project_id: getPid(),
|
|
723
751
|
doc_type: knowledge.doc_type,
|
|
724
752
|
title: knowledge.title.substring(0, 150),
|
|
@@ -738,12 +766,77 @@ async function pushKnowledge(knowledge) {
|
|
|
738
766
|
return null;
|
|
739
767
|
}
|
|
740
768
|
}
|
|
769
|
+
async function searchPromptPattern(prompt, limit = 5) {
|
|
770
|
+
if (!isConfigured())
|
|
771
|
+
return [];
|
|
772
|
+
try {
|
|
773
|
+
const apiUrl = process.env.ARCALITY_API_URL ? `${process.env.ARCALITY_API_URL}/api/v1/prompt-execution-patterns/search` : `${getBase()}/prompt-execution-patterns/search`;
|
|
774
|
+
const payload = {
|
|
775
|
+
organization_id: getOrgId(),
|
|
776
|
+
project_id: getPid(),
|
|
777
|
+
prompt,
|
|
778
|
+
limit
|
|
779
|
+
};
|
|
780
|
+
console.log(`[PatternSearch] \u{1F50D} Buscando patrones en: ${apiUrl}`);
|
|
781
|
+
console.log(`[PatternSearch] \u{1F4E6} Payload: ${JSON.stringify(payload, null, 2)}`);
|
|
782
|
+
const res = await fetch(apiUrl, {
|
|
783
|
+
method: "POST",
|
|
784
|
+
headers: {
|
|
785
|
+
"Content-Type": "application/json",
|
|
786
|
+
"x-api-key": getKey()
|
|
787
|
+
},
|
|
788
|
+
body: JSON.stringify(payload)
|
|
789
|
+
});
|
|
790
|
+
if (!res.ok) {
|
|
791
|
+
console.warn(`[PatternSearch] HTTP ${res.status} from ${apiUrl}`);
|
|
792
|
+
return [];
|
|
793
|
+
}
|
|
794
|
+
const data = await res.json();
|
|
795
|
+
const results = Array.isArray(data) ? data : data.matches || data.results || [];
|
|
796
|
+
console.log(`[PatternSearch] \u2705 ${results.length} patrones encontrados.`);
|
|
797
|
+
return results;
|
|
798
|
+
} catch (err) {
|
|
799
|
+
console.warn(`[PatternSearch] \u26A0\uFE0F Error buscando patrones: ${err?.message || "unknown"}`);
|
|
800
|
+
return [];
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
async function savePromptPattern(patternData) {
|
|
804
|
+
if (!isConfigured())
|
|
805
|
+
return false;
|
|
806
|
+
try {
|
|
807
|
+
const apiUrl = process.env.ARCALITY_API_URL ? `${process.env.ARCALITY_API_URL}/api/v1/prompt-execution-patterns` : `${getBase()}/prompt-execution-patterns`;
|
|
808
|
+
const payload = {
|
|
809
|
+
organization_id: getOrgId(),
|
|
810
|
+
project_id: getPid(),
|
|
811
|
+
mission_id: getMissionId(),
|
|
812
|
+
...patternData
|
|
813
|
+
};
|
|
814
|
+
console.log(`[PatternSave] \u{1F4E4} POST ${apiUrl} | org=${payload.organization_id} proj=${payload.project_id} mission=${payload.mission_id}`);
|
|
815
|
+
const res = await fetch(apiUrl, {
|
|
816
|
+
method: "POST",
|
|
817
|
+
headers: {
|
|
818
|
+
"Content-Type": "application/json",
|
|
819
|
+
"x-api-key": getKey()
|
|
820
|
+
},
|
|
821
|
+
body: JSON.stringify(payload)
|
|
822
|
+
});
|
|
823
|
+
if (!res.ok) {
|
|
824
|
+
console.warn(`[PatternSave] \u26A0\uFE0F HTTP ${res.status} al guardar patr\xF3n en ${apiUrl}: ${await res.text().catch(() => "")}`);
|
|
825
|
+
} else {
|
|
826
|
+
console.log(`[PatternSave] \u2705 Patr\xF3n guardado exitosamente.`);
|
|
827
|
+
}
|
|
828
|
+
return res.ok;
|
|
829
|
+
} catch (err) {
|
|
830
|
+
console.warn(`[PatternSave] \u26A0\uFE0F Error guardando patr\xF3n: ${err?.message || "unknown"}`);
|
|
831
|
+
return false;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
741
834
|
async function getKnownFieldIdentifiers(pathUrl) {
|
|
742
835
|
if (!isConfigured())
|
|
743
836
|
return [];
|
|
744
837
|
try {
|
|
745
838
|
const res = await fetch(
|
|
746
|
-
`${getBase()}/portal/context?project_id=${getPid()}&path=${encodeURIComponent(pathUrl)}`,
|
|
839
|
+
`${getBase()}/portal/context?organization_id=${getOrgId()}&project_id=${getPid()}&path=${encodeURIComponent(pathUrl)}`,
|
|
747
840
|
{ headers: { "x-api-key": getKey() } }
|
|
748
841
|
);
|
|
749
842
|
if (!res.ok)
|
|
@@ -801,6 +894,8 @@ var AIAgentHelper = class {
|
|
|
801
894
|
logs = [];
|
|
802
895
|
checkpoints = /* @__PURE__ */ new Map();
|
|
803
896
|
sessionStorage = /* @__PURE__ */ new Map();
|
|
897
|
+
criticalNetworkError = null;
|
|
898
|
+
criticalJsError = null;
|
|
804
899
|
constructor(page, contextDir = "out", testInfo, knowledgeService) {
|
|
805
900
|
this.page = page;
|
|
806
901
|
this.contextDir = contextDir;
|
|
@@ -815,10 +910,44 @@ var AIAgentHelper = class {
|
|
|
815
910
|
});
|
|
816
911
|
this.page.on("requestfailed", (request) => {
|
|
817
912
|
const failure = request.failure();
|
|
913
|
+
const resourceType = request.resourceType();
|
|
914
|
+
const url = request.url();
|
|
915
|
+
if (["fetch", "xhr", "document"].includes(resourceType)) {
|
|
916
|
+
if (!url.includes("google") && !url.includes("analytics") && !url.includes("telemetry")) {
|
|
917
|
+
if (resourceType === "document" || request.method() !== "GET") {
|
|
918
|
+
this.criticalNetworkError = `Network Request Failed (${request.method()}): ${url} - ${failure?.errorText || "Aborted"}`;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
818
922
|
this.logs.push(`[NETWORK_ERROR] ${request.method()} ${request.url()} - ${failure?.errorText || "Unknown error"}`);
|
|
819
923
|
if (this.logs.length > 30)
|
|
820
924
|
this.logs.shift();
|
|
821
925
|
});
|
|
926
|
+
this.page.on("response", (response) => {
|
|
927
|
+
const status = response.status();
|
|
928
|
+
if (status >= 500) {
|
|
929
|
+
const request = response.request();
|
|
930
|
+
const resourceType = request.resourceType();
|
|
931
|
+
const url = response.url();
|
|
932
|
+
if (["fetch", "xhr", "document"].includes(resourceType)) {
|
|
933
|
+
if (!url.includes("google") && !url.includes("analytics") && !url.includes("telemetry")) {
|
|
934
|
+
if (resourceType === "document" || request.method() !== "GET") {
|
|
935
|
+
this.criticalNetworkError = `HTTP Server Error ${status}: ${request.method()} ${url}`;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
this.page.on("pageerror", (exception) => {
|
|
942
|
+
this.criticalJsError = `Excepci\xF3n JS no manejada: ${exception.message}`;
|
|
943
|
+
this.logs.push(`[JS_CRASH] ${exception.message}`);
|
|
944
|
+
});
|
|
945
|
+
this.page.on("crash", () => {
|
|
946
|
+
this.criticalJsError = `Browser Crash: La pesta\xF1a del navegador colaps\xF3 inesperadamente.`;
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
clearNetworkError() {
|
|
950
|
+
this.criticalNetworkError = null;
|
|
822
951
|
}
|
|
823
952
|
loadSkills() {
|
|
824
953
|
try {
|
|
@@ -920,16 +1049,21 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
920
1049
|
const isVisible = (el) => {
|
|
921
1050
|
const style = window.getComputedStyle(el);
|
|
922
1051
|
const rect = el.getBoundingClientRect();
|
|
923
|
-
const
|
|
924
|
-
const
|
|
1052
|
+
const tag = el.tagName.toLowerCase();
|
|
1053
|
+
const isInput = tag === "input" || tag === "textarea" || tag === "select";
|
|
1054
|
+
const isHiddenInput = tag === "input" && el.getAttribute("type") === "hidden";
|
|
1055
|
+
const hasSize = isInput && !isHiddenInput ? true : rect.width > 1 && rect.height > 1;
|
|
1056
|
+
const isNotTranslucent = isInput && !isHiddenInput ? true : parseFloat(style.opacity || "1") > 0.05;
|
|
925
1057
|
const isCssVisible = style.display !== "none" && style.visibility !== "hidden";
|
|
926
1058
|
return isCssVisible && isNotTranslucent && hasSize;
|
|
927
1059
|
};
|
|
928
|
-
const nodes = Array.from(document.querySelectorAll('button, input, select, textarea, [role], a, h1, h2, label, span, p, i, svg, img, [class*="alert"],[class*="toast"],[class*="message"],[class*="error"],[class*="success"],[id*="alert"],[id*="toast"],[role="alert"],[role="status"],.error-message,.success-message'));
|
|
1060
|
+
const nodes = Array.from(document.querySelectorAll('button, input, select, textarea, [role], a, h1, h2, label, span, p, i, svg, img, igx-input-group, igx-select, igx-combo, [role="gridcell"], [role="row"], [role="listitem"], [class*="radio"], [class*="checkbox"], [data-state], [aria-selected], [class*="alert"],[class*="toast"],[class*="message"],[class*="error"],[class*="success"],[id*="alert"],[id*="toast"],[role="alert"],[role="status"],.error-message,.success-message'));
|
|
929
1061
|
let localCount = 0;
|
|
1062
|
+
const nameCounts = {};
|
|
930
1063
|
return nodes.filter((el) => {
|
|
931
1064
|
const htmlEl = el;
|
|
932
|
-
|
|
1065
|
+
const tag = htmlEl.tagName.toLowerCase();
|
|
1066
|
+
if (tag === "input" || tag === "textarea" || tag === "select") {
|
|
933
1067
|
return window.getComputedStyle(htmlEl).display !== "none";
|
|
934
1068
|
}
|
|
935
1069
|
return isVisible(htmlEl);
|
|
@@ -939,23 +1073,33 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
939
1073
|
const role = htmlEl.getAttribute("role") || "";
|
|
940
1074
|
const text = (htmlEl.innerText || htmlEl.getAttribute("aria-label") || htmlEl.getAttribute("title") || "").trim().replace(/\s+/g, " ");
|
|
941
1075
|
const textLower = text.toLowerCase();
|
|
942
|
-
const isCriticalFailure = (textLower.includes("error") || textLower.includes("fall\xF3") || textLower.includes("no se puede") || textLower.includes("ya existe") || textLower.includes("
|
|
1076
|
+
const isCriticalFailure = (textLower.includes("error") || textLower.includes("fall\xF3") || textLower.includes("no se puede") || textLower.includes("ya existe") || textLower.includes("inv\xE1lido") || textLower.includes("incorrecto") || textLower.includes("ligado") || textLower.includes("depende") || textLower.includes("duplicado") || textLower.includes("repetido") || /* Validaciones ZOD / Wizard de campos de formulario */
|
|
943
1077
|
textLower.includes("debe ser mayor") || textLower.includes("debe ser menor") || textLower.includes("debe ser igual") || textLower.includes("debe ser mayor o igual") || textLower.includes("debe ser menor o igual") || /* ZOD date messages (Spanish) */
|
|
944
1078
|
textLower.includes("debe ser posterior") || textLower.includes("debe ser anterior") || textLower.includes("debe ser una fecha") || textLower.includes("fecha debe ser") || textLower.includes("posterior o igual a la fecha") || textLower.includes("anterior o igual a la fecha") || textLower.includes("posterior a la fecha") || textLower.includes("anterior a la fecha") || textLower.includes("fecha") && (textLower.includes("actual") || textLower.includes("pasada") || textLower.includes("futura") || textLower.includes("requerida") || textLower.includes("inv\xE1lida") || textLower.includes("incorrecta") || textLower.includes("posterior") || textLower.includes("anterior")) || /* ZOD number/range messages */
|
|
945
|
-
textLower.includes("debe ser un n\xFAmero") || textLower.includes("debe ser positivo") || textLower.includes("debe ser negativo") || textLower.includes("m\xEDnimo") && textLower.includes("caracteres") || textLower.includes("m\xE1ximo") && textLower.includes("caracteres") ||
|
|
946
|
-
textLower.includes("campo requerido") || textLower.includes("campo obligatorio") || textLower.includes("requerido") || textLower.includes("obligatorio") || textLower.includes("no puede estar vac\xEDo") || textLower.includes("no puede ser") || textLower.includes("no es v\xE1lido") || textLower.includes("no es correcto") || textLower.includes("
|
|
1079
|
+
textLower.includes("debe ser un n\xFAmero") || textLower.includes("debe ser positivo") || textLower.includes("debe ser negativo") || textLower.includes("m\xEDnimo") && textLower.includes("caracteres") || textLower.includes("m\xE1ximo") && textLower.includes("caracteres") || /* Generic ZOD/form required — must be specific, NOT broad instructional text */
|
|
1080
|
+
textLower.includes("campo requerido") || textLower.includes("campo obligatorio") || textLower === "requerido" || textLower === "obligatorio" || textLower.includes("este campo es requerido") || textLower.includes("este campo es obligatorio") || textLower.includes("no puede estar vac\xEDo") || textLower.includes("no puede ser") || textLower.includes("no es v\xE1lido") || textLower.includes("no es correcto") || textLower.includes("formato incorrecto") || textLower.includes("formato inv\xE1lido") || textLower.includes("valor no permitido")) && text.length < 200 && !textLower.includes("recuerda") && !textLower.includes("antes de guardar");
|
|
947
1081
|
const isCriticalSuccess = (textLower.includes("exitosamente") || textLower.includes("guardado") || textLower.includes("creado") || textLower.includes("success") || textLower.includes("correctamente") && text.length < 100) && text.length < 200;
|
|
948
1082
|
const isCriticalFeedback = isCriticalFailure || isCriticalSuccess;
|
|
949
|
-
|
|
950
|
-
|
|
1083
|
+
const isFormSelection = tag === "input" && (htmlEl.getAttribute("type") === "radio" || htmlEl.getAttribute("type") === "checkbox") || (role === "radio" || role === "checkbox" || role === "switch");
|
|
1084
|
+
if (!isCriticalFeedback && (tag === "span" || tag === "p" || tag === "label" || tag === "i" || tag === "svg" || tag === "img" || tag === "div" || tag === "input" || tag === "textarea" || tag === "select")) {
|
|
1085
|
+
const hasIdentification = htmlEl.hasAttribute("aria-label") || htmlEl.hasAttribute("title") || htmlEl.hasAttribute("name") || htmlEl.hasAttribute("placeholder") || role || htmlEl.id;
|
|
951
1086
|
if (text.length > 300 && !isCriticalFeedback && !hasIdentification)
|
|
952
1087
|
return null;
|
|
953
|
-
if (text.length < 2 && !hasIdentification && tag !== "svg" && tag !== "img" && tag !== "i")
|
|
1088
|
+
if (text.length < 2 && !hasIdentification && tag !== "svg" && tag !== "img" && tag !== "i" && !isFormSelection && tag !== "input" && tag !== "textarea" && tag !== "select")
|
|
1089
|
+
return null;
|
|
1090
|
+
}
|
|
1091
|
+
if (tag !== "form" && tag !== "table" && tag !== "tr" && tag !== "body") {
|
|
1092
|
+
const interactiveChildren = htmlEl.querySelectorAll('button, input, select, textarea, a, [role="button"], [role="checkbox"], [role="radio"]');
|
|
1093
|
+
if (interactiveChildren.length >= 2) {
|
|
954
1094
|
return null;
|
|
1095
|
+
}
|
|
955
1096
|
}
|
|
956
1097
|
const idx = startIdx + localCount++;
|
|
957
1098
|
htmlEl.setAttribute("agent-idx", idx.toString());
|
|
958
1099
|
let name = text || htmlEl.getAttribute("aria-label") || htmlEl.getAttribute("title") || htmlEl.getAttribute("placeholder");
|
|
1100
|
+
if (name && name.length > 150) {
|
|
1101
|
+
name = name.substring(0, 147) + "...";
|
|
1102
|
+
}
|
|
959
1103
|
if (!name && (tag === "input" || tag === "select" || htmlEl.getAttribute("role") === "combobox")) {
|
|
960
1104
|
const labelledBy = htmlEl.getAttribute("aria-labelledby");
|
|
961
1105
|
if (labelledBy) {
|
|
@@ -986,6 +1130,30 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
986
1130
|
tries++;
|
|
987
1131
|
}
|
|
988
1132
|
}
|
|
1133
|
+
if (!name) {
|
|
1134
|
+
let next = htmlEl.nextSibling;
|
|
1135
|
+
let tries = 0;
|
|
1136
|
+
while (next && tries < 3) {
|
|
1137
|
+
if (next.nodeType === 3) {
|
|
1138
|
+
const textContent = (next.textContent || "").trim();
|
|
1139
|
+
if (textContent.length > 1) {
|
|
1140
|
+
name = textContent;
|
|
1141
|
+
break;
|
|
1142
|
+
}
|
|
1143
|
+
} else if (next.nodeType === 1) {
|
|
1144
|
+
const nextEl = next;
|
|
1145
|
+
if (nextEl.tagName !== "BUTTON" && nextEl.tagName !== "INPUT") {
|
|
1146
|
+
const nextText = (nextEl.innerText || "").trim();
|
|
1147
|
+
if (nextText.length > 1 && nextText.length < 150) {
|
|
1148
|
+
name = nextText;
|
|
1149
|
+
break;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
next = next.nextSibling;
|
|
1154
|
+
tries++;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
989
1157
|
if (!name) {
|
|
990
1158
|
const container = htmlEl.closest("div, fieldset, section");
|
|
991
1159
|
if (container) {
|
|
@@ -997,6 +1165,15 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
997
1165
|
}
|
|
998
1166
|
}
|
|
999
1167
|
}
|
|
1168
|
+
if (!name && (isFormSelection || tag === "input")) {
|
|
1169
|
+
const parentContainer = htmlEl.closest('li, tr, .row, [class*="item"], [class*="card"], div:has(> span, > p)');
|
|
1170
|
+
if (parentContainer) {
|
|
1171
|
+
const containerText = (parentContainer.innerText || "").trim();
|
|
1172
|
+
if (containerText.length > 1 && containerText.length < 300) {
|
|
1173
|
+
name = containerText.split("\n").filter(Boolean).join(" ");
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1000
1177
|
}
|
|
1001
1178
|
if (!name) {
|
|
1002
1179
|
const iconMatch = htmlEl.outerHTML.match(/fa-([a-z0-9-]+)|md-([a-z0-9-]+)|bi-([a-z0-9-]+)/i);
|
|
@@ -1017,7 +1194,7 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
1017
1194
|
const isError = isCriticalFailure || isErrorClass && text.split(" ").length > 2;
|
|
1018
1195
|
const isSuccess = classLower.includes("success") || isCriticalSuccess || textLower.includes("\xE9xito");
|
|
1019
1196
|
const isTab = role === "tab" || classLower.includes("tab") || classLower.includes("pesta\xF1a");
|
|
1020
|
-
if (tag === "input" || tag === "textarea" || tag === "select")
|
|
1197
|
+
if (tag === "input" || tag === "textarea" || tag === "select" || isFormSelection)
|
|
1021
1198
|
type = "FIELD";
|
|
1022
1199
|
else if (tag === "button" || role === "button" || tag === "a" || role === "link" || isTab)
|
|
1023
1200
|
type = "ACTION";
|
|
@@ -1045,6 +1222,11 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
1045
1222
|
}
|
|
1046
1223
|
if (!name)
|
|
1047
1224
|
name = tag;
|
|
1225
|
+
const baseName = name;
|
|
1226
|
+
nameCounts[baseName] = (nameCounts[baseName] || 0) + 1;
|
|
1227
|
+
if (nameCounts[baseName] > 1) {
|
|
1228
|
+
name = `${baseName} - ocur ${nameCounts[baseName]}`;
|
|
1229
|
+
}
|
|
1048
1230
|
let score = 10;
|
|
1049
1231
|
if (type === "MESSAGE")
|
|
1050
1232
|
score = 200;
|
|
@@ -1077,6 +1259,25 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
1077
1259
|
}
|
|
1078
1260
|
}
|
|
1079
1261
|
console.log(`>>ARCALITY_STATUS>> \u{1F50D} Percepci\xF3n profunda encontr\xF3 ${allComponents.length} componentes`);
|
|
1262
|
+
const urlForBlankCheck = this.page.url();
|
|
1263
|
+
const isAuthPage = urlForBlankCheck.includes("/login") || urlForBlankCheck.includes("/auth") || urlForBlankCheck.includes("oauth") || urlForBlankCheck.includes("/#/");
|
|
1264
|
+
if (allComponents.length < 10 && !isAuthPage) {
|
|
1265
|
+
const bodyTextLength = await this.page.evaluate(() => document.body.innerText.trim().length).catch(() => 999);
|
|
1266
|
+
if (bodyTextLength < 80) {
|
|
1267
|
+
console.warn(`
|
|
1268
|
+
\u26A0\uFE0F [BLANK_STATE] Primera medici\xF3n: ${allComponents.length} componentes, ${bodyTextLength} chars. Esperando re-render...`);
|
|
1269
|
+
await this.page.waitForTimeout(2500);
|
|
1270
|
+
const bodyTextLength2 = await this.page.evaluate(() => document.body.innerText.trim().length).catch(() => 999);
|
|
1271
|
+
const components2 = await this.page.evaluate(() => document.querySelectorAll("a, button, input, select, textarea").length).catch(() => 999);
|
|
1272
|
+
if (bodyTextLength2 < 80 && components2 < 10) {
|
|
1273
|
+
console.error(`
|
|
1274
|
+
\u{1F6D1} [BLANK_STATE_CRASH] Vista en blanco confirmada tras espera (Comps: ${components2}, Texto: ${bodyTextLength2} chars). URL: ${urlForBlankCheck}`);
|
|
1275
|
+
throw new Error(`[BLANK_STATE_CRASH] El portal carg\xF3 en blanco. Esto es un fallo de renderizado del portal, no del agente.`);
|
|
1276
|
+
} else {
|
|
1277
|
+
console.log(`>>ARCALITY_STATUS>> \u2705 [BLANK_STATE] Falsa alarma. P\xE1gina recuper\xF3 contenido tras espera (${components2} comps, ${bodyTextLength2} chars).`);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1080
1281
|
const sortedComponents = allComponents.sort((a, b) => b.score - a.score).slice(0, 500);
|
|
1081
1282
|
const title = await this.page.title();
|
|
1082
1283
|
const url = this.page.url();
|
|
@@ -1214,7 +1415,7 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
1214
1415
|
return false;
|
|
1215
1416
|
const matches = words.filter((w) => stripDynamic(memPrompt).includes(w)).length;
|
|
1216
1417
|
const matchRatio = matches / words.length;
|
|
1217
|
-
if (matchRatio < 0.
|
|
1418
|
+
if (matchRatio < 0.85)
|
|
1218
1419
|
return false;
|
|
1219
1420
|
const memVerbs = ["elimina", "borra", "quita", "delete", "remove", "crea", "nueva", "a\xF1ade", "add", "create", "new", "edita", "modifica", "cambia", "edit", "update"].filter((v) => memPrompt.includes(v));
|
|
1220
1421
|
const hasSharedVerb = currentVerbs.some((cv) => memVerbs.includes(cv));
|
|
@@ -1241,16 +1442,48 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
1241
1442
|
});
|
|
1242
1443
|
if (matchingRun) {
|
|
1243
1444
|
const memStep = matchingRun.steps_data[historyLength];
|
|
1244
|
-
|
|
1445
|
+
let bestMatch = null;
|
|
1446
|
+
let bestMatchScore = -1;
|
|
1447
|
+
for (const c of state.components) {
|
|
1245
1448
|
const cleanMemName = (memStep.componentName || "").replace(/\[.*?\]/g, "").replace(/\(.*?\)/g, "").trim().toLowerCase();
|
|
1246
1449
|
const cleanCurrName = (c.name || "").replace(/\[.*?\]/g, "").replace(/\(.*?\)/g, "").trim().toLowerCase();
|
|
1247
|
-
|
|
1248
|
-
|
|
1450
|
+
let score = -1;
|
|
1451
|
+
if (c.selector === memStep.action_data?.selector) {
|
|
1452
|
+
score = 100;
|
|
1453
|
+
} else if (cleanMemName !== "") {
|
|
1454
|
+
if (cleanMemName === cleanCurrName) {
|
|
1455
|
+
score = 90;
|
|
1456
|
+
} else if (cleanCurrName.includes(cleanMemName)) {
|
|
1457
|
+
const lengthDiff = cleanCurrName.length - cleanMemName.length;
|
|
1458
|
+
if (lengthDiff < 10)
|
|
1459
|
+
score = 80;
|
|
1460
|
+
else if (lengthDiff < 30)
|
|
1461
|
+
score = 60;
|
|
1462
|
+
else if (lengthDiff < 80)
|
|
1463
|
+
score = 40;
|
|
1464
|
+
else
|
|
1465
|
+
score = 5;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
if (score > bestMatchScore) {
|
|
1469
|
+
bestMatchScore = score;
|
|
1470
|
+
bestMatch = c;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
const foundComponent = bestMatchScore >= 40 ? bestMatch : null;
|
|
1249
1474
|
if (!foundComponent && memStep.action_data?.action !== "wait") {
|
|
1250
|
-
console.log(`>>ARCALITY_STATUS>> \u23F3 Componente target no visible ("${memStep.componentName}"). Forzando espera de transici\xF3n DOM...`);
|
|
1251
1475
|
console.log(`>>ARCALITY_STATUS>> \u{1F50D} Descartado (Componente visual no encontrado: ${memStep.componentName})`);
|
|
1252
1476
|
return null;
|
|
1253
1477
|
}
|
|
1478
|
+
if (foundComponent && memStep.action_data?.action === "fill") {
|
|
1479
|
+
const compType = (foundComponent.type || "").toLowerCase();
|
|
1480
|
+
const compName = (foundComponent.name || "").toLowerCase();
|
|
1481
|
+
const looksLikeDropdown = compType.includes("select") || compType.includes("combobox") || compName.includes("tipo") || compName.includes("type") || compName.includes("categor\xEDa") || compName.includes("estado") || compName.includes("frecuencia") || compName.includes("alcance");
|
|
1482
|
+
if (looksLikeDropdown) {
|
|
1483
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F50D} Descartado (fill en dropdown detectado: ${foundComponent.name}). Delegando a IA.`);
|
|
1484
|
+
return null;
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1254
1487
|
const memCompName = memStep.componentName || "";
|
|
1255
1488
|
if (memCompName.length > 80) {
|
|
1256
1489
|
console.log(`>>ARCALITY_STATUS>> \u{1F50D} Descartado (Mensaje enorme o nombre inconsistente: ${memCompName.substring(0, 20)}...)`);
|
|
@@ -1260,10 +1493,23 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
1260
1493
|
return null;
|
|
1261
1494
|
}
|
|
1262
1495
|
if (foundComponent) {
|
|
1496
|
+
let actionData = { ...memStep.action_data };
|
|
1497
|
+
if (actionData.value === "{{secret}}") {
|
|
1498
|
+
const cName = (foundComponent.name || "").toLowerCase();
|
|
1499
|
+
if (cName.includes("password") || cName.includes("contrase\xF1a")) {
|
|
1500
|
+
actionData.value = "Qa_Test123!";
|
|
1501
|
+
} else if (cName.includes("email") || cName.includes("correo")) {
|
|
1502
|
+
actionData.value = `qa_${Date.now()}@test.com`;
|
|
1503
|
+
} else if (cName.includes("descrip")) {
|
|
1504
|
+
actionData.value = `Descripci\xF3n QA Automatizada ${Date.now()}`;
|
|
1505
|
+
} else {
|
|
1506
|
+
actionData.value = `QA_Test_${Date.now()}`;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1263
1509
|
return {
|
|
1264
|
-
thought: `[GU\xCDA DE \xC9XITO] Reutilizando paso maestro aprendido: ${
|
|
1510
|
+
thought: `[GU\xCDA DE \xC9XITO] Reutilizando paso maestro aprendido: ${actionData.action} en ${foundComponent.name}`,
|
|
1265
1511
|
actions: [{
|
|
1266
|
-
...
|
|
1512
|
+
...actionData,
|
|
1267
1513
|
idx: foundComponent.idx,
|
|
1268
1514
|
description: foundComponent.name,
|
|
1269
1515
|
selector: foundComponent.selector,
|
|
@@ -1468,12 +1714,14 @@ Every turn, you MUST follow these 4 phases:
|
|
|
1468
1714
|
## Phase 3: ACT
|
|
1469
1715
|
- Execute your planned actions using \`perform_ui_actions\`.
|
|
1470
1716
|
- Group multiple related actions in one call (e.g., click+fill for 3 fields = 6 actions in one call).
|
|
1471
|
-
- For dropdowns
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
-
|
|
1475
|
-
- **
|
|
1717
|
+
- For comboboxes and dropdowns (CRITICAL PROTOCOL):
|
|
1718
|
+
- **STEP 1 (MANDATORY)**: Click the dropdown/combobox trigger to OPEN it. Wait for the list to appear.
|
|
1719
|
+
- **STEP 2 (MANDATORY, SAME or NEXT turn)**: Click the desired option directly (\`[li]\` or \`[option]\`). Do NOT use \`fill\` to type text.
|
|
1720
|
+
- **STEP 3**: If more fields remain in the same form section, do NOT group them in the same turn. Wait for re-render first.
|
|
1721
|
+
- **NEVER** insert a \`wait\` action between clicking the trigger and clicking the option \u2014 complete both in one turn.
|
|
1722
|
+
- **STRICTLY FORBIDDEN**: Sending Escape repeatedly. After selecting an option, click the NEXT logical button directly.
|
|
1476
1723
|
- For menus: Click menu icon \u2192 WAIT for next turn \u2192 Click option.
|
|
1724
|
+
- For \`<input type="date">\` native fields: NEVER use \`fill\`. ALWAYS use \`interact_native_control\` with value in YYYY-MM-DD format. Do this ONCE per field. If it fails, do NOT retry \u2014 move on and click Siguiente to see the validation message.
|
|
1477
1725
|
|
|
1478
1726
|
## Phase 4: VERIFY
|
|
1479
1727
|
- After critical actions (SAVE/CREATE/DELETE), verify the result:
|
|
@@ -1505,6 +1753,11 @@ These are NOT optional. You MUST use these tools in these situations:
|
|
|
1505
1753
|
# CRITICAL RULES
|
|
1506
1754
|
1. **NEVER BLIND-RETRY**: If an action fails, you MUST investigate with \`inspect_element_details\` before retrying. Repeating the exact same failed action is FORBIDDEN.
|
|
1507
1755
|
2. **NO REPEAT ACTIONS**: Check HISTORY. If you already filled a field or clicked a button, do NOT do it again unless the UI clearly reset OR the button belongs to a different step/modal/section mentioned in the mission (e.g., Save in modal vs Save in main form).
|
|
1756
|
+
3. **GROUP ACTIONS CAREFULLY**: You MUST group non-conflicting actions in a single turn to save time and tokens.
|
|
1757
|
+
- You can \`fill\` multiple plain text inputs in the SAME turn (Nombre, Descripci\xF3n, etc.).
|
|
1758
|
+
- You CANNOT mix dropdown selection with filling other fields in the same turn (React re-renders invalidate idx).
|
|
1759
|
+
- For \`<input type="date">\` fields: use \`interact_native_control\` ONCE per field \u2014 do NOT retry with \`fill\` or \`click\`.
|
|
1760
|
+
- NEVER insert a \`wait\` action between related dropdown steps (click trigger \u2192 click option). It breaks the sequence.
|
|
1508
1761
|
|
|
1509
1762
|
3. **CURRENT STATE > MEMORY**: What you see in CURRENT COMPONENTS is the truth. SUCCESS MEMORY is a guide, not gospel.
|
|
1510
1763
|
4. **PAGE TRANSITIONS**: If URL contains '/edit' or you see a 'Save' button, you ARE in the edit view. Don't go back.
|
|
@@ -1524,6 +1777,7 @@ These are NOT optional. You MUST use these tools in these situations:
|
|
|
1524
1777
|
5. If you cannot determine which field is the duplicate, use \`capture_console_errors\` to get hints from the application.
|
|
1525
1778
|
7. **DATA UNIQUENESS**: When generating test data, ALWAYS include a unique suffix (timestamp, counter). Never use "Test123" or "Prueba" alone.
|
|
1526
1779
|
8. **FINISH CORRECTLY**: Use \`finish: true\` ONLY when the mission objective is FULLY achieved.
|
|
1780
|
+
- **MANDATORY FORMS RULE**: If your mission involves creating, editing, or configuring something, YOU MUST CLICK THE FINAL SUBMIT/SAVE BUTTON ("Guardar", "Siguiente", "Finalizar") AND VERIFY THE SUCCESS MESSAGE BEFORE emitting \`finish: true\`. Typing the last field is NOT finishing the mission! Do NOT finish prematurely.
|
|
1527
1781
|
- **BEWARE**: If you see a record in a table, confirm it is the one you JUST created (e.g., check timestamp or log message). Do NOT assume an existing record is your success.
|
|
1528
1782
|
- **MESSAGES**: Be suspicious of success messages that appear right after login or page load. Real success messages usually appear IMMEDIATELY after you click SAVE.
|
|
1529
1783
|
9. **LARGE COMPONENTS**: If a component name is >50 characters, it's probably a CONTAINER, not a field. Do not interact with it.
|
|
@@ -1535,9 +1789,7 @@ These are NOT optional. You MUST use these tools in these situations:
|
|
|
1535
1789
|
# WIZARD & MULTI-STEP FORMS (CRITICAL)
|
|
1536
1790
|
When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 Step 3):
|
|
1537
1791
|
|
|
1538
|
-
1. **VALIDATION ERROR ON
|
|
1539
|
-
- Look for a "Back" / "Anterior" / "Periodo" / step-number button and click it to return.
|
|
1540
|
-
- NEVER retry the Save button without first fixing the invalid data in the previous step.
|
|
1792
|
+
1. **VALIDATION ERROR ON ADVANCE**: If you click "Next/Siguiente" and the UI DOES NOT advance to the next step, AND you see a \u{1F6D1} [ERROR_CR\xCDTICO], you must fix the data on the CURRENT step. DO NOT GO BACK to previous steps simply because you see an error, unless the error explicitly states that a previous step's data is invalid.
|
|
1541
1793
|
|
|
1542
1794
|
2. **DATE VALIDATION ERRORS \u2014 MANDATORY PROTOCOL (ZOD-AWARE)**:
|
|
1543
1795
|
- ZOD generates Spanish validation messages such as:
|
|
@@ -1548,8 +1800,8 @@ When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 St
|
|
|
1548
1800
|
- ANY \u{1F6D1} [ERROR_CR\xCDTICO] containing FECHA + (POSTERIOR/ANTERIOR/ACTUAL/PASADA/FUTURA) is a **ZOD DATE VALIDATION ERROR**.
|
|
1549
1801
|
- **RECOVERY PROTOCOL** (follow in order, NEVER skip):
|
|
1550
1802
|
1. STOP \u2014 do NOT click Save/Siguiente again with the same data.
|
|
1551
|
-
2. Identify which wizard step contains the failing date field (check HISTORY
|
|
1552
|
-
3.
|
|
1803
|
+
2. Identify which wizard step contains the failing date field (check HISTORY).
|
|
1804
|
+
3. If you are NOT on that step, click the Back/Anterior button to return to it.
|
|
1553
1805
|
4. Locate the date input using \`validate_element_state\` with 'has_value' to confirm its current (bad) value.
|
|
1554
1806
|
5. Clear and re-fill the date using \`interact_native_control\` with control_type \`'date'\` and a value \u2265 TODAY in YYYY-MM-DD format.
|
|
1555
1807
|
6. TODAY: \`${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}\` \u2014 use this exact expression to compute the date dynamically.
|
|
@@ -1557,7 +1809,23 @@ When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 St
|
|
|
1557
1809
|
|
|
1558
1810
|
3. **NEVER LOOP ON THE SAME STEP**: If you've been on the same wizard step for 3+ turns without advancing, go back one step and verify the data is correct before trying to advance again.
|
|
1559
1811
|
|
|
1560
|
-
4. **STEP VERIFICATION**: Before clicking Next/Save on any step, VERIFY that date fields have future-or-today dates. Use \`validate_element_state\` with 'has_value' to confirm
|
|
1812
|
+
4. **STEP VERIFICATION**: Before clicking Next/Save on any step, VERIFY that date fields have future-or-today dates. Use \`validate_element_state\` with 'has_value' to confirm.
|
|
1813
|
+
|
|
1814
|
+
5. **FINAL PREVIEW STEP**: If you reach a final step like "Previsualizaci\xF3n", "Resumen", or see a "Terminar" / "Finalizar" button, your objective is ALMOST COMPLETE. Do NOT go back to previous steps just because you see read-only data. Click the final submit button ("Terminar", "Guardar", "Finalizar") to complete the mission.
|
|
1815
|
+
|
|
1816
|
+
6. **DATE FIELD \u2014 ONE SHOT RULE**: For any \`<input type="date">\`, you get EXACTLY ONE attempt with \`interact_native_control\`. If it does not stick (field shows empty on next perception), do NOT retry the date. Instead: a) use \`send_keyboard_event\` to type the date digit by digit (e.g., "0", "5", "0", "5", "2", "0", "2", "6"), OR b) click \`Siguiente\` and see if ZOD validates it. If ZOD rejects it, THEN and ONLY THEN come back to fix. Looping 3+ times on the same date field is FORBIDDEN.
|
|
1817
|
+
|
|
1818
|
+
7. **MODAL REWARD COMBOBOX**: In the rewards modal ("Configurar Recompensas del Juego"), the "Tipo de recompensa" dropdown is a \`[div]\` trigger, NOT an \`[input]\`. Always use 2 turns: Turn A = Click the trigger div to open the option list. Turn B = Click the specific option (e.g., \`[li] Puntos\`, \`[li] Cup\xF3n\`). Do NOT use \`fill\` on this field.
|
|
1819
|
+
|
|
1820
|
+
8. **MODAL SELECTION PROTOCOL (CRITICAL)**: In selection modals (e.g., "Selecciona cup\xF3n"), you MUST select an item BEFORE clicking "Seleccionar" or "Confirmar". To select an item, click on ANY visible element within the item's row: the radio button, the item's name, OR its status label (e.g., the text "Activo" next to a coupon name). Clicking the confirm button without a prior selection WILL FAIL. If you do NOT see a radio button IDX in the component list, click the item's STATUS text (like "Activo") or its NAME text instead \u2014 this is the EXPECTED behavior for custom UI components.
|
|
1821
|
+
|
|
1822
|
+
9. **RADIO BUTTON FALLBACK**: If you cannot find a dedicated radio button element for a list item, do NOT loop. Instead: a) Click the item's visible text (name, status, or any text in its row). b) If that doesn't work, try clicking the row container element. c) NEVER use the search box to "select" an item \u2014 the search box only filters the list, it does NOT select.
|
|
1823
|
+
|
|
1824
|
+
10. **DROPDOWN TOGGLE WARNING (CRITICAL)**: Clicking a dropdown/combobox trigger TOGGLES it: 1st click = open, 2nd click = CLOSE, 3rd click = open again. If you clicked a dropdown trigger and do NOT see options in the next turn, do NOT click it again \u2014 you will just close it! Instead, use \`send_keyboard_event\` with "ArrowDown" to open/navigate the dropdown, then "Enter" to select.
|
|
1825
|
+
|
|
1826
|
+
11. **COMBOBOX KEYBOARD FALLBACK**: If you click a combobox/select field and the dropdown options (\`[li]\`, \`[OPTION]\`) are NOT visible after 1 turn, use this keyboard sequence: a) Click the combobox field ONCE. b) Use \`send_keyboard_event\` with key "ArrowDown" to open the options and highlight the first one. c) Use \`send_keyboard_event\` with key "Enter" to select it. This avoids the toggle problem and works with all dropdown implementations.
|
|
1827
|
+
|
|
1828
|
+
12. **ERROR STOP PROTOCOL (CRITICAL)**: If you see a message starting with \`\u{1F6D1} [ERROR_CR\xCDTICO]\` or \`\u{1F6D1} ERROR POST-GUARDADO\`, do NOT assume it is a transient error. You MUST analyze the text. If it says "ya existe" or "duplicado", you MUST change the offending field value (e.g., add a random number) BEFORE retrying. If you retry without changing any data and the error persists, you MUST use \`report_inability_to_proceed\` immediately. Looping on the same error toast is FORBIDDEN.`
|
|
1561
1829
|
}
|
|
1562
1830
|
];
|
|
1563
1831
|
if (this.testInfo) {
|
|
@@ -1631,7 +1899,7 @@ ${history.slice(-25).join("\n") || "None"}`
|
|
|
1631
1899
|
headers["anthropic-version"] = "2023-06-01";
|
|
1632
1900
|
}
|
|
1633
1901
|
const controller = new AbortController();
|
|
1634
|
-
const timeoutId = setTimeout(() => controller.abort(),
|
|
1902
|
+
const timeoutId = setTimeout(() => controller.abort(), 18e4);
|
|
1635
1903
|
try {
|
|
1636
1904
|
response = await fetch(endpointUrl, {
|
|
1637
1905
|
method: "POST",
|
|
@@ -1653,7 +1921,7 @@ ${history.slice(-25).join("\n") || "None"}`
|
|
|
1653
1921
|
} catch (fetchErr) {
|
|
1654
1922
|
clearTimeout(timeoutId);
|
|
1655
1923
|
if (fetchErr.name === "AbortError") {
|
|
1656
|
-
throw new Error(`Arcality Brain Timeout: No response from ${endpointUrl} after
|
|
1924
|
+
throw new Error(`Arcality Brain Timeout: No response from ${endpointUrl} after 180s`);
|
|
1657
1925
|
}
|
|
1658
1926
|
throw new Error(`Arcality Brain Network Error: ${fetchErr.message}`);
|
|
1659
1927
|
}
|
|
@@ -1689,7 +1957,12 @@ ${history.slice(-25).join("\n") || "None"}`
|
|
|
1689
1957
|
const textResponse = message.content.find((c) => c.type === "text")?.text || "";
|
|
1690
1958
|
if (toolCalls.length === 0) {
|
|
1691
1959
|
console.log(`(${((Date.now() - startTime) / 1e3).toFixed(1)}s)`);
|
|
1692
|
-
|
|
1960
|
+
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Agente devolvi\xF3 texto sin acci\xF3n. Reinyectando instrucci\xF3n mandatoria...`);
|
|
1961
|
+
anthropicMessages.push({
|
|
1962
|
+
role: "user",
|
|
1963
|
+
content: [{ type: "text", text: `MANDATORY: You MUST call one of the available tools right now. You are NOT allowed to return plain text only. If you are unsure, call 'perform_ui_actions' with the most logical next action based on the mission and history. If you are truly stuck, call 'report_inability_to_proceed'. DO NOT return text without a tool call.` }]
|
|
1964
|
+
});
|
|
1965
|
+
continue;
|
|
1693
1966
|
}
|
|
1694
1967
|
const toolResults = [];
|
|
1695
1968
|
let uiActionCall = null;
|
|
@@ -2247,7 +2520,7 @@ ${report}` });
|
|
|
2247
2520
|
this.lastScreenshot = base64Img;
|
|
2248
2521
|
return {
|
|
2249
2522
|
thought: input.thought,
|
|
2250
|
-
actions: input.actions.map((a) => {
|
|
2523
|
+
actions: (input.actions || []).map((a) => {
|
|
2251
2524
|
const c = state.components.find((comp) => comp.idx === a.idx);
|
|
2252
2525
|
let finalValue = a.value;
|
|
2253
2526
|
if (a.action === "fill" && finalValue && /^\d{2}\/\d{2}\/\d{4}$/.test(finalValue)) {
|
|
@@ -2477,6 +2750,7 @@ var SecurityScanner = class {
|
|
|
2477
2750
|
var import_config = require("dotenv/config");
|
|
2478
2751
|
var fs2 = __toESM(require("fs"));
|
|
2479
2752
|
var path2 = __toESM(require("path"));
|
|
2753
|
+
var crypto2 = __toESM(require("crypto"));
|
|
2480
2754
|
var _sessionRuleCache = /* @__PURE__ */ new Set();
|
|
2481
2755
|
function captureValidationRule(errorMessage, context) {
|
|
2482
2756
|
const key = errorMessage.substring(0, 80).toLowerCase().trim();
|
|
@@ -2503,22 +2777,33 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2503
2777
|
process.env.LOGIN_PASSWORD = process.env[`${activeConfig}_PASS`];
|
|
2504
2778
|
await page.context().grantPermissions(["geolocation"]);
|
|
2505
2779
|
await page.context().setGeolocation({ latitude: 19.4326, longitude: -99.1332 });
|
|
2506
|
-
const agent = new AIAgentHelper(page, contextDir, testInfo);
|
|
2507
|
-
const prompt = process.env.SMART_PROMPT || "Navega al inicio y verifica que el sitio cargue";
|
|
2508
2780
|
const history = [];
|
|
2509
2781
|
const urlHistory = [];
|
|
2510
2782
|
const stepsData = [];
|
|
2783
|
+
const memoryFile = path2.join(contextDir, "memoria-agente.json");
|
|
2784
|
+
if (fs2.existsSync(memoryFile)) {
|
|
2785
|
+
try {
|
|
2786
|
+
fs2.unlinkSync(memoryFile);
|
|
2787
|
+
} catch {
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
const agent = new AIAgentHelper(page, contextDir, testInfo);
|
|
2791
|
+
const prompt = process.env.SMART_PROMPT || "Navega al inicio y verifica que el sitio cargue";
|
|
2511
2792
|
const successKeywords = /exitosamente|creado correctamente|guardado correctamente|misión cumplida|registered successfully|created successfully|saved successfully|operación exitosa|registro guardado|successfully|correctly/i;
|
|
2512
2793
|
const failureKeywords = /\berror\b|falló|failed|no se puede|cannot|unable|ya existe|already exists|existente|exist|inválido|invalid|incorrecto|incorrect|ligado|linked|depende|depends|duplicado|duplicate|repetido|repeated|conflict|reintentar|retry|missing|required|obligatorio|bad request|400|500/i;
|
|
2794
|
+
const systemErrorKeywords = /error interno|internal error|intentarlo más tarde|try again later|inténtelo más tarde|servicio no disponible|service unavailable|something went wrong|algo salió mal|ha ocurrido un error inesperado|unexpected error|server error|no se pudo procesar|could not be processed|comuníquese con soporte|contact support/i;
|
|
2513
2795
|
let aiMarkedSuccess = false;
|
|
2514
2796
|
let hasCriticalError = false;
|
|
2797
|
+
let failReason = "";
|
|
2515
2798
|
let resultsSaved = false;
|
|
2516
2799
|
let isFinished = false;
|
|
2517
2800
|
let stepCount = 0;
|
|
2518
2801
|
let guideStepCount = 0;
|
|
2802
|
+
let guideIdx = 0;
|
|
2519
2803
|
let stepsDataBackup = [];
|
|
2520
|
-
const maxSteps =
|
|
2804
|
+
const maxSteps = 50;
|
|
2521
2805
|
let lastSeenSuccessToast = "";
|
|
2806
|
+
let lastSeenErrorToast = "";
|
|
2522
2807
|
let accumulatedCost = 0;
|
|
2523
2808
|
let totalMissionUsage = { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 };
|
|
2524
2809
|
const saveMissionResults = () => {
|
|
@@ -2529,10 +2814,10 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2529
2814
|
fs2.mkdirSync(contextDir, { recursive: true });
|
|
2530
2815
|
const finalSuccess = aiMarkedSuccess && !hasCriticalError;
|
|
2531
2816
|
try {
|
|
2532
|
-
const
|
|
2817
|
+
const memoryFile2 = path2.join(contextDir, "memoria-agente.json");
|
|
2533
2818
|
let memories = [];
|
|
2534
|
-
if (fs2.existsSync(
|
|
2535
|
-
const content = fs2.readFileSync(
|
|
2819
|
+
if (fs2.existsSync(memoryFile2)) {
|
|
2820
|
+
const content = fs2.readFileSync(memoryFile2, "utf8").trim();
|
|
2536
2821
|
if (content) {
|
|
2537
2822
|
try {
|
|
2538
2823
|
memories = JSON.parse(content);
|
|
@@ -2542,24 +2827,77 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2542
2827
|
}
|
|
2543
2828
|
}
|
|
2544
2829
|
}
|
|
2545
|
-
const
|
|
2830
|
+
const rawSteps = stepsData.length > 0 ? stepsData : stepsDataBackup;
|
|
2831
|
+
const cleanSteps = rawSteps.filter((step, idx) => {
|
|
2832
|
+
const name = (step.componentName || "").toLowerCase();
|
|
2833
|
+
const stepUrl = (step.url || "").toLowerCase();
|
|
2834
|
+
if (stepUrl.includes("/login") || name.includes("password") || name.includes("contrase\xF1a") || name.includes("iniciar sesi\xF3n")) {
|
|
2835
|
+
return false;
|
|
2836
|
+
}
|
|
2837
|
+
if (step.action_data?.action === "wait")
|
|
2838
|
+
return false;
|
|
2839
|
+
if (name.includes("cancelar") && idx > 0) {
|
|
2840
|
+
const prev = (rawSteps[idx - 1]?.componentName || "").toLowerCase();
|
|
2841
|
+
if (prev.includes("siguiente") || prev.includes("next") || prev.includes("guardar"))
|
|
2842
|
+
return false;
|
|
2843
|
+
}
|
|
2844
|
+
if (name.includes("agregar recompensas") && idx > 0) {
|
|
2845
|
+
const prev = (rawSteps[idx - 1]?.componentName || "").toLowerCase();
|
|
2846
|
+
if (prev.includes("siguiente") || prev.includes("next"))
|
|
2847
|
+
return false;
|
|
2848
|
+
}
|
|
2849
|
+
if (idx > 0) {
|
|
2850
|
+
const prev = rawSteps[idx - 1];
|
|
2851
|
+
const prevName = (prev?.componentName || "").toLowerCase();
|
|
2852
|
+
if (prevName === name && name !== "") {
|
|
2853
|
+
const currAction = step.action_data?.action;
|
|
2854
|
+
const prevAction = prev.action_data?.action;
|
|
2855
|
+
if (prevAction === "interact_native_control")
|
|
2856
|
+
return false;
|
|
2857
|
+
if (prevAction === "fill" && currAction === "click")
|
|
2858
|
+
return false;
|
|
2859
|
+
if (prevAction === "click" && currAction === "click")
|
|
2860
|
+
return false;
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
if (step.action_data?.action === "click" && idx < rawSteps.length - 1) {
|
|
2864
|
+
const next = rawSteps[idx + 1];
|
|
2865
|
+
if (next.action_data?.action === "fill" && next.componentName === step.componentName) {
|
|
2866
|
+
return false;
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
if ((step.action_data?.action === "fill" || step.action_data?.action === "interact_native_control" || step.action_data?.action === "click") && (name.includes("fecha") || name.includes("date"))) {
|
|
2870
|
+
for (let j = idx + 1; j < rawSteps.length; j++) {
|
|
2871
|
+
const futureStep = rawSteps[j];
|
|
2872
|
+
const futureName = (futureStep.componentName || "").toLowerCase();
|
|
2873
|
+
if (futureName.includes("siguiente") || futureName.includes("next") || futureName.includes("guardar") || futureName.includes("save")) {
|
|
2874
|
+
break;
|
|
2875
|
+
}
|
|
2876
|
+
if (futureName === name && (futureStep.action_data?.action === "fill" || futureStep.action_data?.action === "interact_native_control")) {
|
|
2877
|
+
return false;
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
return true;
|
|
2882
|
+
});
|
|
2546
2883
|
const missionData = {
|
|
2547
2884
|
prompt,
|
|
2548
2885
|
steps: history,
|
|
2549
|
-
steps_data:
|
|
2886
|
+
steps_data: cleanSteps,
|
|
2550
2887
|
success: finalSuccess,
|
|
2551
2888
|
error: hasCriticalError,
|
|
2889
|
+
fail_reason: failReason,
|
|
2552
2890
|
timestamp: Date.now()
|
|
2553
2891
|
};
|
|
2554
2892
|
memories.push(missionData);
|
|
2555
|
-
fs2.writeFileSync(
|
|
2556
|
-
console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Memoria guardada: ${
|
|
2893
|
+
fs2.writeFileSync(memoryFile2, JSON.stringify(memories, null, 2));
|
|
2894
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Memoria guardada: ${cleanSteps.length} pasos limpios (${rawSteps.length - cleanSteps.length} corruptos filtrados)`);
|
|
2557
2895
|
} catch (err) {
|
|
2558
2896
|
console.error("\u274C Fall\xF3 al guardar memoria:", err);
|
|
2559
2897
|
}
|
|
2560
2898
|
try {
|
|
2561
2899
|
const logPath = path2.join(contextDir, `agent-log-${Date.now()}.json`);
|
|
2562
|
-
fs2.writeFileSync(logPath, JSON.stringify({ prompt, history, steps: stepCount, success: finalSuccess, error: hasCriticalError, usage: totalMissionUsage, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
2900
|
+
fs2.writeFileSync(logPath, JSON.stringify({ prompt, history, steps: stepCount, success: finalSuccess, error: hasCriticalError, fail_reason: failReason, usage: totalMissionUsage, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
2563
2901
|
try {
|
|
2564
2902
|
fs2.appendFileSync(path2.join(contextDir, "arcality-history.log"), JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), mission: prompt.substring(0, 150), success: finalSuccess, steps: stepCount, usage: totalMissionUsage }) + "\n");
|
|
2565
2903
|
} catch (e) {
|
|
@@ -2583,7 +2921,15 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2583
2921
|
if (!isFailed && stepsData.length === 0 && stepsDataBackup.length === 0) {
|
|
2584
2922
|
console.warn(`[CollectiveMemory] \u26A0\uFE0F stepsData vac\xEDo en path de \xE9xito \u2014 intentando usar backup...`);
|
|
2585
2923
|
}
|
|
2586
|
-
const
|
|
2924
|
+
const baseSteps = stepsData.length > 0 ? stepsData : stepsDataBackup;
|
|
2925
|
+
const effectiveSteps = baseSteps.filter((step) => {
|
|
2926
|
+
const name = (step.componentName || "").toLowerCase();
|
|
2927
|
+
const stepUrl = (step.url || "").toLowerCase();
|
|
2928
|
+
if (stepUrl.includes("/login") || name.includes("password") || name.includes("contrase\xF1a") || name.includes("iniciar sesi\xF3n")) {
|
|
2929
|
+
return false;
|
|
2930
|
+
}
|
|
2931
|
+
return true;
|
|
2932
|
+
});
|
|
2587
2933
|
console.log(`
|
|
2588
2934
|
======================================================`);
|
|
2589
2935
|
console.log(`\u{1F9E0} [MEMORIA COLECTIVA - INGESTA DE APRENDIZAJE]`);
|
|
@@ -2654,10 +3000,10 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2654
3000
|
throw new Error(`URL Inv\xE1lida: "${loginUrl}". Aseg\xFArate de configurar la Base URL con http://`);
|
|
2655
3001
|
await page.goto(loginUrl);
|
|
2656
3002
|
try {
|
|
2657
|
-
const userInp = page.locator('input[
|
|
3003
|
+
const userInp = page.locator('input[id="username"], input[name="username"], input[type="email"], input[name="email"], [placeholder*="usuario" i], [placeholder*="correo" i], [placeholder*="email" i]').first();
|
|
2658
3004
|
await userInp.waitFor({ state: "visible", timeout: 1e4 });
|
|
2659
|
-
const passInp = page.locator('input[
|
|
2660
|
-
const subBtn = page.locator('button[type="submit"], button:has-text("Login"), button:has-text("Entrar"), [role="button"]:has-text("Entrar")').first();
|
|
3005
|
+
const passInp = page.locator('input[id="password"], input[name="password"], input[type="password"], [placeholder*="contrase\xF1a" i]').first();
|
|
3006
|
+
const subBtn = page.locator('button[type="submit"], button:has-text("Login"), button:has-text("Entrar"), [role="button"]:has-text("Entrar"), button:has-text("ENTRAR")').first();
|
|
2661
3007
|
await userInp.click();
|
|
2662
3008
|
await userInp.fill(process.env.LOGIN_USER || "");
|
|
2663
3009
|
await passInp.click();
|
|
@@ -2690,6 +3036,90 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2690
3036
|
isFinished = true;
|
|
2691
3037
|
saveMissionResults();
|
|
2692
3038
|
});
|
|
3039
|
+
let reusedPatternId = null;
|
|
3040
|
+
let usedDirectReuse = false;
|
|
3041
|
+
let patternContext = "";
|
|
3042
|
+
try {
|
|
3043
|
+
const patterns = await searchPromptPattern(prompt, 5);
|
|
3044
|
+
if (patterns && patterns.length > 0) {
|
|
3045
|
+
const bestMatch = patterns[0];
|
|
3046
|
+
const normalize = (txt) => {
|
|
3047
|
+
return txt.toLowerCase().replace(/ar\b|er\b|ir\b/g, "").split(/\s+/).filter((w) => w.length > 2 && !["una", "uno", "las", "los", "del", "con", "para", "nueva", "nuevo"].includes(w));
|
|
3048
|
+
};
|
|
3049
|
+
const promptKeywords = normalize(prompt);
|
|
3050
|
+
const patternKeywords = normalize(bestMatch.prompt || bestMatch.original_prompt || bestMatch.originalPrompt || bestMatch.name || "");
|
|
3051
|
+
const matches = promptKeywords.filter((kw) => patternKeywords.some((pkw) => pkw.includes(kw) || kw.includes(pkw))).length;
|
|
3052
|
+
const matchRatio = matches / Math.max(promptKeywords.length, 1);
|
|
3053
|
+
if (matchRatio >= 0.85) {
|
|
3054
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Memoria Colectiva detectada (id: ${bestMatch.id}). Descargando a memoria local...`);
|
|
3055
|
+
reusedPatternId = bestMatch.id || "unknown";
|
|
3056
|
+
let patternJsonObj = {};
|
|
3057
|
+
try {
|
|
3058
|
+
const rawPj = bestMatch.pattern_json || bestMatch.patternJson;
|
|
3059
|
+
patternJsonObj = typeof rawPj === "string" ? JSON.parse(rawPj) : rawPj || {};
|
|
3060
|
+
} catch {
|
|
3061
|
+
patternJsonObj = {};
|
|
3062
|
+
}
|
|
3063
|
+
const rawStepsTechnical = patternJsonObj.steps_technical || patternJsonObj.stepsTechnical;
|
|
3064
|
+
const resolvedStepsData = Array.isArray(rawStepsTechnical) ? rawStepsTechnical : [];
|
|
3065
|
+
if (resolvedStepsData.length === 0) {
|
|
3066
|
+
console.warn(`>>ARCALITY_STATUS>> \u26A0\uFE0F Patr\xF3n descargado sin steps_technical v\xE1lidos. Modo Gu\xEDa no ser\xE1 activado.`);
|
|
3067
|
+
}
|
|
3068
|
+
const localMemoryData = {
|
|
3069
|
+
prompt,
|
|
3070
|
+
steps: patternJsonObj.history || [],
|
|
3071
|
+
steps_data: resolvedStepsData,
|
|
3072
|
+
success: true,
|
|
3073
|
+
error: false,
|
|
3074
|
+
timestamp: Date.now()
|
|
3075
|
+
};
|
|
3076
|
+
try {
|
|
3077
|
+
const memoryFile2 = path2.join(contextDir, "memoria-agente.json");
|
|
3078
|
+
let memories = [];
|
|
3079
|
+
if (fs2.existsSync(memoryFile2)) {
|
|
3080
|
+
try {
|
|
3081
|
+
memories = JSON.parse(fs2.readFileSync(memoryFile2, "utf8"));
|
|
3082
|
+
} catch {
|
|
3083
|
+
memories = [];
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
const normalizeForDedup = (s) => s.toLowerCase().replace(/\s+/g, " ").trim();
|
|
3087
|
+
const normalizedCurrentPrompt = normalizeForDedup(prompt);
|
|
3088
|
+
const alreadyExists = memories.some((m) => normalizeForDedup(m.prompt || "") === normalizedCurrentPrompt);
|
|
3089
|
+
if (!alreadyExists) {
|
|
3090
|
+
if (resolvedStepsData.length > 0) {
|
|
3091
|
+
memories.push(localMemoryData);
|
|
3092
|
+
fs2.writeFileSync(memoryFile2, JSON.stringify(memories, null, 2));
|
|
3093
|
+
console.log(`>>ARCALITY_STATUS>> \u2728 Memoria sincronizada localmente (${resolvedStepsData.length} pasos). El Agente entrar\xE1 en modo GU\xCDA.`);
|
|
3094
|
+
} else {
|
|
3095
|
+
console.warn(`>>ARCALITY_STATUS>> \u26A0\uFE0F Memoria NO sincronizada: el patr\xF3n del backend tiene 0 pasos t\xE9cnicos v\xE1lidos.`);
|
|
3096
|
+
}
|
|
3097
|
+
} else {
|
|
3098
|
+
console.log(`>>ARCALITY_STATUS>> \u2139\uFE0F Patr\xF3n ya existe en memoria local. Modo GU\xCDA activado sin re-escritura.`);
|
|
3099
|
+
}
|
|
3100
|
+
} catch (err) {
|
|
3101
|
+
console.warn(`Error al sincronizar memoria local: ${err}`);
|
|
3102
|
+
}
|
|
3103
|
+
} else if (matchRatio >= 0.5) {
|
|
3104
|
+
let patternJsonObj = {};
|
|
3105
|
+
try {
|
|
3106
|
+
patternJsonObj = typeof bestMatch.pattern_json === "string" ? JSON.parse(bestMatch.pattern_json) : bestMatch.pattern_json || bestMatch.patternJson || {};
|
|
3107
|
+
} catch {
|
|
3108
|
+
patternJsonObj = {};
|
|
3109
|
+
}
|
|
3110
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F916} Similitud parcial detectada (text_match_found: true). LLM ser\xE1 llamado con contexto del patr\xF3n (llm_called: true)`);
|
|
3111
|
+
const readableHistory = patternJsonObj.history?.slice(0, 10).map((h) => `- ${h}`).join("\n") || "";
|
|
3112
|
+
patternContext = `Existe un patr\xF3n similar previamente aprobado que logr\xF3 el \xE9xito. \xDAsalo como referencia para tu estrategia:
|
|
3113
|
+
${readableHistory}`;
|
|
3114
|
+
} else {
|
|
3115
|
+
console.log(`>>ARCALITY_STATUS>> \u2139\uFE0F Patr\xF3n de BD descartado por baja similitud (${matchRatio.toFixed(2)}).`);
|
|
3116
|
+
}
|
|
3117
|
+
} else {
|
|
3118
|
+
console.log(`>>ARCALITY_STATUS>> \u2139\uFE0F No se encontraron patrones previos (no_match: true).`);
|
|
3119
|
+
}
|
|
3120
|
+
} catch (e) {
|
|
3121
|
+
console.warn(`Error buscando patrones: ${e.message}`);
|
|
3122
|
+
}
|
|
2693
3123
|
const maxTotalIterations = maxSteps + 50;
|
|
2694
3124
|
let totalIterations = 0;
|
|
2695
3125
|
while (!isFinished && stepCount < maxSteps) {
|
|
@@ -2697,10 +3127,22 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2697
3127
|
if (totalIterations > maxTotalIterations) {
|
|
2698
3128
|
console.error(`>>ARCALITY_STATUS>> \u{1F6D1} Safety limit: ${totalIterations} iteraciones totales alcanzadas. Abortando.`);
|
|
2699
3129
|
hasCriticalError = true;
|
|
3130
|
+
failReason = "safety_limit_iterations";
|
|
2700
3131
|
break;
|
|
2701
3132
|
}
|
|
2702
3133
|
let containsError = false;
|
|
2703
|
-
|
|
3134
|
+
if (page.isClosed()) {
|
|
3135
|
+
console.error(`
|
|
3136
|
+
\u{1F6D1} [PAGE CRASH] La pesta\xF1a del navegador fue cerrada inesperadamente (inicio de ciclo).`);
|
|
3137
|
+
console.error(` >> Causa probable: El portal caus\xF3 una navegaci\xF3n forzada, crash de tab, o redirigi\xF3 fuera del contexto.`);
|
|
3138
|
+
hasCriticalError = true;
|
|
3139
|
+
failReason = "portal_page_crash";
|
|
3140
|
+
isFinished = true;
|
|
3141
|
+
saveMissionResults();
|
|
3142
|
+
break;
|
|
3143
|
+
}
|
|
3144
|
+
await page.waitForTimeout(1e3).catch(() => {
|
|
3145
|
+
});
|
|
2704
3146
|
const currentUrl = page.url();
|
|
2705
3147
|
const historyStr = history.join(" ").toLowerCase();
|
|
2706
3148
|
const hadSubmitAction = historyStr.includes("guardar") || historyStr.includes("save") || historyStr.includes("registrar");
|
|
@@ -2731,6 +3173,49 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2731
3173
|
}
|
|
2732
3174
|
}
|
|
2733
3175
|
}
|
|
3176
|
+
const isReviewStepVisible = await page.locator('text="Previsualizaci\xF3n"').isVisible().catch(() => false) || await page.locator('text="Resumen"').isVisible().catch(() => false) || await page.locator('text="Confirmaci\xF3n"').isVisible().catch(() => false);
|
|
3177
|
+
const looksLikeFinalStep = isReviewStepVisible && stepCount >= 10;
|
|
3178
|
+
if (looksLikeFinalStep && !isFinished) {
|
|
3179
|
+
const finalSaveBtn = page.locator('button:has-text("Guardar"), button:has-text("Finalizar"), button:has-text("Terminar"), button:has-text("Crear")');
|
|
3180
|
+
const btnCount = await finalSaveBtn.count();
|
|
3181
|
+
if (btnCount > 0) {
|
|
3182
|
+
const firstBtn = finalSaveBtn.first();
|
|
3183
|
+
if (await firstBtn.isVisible().catch(() => false) && await firstBtn.isEnabled().catch(() => false)) {
|
|
3184
|
+
const btnText = await firstBtn.innerText().catch(() => "");
|
|
3185
|
+
const isModalOpen = await page.locator('[role="dialog"], .modal, .mat-dialog-container').first().isVisible().catch(() => false);
|
|
3186
|
+
if (!isModalOpen) {
|
|
3187
|
+
console.log(`
|
|
3188
|
+
\u2728 [PROACTIVE FINISH] Bot\xF3n final detectado: "${btnText.trim()}". Ejecutando sin turno IA para evitar timeout.`);
|
|
3189
|
+
history.push(`Turno ${stepCount}: [AUTO] click en "[button] ${btnText.trim()}" (Detecci\xF3n proactiva del paso final)`);
|
|
3190
|
+
await firstBtn.click({ timeout: 5e3 }).catch(() => {
|
|
3191
|
+
});
|
|
3192
|
+
await page.waitForTimeout(3e3);
|
|
3193
|
+
const postSaveFb = await page.locator('.toast, .alert, [role="status"], [role="alert"], .snackbar, .notification, .mat-snack-bar-container').all();
|
|
3194
|
+
let proactiveSuccess = false;
|
|
3195
|
+
for (const fb of postSaveFb) {
|
|
3196
|
+
if (await fb.isVisible().catch(() => false)) {
|
|
3197
|
+
const fbTxt = await fb.innerText().catch(() => "");
|
|
3198
|
+
if (successKeywords.test(fbTxt)) {
|
|
3199
|
+
console.log(`>>ARCALITY_STATUS>> \u2728 [PROACTIVE FINISH] \xC9xito confirmado por toast: "${fbTxt.substring(0, 60)}"`);
|
|
3200
|
+
proactiveSuccess = true;
|
|
3201
|
+
break;
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
const postSaveUrl = page.url();
|
|
3206
|
+
const urlChanged = postSaveUrl !== currentUrl && !postSaveUrl.includes("/new") && !postSaveUrl.includes("/create");
|
|
3207
|
+
if (proactiveSuccess || urlChanged) {
|
|
3208
|
+
aiMarkedSuccess = true;
|
|
3209
|
+
isFinished = true;
|
|
3210
|
+
hasCriticalError = false;
|
|
3211
|
+
await persistCollectiveMemory();
|
|
3212
|
+
saveMissionResults();
|
|
3213
|
+
break;
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
2734
3219
|
if (stepCount === 1) {
|
|
2735
3220
|
const initialToasts = await page.locator('.toast, .alert, [role="status"], [role="alert"]').all();
|
|
2736
3221
|
for (const t of initialToasts) {
|
|
@@ -2741,6 +3226,49 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2741
3226
|
}
|
|
2742
3227
|
}
|
|
2743
3228
|
}
|
|
3229
|
+
let networkError = agent.criticalNetworkError;
|
|
3230
|
+
if (networkError) {
|
|
3231
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F6D1} Error interno del Portal/Red detectado: "${networkError}"`);
|
|
3232
|
+
hasCriticalError = true;
|
|
3233
|
+
failReason = "portal_network_error";
|
|
3234
|
+
isFinished = true;
|
|
3235
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} ERROR CR\xCDTICO DE SISTEMA/RED: ${networkError}. El portal fall\xF3 internamente al ejecutar una petici\xF3n XHR/Fetch.`);
|
|
3236
|
+
saveMissionResults();
|
|
3237
|
+
break;
|
|
3238
|
+
}
|
|
3239
|
+
let jsError = agent.criticalJsError;
|
|
3240
|
+
if (jsError) {
|
|
3241
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F6D1} Error Fatal de JavaScript detectado: "${jsError}"`);
|
|
3242
|
+
hasCriticalError = true;
|
|
3243
|
+
failReason = "portal_js_crash";
|
|
3244
|
+
isFinished = true;
|
|
3245
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} ERROR CR\xCDTICO DE APLICACI\xD3N: ${jsError}. El portal sufri\xF3 una excepci\xF3n no manejada y dej\xF3 de responder.`);
|
|
3246
|
+
saveMissionResults();
|
|
3247
|
+
break;
|
|
3248
|
+
}
|
|
3249
|
+
try {
|
|
3250
|
+
const systemToastEls = await page.locator('.toast, .alert, [role="alert"], [role="status"], .snackbar, .notification, .error-message, .mat-snack-bar-container').all();
|
|
3251
|
+
for (const el of systemToastEls) {
|
|
3252
|
+
if (await el.isVisible().catch(() => false)) {
|
|
3253
|
+
const sysErrTxt = await el.innerText().catch(() => "");
|
|
3254
|
+
if (sysErrTxt && systemErrorKeywords.test(sysErrTxt)) {
|
|
3255
|
+
console.error(`
|
|
3256
|
+
\u{1F6D1} [SYSTEM ERROR DETECTADO] El portal report\xF3 un error de sistema no recuperable en UI:`);
|
|
3257
|
+
console.error(` Mensaje: "${sysErrTxt.trim().substring(0, 150)}"`);
|
|
3258
|
+
console.error(` >> Tipo: portal_internal_error | El agente NO puede corregir errores del servidor.`);
|
|
3259
|
+
hasCriticalError = true;
|
|
3260
|
+
failReason = "portal_internal_error";
|
|
3261
|
+
isFinished = true;
|
|
3262
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} ERROR SIST\xC9MICO DEL PORTAL: "${sysErrTxt.trim().substring(0, 200)}". Este es un fallo del servidor, no un error de datos del agente. El portal devolvi\xF3 HTTP 200 pero report\xF3 un error interno en la UI.`);
|
|
3263
|
+
saveMissionResults();
|
|
3264
|
+
break;
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
} catch {
|
|
3269
|
+
}
|
|
3270
|
+
if (isFinished)
|
|
3271
|
+
break;
|
|
2744
3272
|
let hasVisibleError = false;
|
|
2745
3273
|
try {
|
|
2746
3274
|
const errorEls = await page.locator('.toast, .alert, [role="alert"], [role="status"], .snackbar, .notification, .error-message').all();
|
|
@@ -2757,9 +3285,17 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2757
3285
|
}
|
|
2758
3286
|
let response = null;
|
|
2759
3287
|
if (!hasVisibleError) {
|
|
2760
|
-
response = await agent.getActionFromGuia(prompt,
|
|
3288
|
+
response = await agent.getActionFromGuia(prompt, guideIdx);
|
|
3289
|
+
if (!response) {
|
|
3290
|
+
await page.waitForTimeout(1e3);
|
|
3291
|
+
response = await agent.getActionFromGuia(prompt, guideIdx);
|
|
3292
|
+
if (response) {
|
|
3293
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F504} Gu\xEDa recuper\xF3 el rastro tras esperar 1000ms.`);
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
2761
3296
|
} else {
|
|
2762
3297
|
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Error real detectado en pantalla. Saltando Gu\xEDa.`);
|
|
3298
|
+
guideIdx = 999999;
|
|
2763
3299
|
if (stepsData.length > 0) {
|
|
2764
3300
|
stepsDataBackup = [...stepsData];
|
|
2765
3301
|
stepsData.length = 0;
|
|
@@ -2787,13 +3323,15 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2787
3323
|
const isGuideRepeating = guideActionDesc && recentHistory.some((h) => h.includes(guideActionDesc.substring(0, 30)));
|
|
2788
3324
|
if (isGuideRepeating) {
|
|
2789
3325
|
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Gu\xEDa invalidada: la acci\xF3n "${guideActionDesc.substring(0, 50)}" ya fue intentada. Delegando a IA.`);
|
|
3326
|
+
guideIdx++;
|
|
2790
3327
|
response = null;
|
|
2791
3328
|
} else {
|
|
2792
3329
|
guideStepCount++;
|
|
3330
|
+
guideIdx++;
|
|
2793
3331
|
console.log(`
|
|
2794
3332
|
======================================================`);
|
|
2795
3333
|
console.log(`\u{1F680} [USANDO GU\xCDA DE \xC9XITO - MEMORIA COLECTIVA AHORRANDO TOKENS]`);
|
|
2796
|
-
console.log(`>>arcality>> Paso validado (Gu\xEDa #${guideStepCount}, NO consume turno IA)`);
|
|
3334
|
+
console.log(`>>arcality>> Paso validado (Gu\xEDa #${guideStepCount}, guideIdx=${guideIdx}, NO consume turno IA)`);
|
|
2797
3335
|
console.log(`======================================================
|
|
2798
3336
|
`);
|
|
2799
3337
|
}
|
|
@@ -2801,7 +3339,10 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2801
3339
|
if (!response) {
|
|
2802
3340
|
stepCount++;
|
|
2803
3341
|
console.log(`>>ARCALITY_STATUS>> \u23F3 Turno IA ${stepCount} de ${maxSteps} (Gu\xEDa us\xF3 ${guideStepCount} turnos gratis)...`);
|
|
2804
|
-
|
|
3342
|
+
const effectivePrompt = patternContext ? `${prompt}
|
|
3343
|
+
|
|
3344
|
+
${patternContext}` : prompt;
|
|
3345
|
+
response = await agent.askIA(effectivePrompt, history, stepCount);
|
|
2805
3346
|
if (response.usage) {
|
|
2806
3347
|
const inputs = response.usage.input_tokens || 0;
|
|
2807
3348
|
const cacheCreates = response.usage.cache_creation_input_tokens || 0;
|
|
@@ -2822,6 +3363,7 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2822
3363
|
response.actions = [];
|
|
2823
3364
|
aiMarkedSuccess = false;
|
|
2824
3365
|
hasCriticalError = true;
|
|
3366
|
+
failReason = "cost_limit_reached";
|
|
2825
3367
|
}
|
|
2826
3368
|
}
|
|
2827
3369
|
}
|
|
@@ -2832,19 +3374,26 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2832
3374
|
console.log(`======================================================
|
|
2833
3375
|
`);
|
|
2834
3376
|
if (!response.finish && history.length >= 6) {
|
|
2835
|
-
const
|
|
2836
|
-
const clickActions =
|
|
2837
|
-
const fillActions =
|
|
3377
|
+
const lastActions = history.slice(-8);
|
|
3378
|
+
const clickActions = lastActions.filter((h) => h.includes("click en")).map((h) => h.replace(/^Turno \d+:\s*/, ""));
|
|
3379
|
+
const fillActions = lastActions.filter((h) => h.includes("fill en")).map((h) => h.replace(/^Turno \d+:\s*/, ""));
|
|
2838
3380
|
const uniqueClicks = new Set(clickActions);
|
|
2839
3381
|
const uniqueFills = new Set(fillActions);
|
|
2840
3382
|
const isClickLoop = clickActions.length >= 4 && uniqueClicks.size <= 2;
|
|
2841
3383
|
const isFillLoop = fillActions.length >= 4 && uniqueFills.size <= 2;
|
|
2842
|
-
|
|
3384
|
+
const currentTurnActions = history.filter((h) => h.startsWith(`Turno ${stepCount}:`));
|
|
3385
|
+
const currentTurnHasFill = currentTurnActions.some((h) => h.includes("fill en"));
|
|
3386
|
+
const currentTurnHasNewTarget = currentTurnActions.some((h) => {
|
|
3387
|
+
const match = h.match(/(?:click|fill) en "(.+?)"/);
|
|
3388
|
+
return match && !uniqueClicks.has(`click en ${match[1]}`) && !uniqueClicks.has(`fill en ${match[1]}`);
|
|
3389
|
+
});
|
|
3390
|
+
const agentRecovered = currentTurnHasFill || currentTurnHasNewTarget;
|
|
3391
|
+
if ((isClickLoop || isFillLoop) && !agentRecovered) {
|
|
2843
3392
|
const loopType = isClickLoop ? "click" : "fill";
|
|
2844
3393
|
const uniqueTargets = isClickLoop ? uniqueClicks : uniqueFills;
|
|
2845
3394
|
console.warn(`
|
|
2846
3395
|
\u26A0\uFE0F [LOOP DETECTOR] Patr\xF3n repetitivo de ${loopType} detectado:`);
|
|
2847
|
-
console.warn(` \xDAltimas acciones: ${
|
|
3396
|
+
console.warn(` \xDAltimas acciones: ${lastActions.join(" \u2192 ")}`);
|
|
2848
3397
|
console.warn(` El agente est\xE1 atrapado. Forzando terminaci\xF3n con sugerencia.`);
|
|
2849
3398
|
response.thought = `\u{1F6D1} ERROR: Me he quedado atrapado en un bucle repetitivo intentando interactuar con: ${Array.from(uniqueTargets).join(", ")}. No puedo continuar la misi\xF3n de forma aut\xF3noma.
|
|
2850
3399
|
|
|
@@ -2853,6 +3402,9 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2853
3402
|
response.actions = [];
|
|
2854
3403
|
aiMarkedSuccess = false;
|
|
2855
3404
|
hasCriticalError = true;
|
|
3405
|
+
failReason = "loop_detected";
|
|
3406
|
+
} else if ((isClickLoop || isFillLoop) && agentRecovered) {
|
|
3407
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F504} Loop candidato detectado pero el agente se recuper\xF3 en este turno. Continuando misi\xF3n.`);
|
|
2856
3408
|
}
|
|
2857
3409
|
}
|
|
2858
3410
|
if (response.actions && response.actions.length > 0) {
|
|
@@ -2865,8 +3417,15 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2865
3417
|
const urlBeforeAction = page.url();
|
|
2866
3418
|
if ((step.action === "click" || step.action === "double_click") && loc) {
|
|
2867
3419
|
const desc = (step.description || "").toLowerCase();
|
|
2868
|
-
const
|
|
2869
|
-
const
|
|
3420
|
+
const elementType = await loc.getAttribute("type").catch(() => "") || "";
|
|
3421
|
+
const elementRole = await loc.getAttribute("role").catch(() => "") || "";
|
|
3422
|
+
const ariaExpanded = await loc.getAttribute("aria-expanded").catch(() => "") || "";
|
|
3423
|
+
const isInsideForm = await loc.evaluate((el) => !!el.closest("form")).catch(() => false);
|
|
3424
|
+
const isLoginPage = page.url().includes("/login") || page.url().includes("/auth") || page.url().includes("oauth") || await page.locator('input[type="password"]').count().then((c) => c > 0).catch(() => false);
|
|
3425
|
+
const isAnchorLink = desc.startsWith("[a]") || step.type === "a";
|
|
3426
|
+
const isMenuItem = isAnchorLink || elementRole === "menuitem" || elementRole === "option" || desc.startsWith("[li]") || step.type === "li";
|
|
3427
|
+
const isMenuTrigger = desc.includes("more_vert") || desc.includes("more_horiz") || elementRole === "menu" || ariaExpanded !== "";
|
|
3428
|
+
const isSubmitAction = !isMenuItem && (elementType === "submit" || isInsideForm && step.type === "button" || isLoginPage);
|
|
2870
3429
|
if (isSubmitAction) {
|
|
2871
3430
|
const rawBodyText = await page.innerText("body").catch(() => "");
|
|
2872
3431
|
const textLower = rawBodyText.toLowerCase();
|
|
@@ -2880,6 +3439,7 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2880
3439
|
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Feedback de ERROR en elemento peque\xF1o: "${elementTextLower.substring(0, 50)}..."`);
|
|
2881
3440
|
aiMarkedSuccess = false;
|
|
2882
3441
|
hasCriticalError = true;
|
|
3442
|
+
failReason = "ui_validation_error";
|
|
2883
3443
|
isFinished = true;
|
|
2884
3444
|
saveMissionResults();
|
|
2885
3445
|
break;
|
|
@@ -2895,12 +3455,10 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2895
3455
|
}
|
|
2896
3456
|
}
|
|
2897
3457
|
}
|
|
2898
|
-
const previousSubmit = history.find(
|
|
2899
|
-
(h) => h.toLowerCase().includes("click") && (h.toLowerCase().includes("guardar") || h.toLowerCase().includes("save") || h.toLowerCase().includes("crear") || h.toLowerCase().includes("registrar"))
|
|
2900
|
-
);
|
|
3458
|
+
const previousSubmit = history.find((h) => h.includes("[SUBMIT]"));
|
|
2901
3459
|
if (previousSubmit && !allowRepeatedNames) {
|
|
2902
3460
|
console.log(`>>ARCALITY_STATUS>> \u{1F6D1} Advertencia: Click persistente en bot\xF3n de acci\xF3n.`);
|
|
2903
|
-
history.push(`Turno ${stepCount}: Intentaste
|
|
3461
|
+
history.push(`Turno ${stepCount}: [SUBMIT] Intentaste enviar el formulario con "${desc}" nuevamente, pero la p\xE1gina sigue en el mismo estado. \xBFHay alg\xFAn error visible que debas corregir antes?`);
|
|
2904
3462
|
await page.waitForTimeout(1e3);
|
|
2905
3463
|
}
|
|
2906
3464
|
}
|
|
@@ -2927,7 +3485,7 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2927
3485
|
}
|
|
2928
3486
|
console.log(`
|
|
2929
3487
|
\u{1F6D1} [AUTO-STOP] Bloqueando click repetido en "${desc}" (${sameActionCount} veces). Forzando re-evaluaci\xF3n.`);
|
|
2930
|
-
history.push(`Turno ${stepCount}: \u{1F6D1} [AUTO-STOP] El click en "${desc}" fue bloqueado despu\xE9s de ${sameActionCount} repeticiones.
|
|
3488
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} [AUTO-STOP] El click en "${desc}" fue bloqueado despu\xE9s de ${sameActionCount} repeticiones. Si es un dropdown/combobox que no abre sus opciones, usa send_keyboard_event con key "ArrowDown" para navegar opciones y "Enter" para seleccionar. NO vuelvas a hacer click en el mismo elemento.`);
|
|
2931
3489
|
await page.waitForTimeout(800);
|
|
2932
3490
|
break;
|
|
2933
3491
|
}
|
|
@@ -2939,19 +3497,23 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2939
3497
|
await loc.scrollIntoViewIfNeeded({ timeout: 5e3 }).catch(() => {
|
|
2940
3498
|
});
|
|
2941
3499
|
if (step.action === "double_click") {
|
|
2942
|
-
await loc.dblclick({ timeout:
|
|
3500
|
+
await loc.dblclick({ timeout: 4e3, force: true });
|
|
2943
3501
|
} else {
|
|
2944
3502
|
if (desc.includes("toast") || desc.includes("close")) {
|
|
2945
3503
|
await loc.click({ timeout: 2e3, force: true }).catch(() => console.log(" \u26A0\uFE0F No se pudo cerrar toast, ignorando..."));
|
|
2946
3504
|
} else {
|
|
2947
|
-
await loc.click({ timeout:
|
|
3505
|
+
await loc.click({ timeout: 4e3, force: true });
|
|
2948
3506
|
}
|
|
2949
3507
|
}
|
|
2950
3508
|
if (isMenuTrigger) {
|
|
2951
3509
|
console.log(" \u23F3 Abriendo men\xFA, esperando 1.5s para renderizado...");
|
|
2952
3510
|
await page.waitForTimeout(1500);
|
|
2953
3511
|
}
|
|
2954
|
-
|
|
3512
|
+
if (!isMenuTrigger && (elementRole === "combobox" || elementRole === "listbox" || ariaExpanded === "true")) {
|
|
3513
|
+
console.log(" \u23F3 Dropdown/combobox detectado por ARIA role, esperando 800ms para opciones...");
|
|
3514
|
+
await page.waitForTimeout(800);
|
|
3515
|
+
}
|
|
3516
|
+
const actsLikeSubmit = isSubmitAction;
|
|
2955
3517
|
if (actsLikeSubmit) {
|
|
2956
3518
|
console.log(" \u23F3 Esperando transici\xF3n post-guardado...");
|
|
2957
3519
|
try {
|
|
@@ -2972,8 +3534,29 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2972
3534
|
saveMissionResults();
|
|
2973
3535
|
foundSuccessPost = true;
|
|
2974
3536
|
break;
|
|
3537
|
+
} else if (systemErrorKeywords.test(fbText)) {
|
|
3538
|
+
console.error(`
|
|
3539
|
+
\u{1F6D1} [SYSTEM ERROR POST-GUARDADO] Portal report\xF3 error de sistema: "${fbText.substring(0, 100)}"`);
|
|
3540
|
+
console.error(` >> Tipo: portal_internal_error | Causa probable: fallo en la capa de servicio del backend.`);
|
|
3541
|
+
hasCriticalError = true;
|
|
3542
|
+
failReason = "portal_internal_error";
|
|
3543
|
+
isFinished = true;
|
|
3544
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} ERROR SIST\xC9MICO POST-GUARDADO: "${fbText.trim().substring(0, 200)}". El portal respondi\xF3 con un error de servidor. Imposible continuar sin intervenci\xF3n manual.`);
|
|
3545
|
+
saveMissionResults();
|
|
3546
|
+
foundSuccessPost = true;
|
|
3547
|
+
break;
|
|
2975
3548
|
} else if (failureKeywords.test(fbText.toLowerCase())) {
|
|
2976
3549
|
console.log(`>>ARCALITY_STATUS>> \u{1F4DA} [BUSINESS RULE] Error de validaci\xF3n capturado: "${fbText.substring(0, 80)}"`);
|
|
3550
|
+
if (lastSeenErrorToast === fbText.trim()) {
|
|
3551
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F6D1} ERROR PERSISTENTE DETECTADO. La aplicaci\xF3n no acepta los cambios tras reintento.`);
|
|
3552
|
+
hasCriticalError = true;
|
|
3553
|
+
failReason = "unresolved_ui_error";
|
|
3554
|
+
isFinished = true;
|
|
3555
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} ERROR PERSISTENTE: "${fbText.trim()}". La misi\xF3n no puede continuar porque el portal sigue rechazando la acci\xF3n.`);
|
|
3556
|
+
saveMissionResults();
|
|
3557
|
+
break;
|
|
3558
|
+
}
|
|
3559
|
+
lastSeenErrorToast = fbText.trim();
|
|
2977
3560
|
captureValidationRule(fbText.trim(), `Acci\xF3n: click en "${desc}" en ${page.url()}`);
|
|
2978
3561
|
const isDuplicateError = /repetido|duplicado|ya existe|already exists/i.test(fbText);
|
|
2979
3562
|
const correctionHint = isDuplicateError ? `CORRECCI\xD3N OBLIGATORIA: El valor que ingresaste ya existe. Ve al campo que lo caus\xF3 y c\xE1mbialo por uno \xFAnico (a\xF1ade _${Date.now().toString().slice(-4)} como sufijo).` : `CORRECCI\xD3N OBLIGATORIA: Corrige el campo se\xF1alado antes de intentar guardar de nuevo.`;
|
|
@@ -3019,11 +3602,36 @@ function captureValidationRule(errorMessage, context) {
|
|
|
3019
3602
|
await page.waitForTimeout(500);
|
|
3020
3603
|
}
|
|
3021
3604
|
} else if (step.action === "fill" && loc && step.value) {
|
|
3022
|
-
const
|
|
3023
|
-
if (
|
|
3605
|
+
const inputType = await loc.evaluate((el) => el.tagName === "INPUT" ? (el.type || "").toLowerCase() : null).catch(() => null);
|
|
3606
|
+
if (inputType === "file") {
|
|
3024
3607
|
console.log(` \u{1F4C2} Detectado input de archivo. Subiendo: "${step.value}"`);
|
|
3025
3608
|
const filePath = step.value.replace(/^"|"$/g, "").trim();
|
|
3026
3609
|
await loc.setInputFiles(filePath);
|
|
3610
|
+
} else if (inputType === "date" || inputType === "time" || inputType === "datetime-local") {
|
|
3611
|
+
console.log(` \u{1F39B}\uFE0F Detectado input nativo (${inputType}). Usando estrategia segura para: "${step.value}"`);
|
|
3612
|
+
try {
|
|
3613
|
+
await loc.fill(step.value, { timeout: 3e3 });
|
|
3614
|
+
await loc.evaluate((el) => {
|
|
3615
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
3616
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
3617
|
+
});
|
|
3618
|
+
} catch (err) {
|
|
3619
|
+
if (err.message && err.message.includes("Malformed value")) {
|
|
3620
|
+
console.log(` \u26A0\uFE0F Malformed value detectado en input nativo. Usando fallback de teclado...`);
|
|
3621
|
+
await loc.click({ force: true });
|
|
3622
|
+
await page.keyboard.press("Control+a");
|
|
3623
|
+
const digits = step.value.replace(/[:-T/]/g, "");
|
|
3624
|
+
await page.keyboard.type(digits, { delay: 50 });
|
|
3625
|
+
await loc.evaluate((el) => {
|
|
3626
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
3627
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
3628
|
+
});
|
|
3629
|
+
} else {
|
|
3630
|
+
throw err;
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
await loc.press("Tab").catch(() => {
|
|
3634
|
+
});
|
|
3027
3635
|
} else {
|
|
3028
3636
|
await loc.click({ timeout: 5e3 }).catch(() => {
|
|
3029
3637
|
});
|
|
@@ -3040,7 +3648,7 @@ function captureValidationRule(errorMessage, context) {
|
|
|
3040
3648
|
history.push(`Turno ${stepCount}: ${step.action} en "${descForHistory}"${valStr}`);
|
|
3041
3649
|
urlHistory.push(page.url());
|
|
3042
3650
|
const compName = (step.description || "").substring(0, 80);
|
|
3043
|
-
if (compName.length
|
|
3651
|
+
if (compName.length > 0) {
|
|
3044
3652
|
stepsData.push({
|
|
3045
3653
|
url: urlBeforeAction,
|
|
3046
3654
|
// USAR URL CAPTURADA ANTES
|
|
@@ -3052,16 +3660,76 @@ function captureValidationRule(errorMessage, context) {
|
|
|
3052
3660
|
});
|
|
3053
3661
|
}
|
|
3054
3662
|
} catch (e) {
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
console.error(`>>ARCALITY_STATUS>> \u{1F6D1} Demasiados fallos t\xE9cnicos en este elemento "${step.description}". Abortando misi\xF3n.`);
|
|
3663
|
+
const errMsg = e.message || "";
|
|
3664
|
+
if (errMsg.includes("[BLANK_STATE_CRASH]")) {
|
|
3665
|
+
console.error(`
|
|
3666
|
+
\u{1F6D1} [BLANK_STATE] El portal carg\xF3 en blanco despu\xE9s de la acci\xF3n "${step.description || step.action}".`);
|
|
3667
|
+
console.error(` >> Causa: Error de renderizado del portal (White Screen). El agente no puede continuar.`);
|
|
3061
3668
|
hasCriticalError = true;
|
|
3669
|
+
failReason = "portal_render_failure";
|
|
3062
3670
|
isFinished = true;
|
|
3671
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} [BLANK_STATE_CRASH] El portal qued\xF3 en blanco tras navegar. Esto es un bug del portal, no del agente.`);
|
|
3672
|
+
saveMissionResults();
|
|
3063
3673
|
break;
|
|
3064
3674
|
}
|
|
3675
|
+
const isPageClosed = errMsg.includes("Target page, context or browser has been closed") || errMsg.includes("page has been closed") || errMsg.includes("Session closed") || page.isClosed();
|
|
3676
|
+
if (isPageClosed) {
|
|
3677
|
+
console.error(`
|
|
3678
|
+
\u{1F6D1} [PAGE CRASH] La pesta\xF1a colaps\xF3 durante la acci\xF3n "${step.description || step.action}"`);
|
|
3679
|
+
console.error(` >> Error: ${errMsg.substring(0, 150)}`);
|
|
3680
|
+
console.error(` >> Tipo: portal_page_crash | El portal cerr\xF3 el contexto del navegador inesperadamente.`);
|
|
3681
|
+
hasCriticalError = true;
|
|
3682
|
+
failReason = "portal_page_crash";
|
|
3683
|
+
isFinished = true;
|
|
3684
|
+
saveMissionResults();
|
|
3685
|
+
break;
|
|
3686
|
+
}
|
|
3687
|
+
const isPlaywrightTimeout = errMsg.includes("Timeout") && errMsg.includes("ms exceeded");
|
|
3688
|
+
const elementDesc = step.description || "";
|
|
3689
|
+
if (isPlaywrightTimeout && elementDesc) {
|
|
3690
|
+
const prevTimeoutOnSameEl = history.filter(
|
|
3691
|
+
(h) => h.includes("STALE_IDX") && h.includes(`IDX:${step.idx}`)
|
|
3692
|
+
).length;
|
|
3693
|
+
if (prevTimeoutOnSameEl >= 1) {
|
|
3694
|
+
console.error(`
|
|
3695
|
+
\u{1F6D1} [STALE IDX] El elemento "${elementDesc.substring(0, 60)}" produjo Timeout 2 veces.`);
|
|
3696
|
+
console.error(` >> El DOM fue re-renderizado mientras la IA razonaba. El IDX original ya no existe.`);
|
|
3697
|
+
console.error(` >> Tipo: stale_element_timeout | Abortando para evitar ciclo infinito.`);
|
|
3698
|
+
hasCriticalError = true;
|
|
3699
|
+
failReason = "stale_element_timeout";
|
|
3700
|
+
isFinished = true;
|
|
3701
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} [STALE_IDX] Timeout REPETIDO en "${elementDesc}". El elemento fue destruido por un re-render de React/Angular. La misi\xF3n no puede continuar sin intervenci\xF3n manual.`);
|
|
3702
|
+
saveMissionResults();
|
|
3703
|
+
break;
|
|
3704
|
+
} else {
|
|
3705
|
+
history.push(`Turno ${stepCount}: \u274C [STALE_IDX] FALL\xD3 ${step.action} en "${elementDesc}" \u2014 Timeout: El elemento con IDX:${step.idx} ya NO existe en el DOM porque la p\xE1gina se re-renderiz\xF3 mientras razonabas. ACCI\xD3N OBLIGATORIA: En tu pr\xF3ximo turno, NO uses el mismo IDX. La percepci\xF3n ya te dar\xE1 los IDX actualizados. Busca el elemento por su texto visible, no por IDX.`);
|
|
3706
|
+
}
|
|
3707
|
+
} else {
|
|
3708
|
+
history.push(`Turno ${stepCount}: \u274C FALL\xD3 ${step.action} en "${elementDesc}" - Error: ${errMsg.substring(0, 200)}. REGLA MANDATORIA: Debes investigar la causa usando 'inspect_element_details' o 'capture_console_errors' antes de cualquier otro paso.`);
|
|
3709
|
+
}
|
|
3710
|
+
console.warn(` \u26A0\uFE0F Acci\xF3n fallida: ${errMsg.substring(0, 200)}`);
|
|
3711
|
+
containsError = true;
|
|
3712
|
+
guideIdx = 999999;
|
|
3713
|
+
if (stepsData.length > 0) {
|
|
3714
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F6D1} Falla detectada. Invalidando Gu\xEDa de \xC9xito para sincronizar con IA...`);
|
|
3715
|
+
stepsDataBackup = [...stepsData];
|
|
3716
|
+
stepsData.length = 0;
|
|
3717
|
+
}
|
|
3718
|
+
if (!page.isClosed() && (step.type === "li" || (step.description || "").toLowerCase().includes("[li]"))) {
|
|
3719
|
+
console.log(` \u{1F4A1} Sugerencia: El elemento de lista fall\xF3. Intentando cerrar dropdown con Escape...`);
|
|
3720
|
+
await page.keyboard.press("Escape").catch(() => {
|
|
3721
|
+
});
|
|
3722
|
+
}
|
|
3723
|
+
if (!isPlaywrightTimeout) {
|
|
3724
|
+
const errorCount = history.filter((h) => h.includes("FALL\xD3") && h.includes(elementDesc)).length;
|
|
3725
|
+
if (errorCount >= 2) {
|
|
3726
|
+
console.error(`>>ARCALITY_STATUS>> \u{1F6D1} Demasiados fallos t\xE9cnicos en este elemento "${elementDesc}". Abortando misi\xF3n.`);
|
|
3727
|
+
hasCriticalError = true;
|
|
3728
|
+
failReason = "repeated_action_failure";
|
|
3729
|
+
isFinished = true;
|
|
3730
|
+
break;
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3065
3733
|
}
|
|
3066
3734
|
}
|
|
3067
3735
|
}
|
|
@@ -3095,7 +3763,7 @@ function captureValidationRule(errorMessage, context) {
|
|
|
3095
3763
|
}
|
|
3096
3764
|
}
|
|
3097
3765
|
}
|
|
3098
|
-
if (!hasVisibleFailure) {
|
|
3766
|
+
if (!hasVisibleFailure && !hasCriticalError) {
|
|
3099
3767
|
aiMarkedSuccess = true;
|
|
3100
3768
|
hasCriticalError = false;
|
|
3101
3769
|
saveMissionResults();
|
|
@@ -3104,6 +3772,11 @@ function captureValidationRule(errorMessage, context) {
|
|
|
3104
3772
|
console.log(`>>ARCALITY_STATUS>> \u{1F504} Gu\xEDa de \xC9xito restaurada (${stepsDataBackup.length} pasos recuperados).`);
|
|
3105
3773
|
}
|
|
3106
3774
|
await persistCollectiveMemory();
|
|
3775
|
+
} else if (hasCriticalError) {
|
|
3776
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F6D1} Finalizando por error cr\xEDtico: ${failReason || "unknown"}`);
|
|
3777
|
+
aiMarkedSuccess = false;
|
|
3778
|
+
isFinished = true;
|
|
3779
|
+
saveMissionResults();
|
|
3107
3780
|
} else {
|
|
3108
3781
|
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F El agente intent\xF3 finalizar pero se detect\xF3 un ERROR en alert/toast.`);
|
|
3109
3782
|
aiMarkedSuccess = false;
|
|
@@ -3123,7 +3796,18 @@ function captureValidationRule(errorMessage, context) {
|
|
|
3123
3796
|
if (await fb.isVisible()) {
|
|
3124
3797
|
const rawTxt = await fb.innerText();
|
|
3125
3798
|
const txt = rawTxt.toLowerCase();
|
|
3126
|
-
if (
|
|
3799
|
+
if (systemErrorKeywords.test(rawTxt)) {
|
|
3800
|
+
console.error(`
|
|
3801
|
+
\u{1F6D1} [SYSTEM ERROR EN BUCLE PRINCIPAL] Portal report\xF3 error de sistema: "${rawTxt.substring(0, 100)}"`);
|
|
3802
|
+
console.error(` >> Tipo: portal_internal_error | El runner detendr\xE1 la misi\xF3n.`);
|
|
3803
|
+
hasCriticalError = true;
|
|
3804
|
+
failReason = "portal_internal_error";
|
|
3805
|
+
isFinished = true;
|
|
3806
|
+
aiMarkedSuccess = false;
|
|
3807
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} [ERROR_SIST\xC9MICO] El portal devolvi\xF3 un error de servidor en la UI: "${rawTxt.trim().substring(0, 200)}". Este error no puede ser corregido por el agente. Requiere intervenci\xF3n en el backend.`);
|
|
3808
|
+
saveMissionResults();
|
|
3809
|
+
break;
|
|
3810
|
+
} else if (failureKeywords.test(txt)) {
|
|
3127
3811
|
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Feedback de ERROR detectado: "${rawTxt.substring(0, 60)}..."`);
|
|
3128
3812
|
aiMarkedSuccess = false;
|
|
3129
3813
|
captureValidationRule(rawTxt.trim(), `URL: ${page.url()}`);
|
|
@@ -3135,13 +3819,20 @@ function captureValidationRule(errorMessage, context) {
|
|
|
3135
3819
|
history.push(`Turno ${stepCount}: \u{1F6D1} [ERROR_CR\xCDTICO] Feedback de validaci\xF3n/error visible en UI: "${rawTxt.trim()}"`);
|
|
3136
3820
|
} else if (successKeywords.test(txt)) {
|
|
3137
3821
|
if (txt !== lastSeenSuccessToast) {
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3822
|
+
const isOnWizardForm = currentUrl.includes("/new") || currentUrl.includes("/create") || currentUrl.includes("/edit");
|
|
3823
|
+
const isStrongFinalSuccess = txt.includes("completado") || txt.includes("registro exitoso") || txt.includes("creado exitosamente");
|
|
3824
|
+
if (isOnWizardForm && !isStrongFinalSuccess && stepCount < maxSteps - 2) {
|
|
3825
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F4A1} Toast parcial detectado en wizard ("${txt.substring(0, 30)}"). Delegando confirmaci\xF3n a la IA para evitar falso positivo.`);
|
|
3826
|
+
lastSeenSuccessToast = txt;
|
|
3827
|
+
} else {
|
|
3828
|
+
console.log(`>>ARCALITY_STATUS>> \u2728 \xC9xito visual detectado: "${txt.substring(0, 50)}..."`);
|
|
3829
|
+
aiMarkedSuccess = true;
|
|
3830
|
+
isFinished = true;
|
|
3831
|
+
hasCriticalError = false;
|
|
3832
|
+
await persistCollectiveMemory();
|
|
3833
|
+
saveMissionResults();
|
|
3834
|
+
break;
|
|
3835
|
+
}
|
|
3145
3836
|
} else {
|
|
3146
3837
|
console.log(`>>ARCALITY_STATUS>> \u{1F4A1} Mensaje de \xE9xito persistente ignorado.`);
|
|
3147
3838
|
}
|
|
@@ -3153,6 +3844,50 @@ function captureValidationRule(errorMessage, context) {
|
|
|
3153
3844
|
if (aiMarkedSuccess) {
|
|
3154
3845
|
hasCriticalError = false;
|
|
3155
3846
|
await persistCollectiveMemory();
|
|
3847
|
+
if (!usedDirectReuse && stepsData.length > 0) {
|
|
3848
|
+
const normalizedPrompt = prompt.toLowerCase().trim();
|
|
3849
|
+
const patternKey = crypto2.createHash("sha256").update(normalizedPrompt).digest("hex");
|
|
3850
|
+
if (!hasCriticalError) {
|
|
3851
|
+
const isDuplicate = patternContext !== "" || usedDirectReuse;
|
|
3852
|
+
if (isDuplicate) {
|
|
3853
|
+
console.log(`>>ARCALITY_STATUS>> \u2139\uFE0F No se guardar\xE1 el patr\xF3n. duplicate_skipped: true (ya existe uno similar o se us\xF3 directamente)`);
|
|
3854
|
+
}
|
|
3855
|
+
const stepsToSave = stepsData.length > 0 ? stepsData : stepsDataBackup;
|
|
3856
|
+
const hasCompleteSteps = stepsToSave.length >= stepsDataBackup.length;
|
|
3857
|
+
if (!isDuplicate && hasCompleteSteps) {
|
|
3858
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F4BE} Guardando nuevo patr\xF3n de ejecuci\xF3n (${stepsToSave.length} pasos, pattern_saved: true)...`);
|
|
3859
|
+
const sanitizedSteps = stepsToSave.map((step, idx) => {
|
|
3860
|
+
const s = JSON.parse(JSON.stringify(step));
|
|
3861
|
+
s.step_index = idx;
|
|
3862
|
+
if (s.action_data && s.action_data.value) {
|
|
3863
|
+
if (s.action_data.value === process.env.LOGIN_USER)
|
|
3864
|
+
s.action_data.value = "{{username}}";
|
|
3865
|
+
else if (s.action_data.value === process.env.LOGIN_PASSWORD)
|
|
3866
|
+
s.action_data.value = "{{password}}";
|
|
3867
|
+
else if (s.componentType === "password" || s.action_data.value.length > 30)
|
|
3868
|
+
s.action_data.value = "{{secret}}";
|
|
3869
|
+
}
|
|
3870
|
+
return s;
|
|
3871
|
+
});
|
|
3872
|
+
await savePromptPattern({
|
|
3873
|
+
original_prompt: prompt,
|
|
3874
|
+
pattern_key: patternKey,
|
|
3875
|
+
name: prompt.length > 50 ? prompt.substring(0, 47) + "..." : prompt,
|
|
3876
|
+
pattern_json: JSON.stringify({
|
|
3877
|
+
history,
|
|
3878
|
+
steps: sanitizedSteps.length,
|
|
3879
|
+
steps_technical: sanitizedSteps,
|
|
3880
|
+
assertions: [],
|
|
3881
|
+
variables: {}
|
|
3882
|
+
}),
|
|
3883
|
+
generated_playwright_code: ""
|
|
3884
|
+
}).catch(() => {
|
|
3885
|
+
});
|
|
3886
|
+
} else if (!isDuplicate && !hasCompleteSteps) {
|
|
3887
|
+
console.warn(`>>ARCALITY_STATUS>> \u26A0\uFE0F Patr\xF3n NO guardado: stepsData (${stepsToSave.length}) < stepsDataBackup (${stepsDataBackup.length}). El patr\xF3n existente en BD es m\xE1s completo y se preserva.`);
|
|
3888
|
+
}
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3156
3891
|
console.log("\u270D\uFE0F [HUMANIZANDO] Generando resumen de \xE9xito para el reporte...");
|
|
3157
3892
|
const summary = await agent.humanizeMissionResult(prompt, history);
|
|
3158
3893
|
if (summary) {
|
|
@@ -3204,13 +3939,33 @@ Evidence: ${JSON.stringify(v.evidence).substring(0, 400)}`,
|
|
|
3204
3939
|
} catch (e) {
|
|
3205
3940
|
console.error("Error in Security Scan:", e.message);
|
|
3206
3941
|
}
|
|
3942
|
+
if (!aiMarkedSuccess && !failReason) {
|
|
3943
|
+
if (stepCount >= maxSteps)
|
|
3944
|
+
failReason = "max_steps_reached";
|
|
3945
|
+
else
|
|
3946
|
+
failReason = "unknown_failure";
|
|
3947
|
+
}
|
|
3207
3948
|
saveMissionResults();
|
|
3208
3949
|
if (!aiMarkedSuccess) {
|
|
3209
3950
|
console.log(`>>ARCALITY_STATUS>> \u{1F4CA} Uso total de tokens esta misi\xF3n: IN=${totalMissionUsage.input_tokens} | OUT=${totalMissionUsage.output_tokens} | Cache writes=${totalMissionUsage.cache_creation_input_tokens} | Cache reads=${totalMissionUsage.cache_read_input_tokens}`);
|
|
3210
3951
|
await persistCollectiveMemory(true).catch(() => {
|
|
3211
3952
|
});
|
|
3212
|
-
throw new Error(
|
|
3953
|
+
throw new Error(`Misi\xF3n no completada o finalizada con errores. Raz\xF3n: ${failReason}`);
|
|
3213
3954
|
} else {
|
|
3214
3955
|
console.log(`>>ARCALITY_STATUS>> \u{1F4CA} Uso total de tokens esta misi\xF3n: IN=${totalMissionUsage.input_tokens} | OUT=${totalMissionUsage.output_tokens} | Cache writes=${totalMissionUsage.cache_creation_input_tokens} | Cache reads=${totalMissionUsage.cache_read_input_tokens}`);
|
|
3215
3956
|
}
|
|
3957
|
+
if (fs2.existsSync(contextDir)) {
|
|
3958
|
+
try {
|
|
3959
|
+
const files = fs2.readdirSync(contextDir);
|
|
3960
|
+
for (const file of files) {
|
|
3961
|
+
if (file.endsWith(".json") || file.endsWith(".png")) {
|
|
3962
|
+
fs2.unlinkSync(path2.join(contextDir, file));
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
fs2.rmSync(contextDir, { recursive: true, force: true });
|
|
3966
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F9F9} Carpeta context eliminada (Memoria ef\xEDmera completada).`);
|
|
3967
|
+
} catch (err) {
|
|
3968
|
+
console.warn(`No se pudo limpiar la carpeta context: ${err}`);
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3216
3971
|
});
|