@arcadialdev/arcality 4.1.3 → 4.1.5
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 +1 -1
- package/playwright.config.js +56 -56
- package/scripts/gen-and-run.mjs +5 -0
- package/src/arcalityClient.mjs +67 -64
- package/src/services/mcpStdioClient.mjs +204 -204
- package/tests/_helpers/ArcalityReporter.js +1342 -1342
- package/tests/_helpers/agentic-runner.bundle.spec.js +291 -20
|
@@ -648,7 +648,14 @@ function simpleHash(str) {
|
|
|
648
648
|
}
|
|
649
649
|
function normalizeBugClassification(value) {
|
|
650
650
|
const text = String(value || "").trim();
|
|
651
|
-
const allowed = [
|
|
651
|
+
const allowed = [
|
|
652
|
+
"[Backend Bug]",
|
|
653
|
+
"[Frontend Bug]",
|
|
654
|
+
"[UI Regression]",
|
|
655
|
+
"[Flaky/Stale Selector]",
|
|
656
|
+
"[Interaction Blocked]",
|
|
657
|
+
"[Validation Error]"
|
|
658
|
+
];
|
|
652
659
|
return allowed.find((category) => text.includes(category)) || "";
|
|
653
660
|
}
|
|
654
661
|
async function createAdoTask(title, description, metadata = {}) {
|
|
@@ -3457,6 +3464,10 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
3457
3464
|
score = 80;
|
|
3458
3465
|
else if (type === "DROPDOWN")
|
|
3459
3466
|
score = 70;
|
|
3467
|
+
const isFieldTag = tag === "input" || tag === "textarea" || tag === "select";
|
|
3468
|
+
const isDisabledEl = htmlEl.disabled === true || htmlEl.hasAttribute("disabled") || htmlEl.getAttribute("aria-disabled") === "true";
|
|
3469
|
+
const isReadonlyEl = htmlEl.readOnly === true || htmlEl.hasAttribute("readonly");
|
|
3470
|
+
const isEditableEl = isFieldTag ? !isDisabledEl && !isReadonlyEl : true;
|
|
3460
3471
|
return {
|
|
3461
3472
|
idx,
|
|
3462
3473
|
type,
|
|
@@ -3465,7 +3476,10 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
3465
3476
|
frameIdx: 0,
|
|
3466
3477
|
// Placeholder
|
|
3467
3478
|
selector: `[agent-idx="${idx}"]`,
|
|
3468
|
-
score
|
|
3479
|
+
score,
|
|
3480
|
+
disabled: isDisabledEl,
|
|
3481
|
+
readonly: isReadonlyEl,
|
|
3482
|
+
editable: isEditableEl
|
|
3469
3483
|
};
|
|
3470
3484
|
}).filter(Boolean);
|
|
3471
3485
|
}, { startIdx: globalIdx });
|
|
@@ -3585,20 +3599,33 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
3585
3599
|
}
|
|
3586
3600
|
fallbackFailureClassification(failReason, failReasoning = "") {
|
|
3587
3601
|
const reason = `${failReason} ${failReasoning}`.toLowerCase();
|
|
3602
|
+
if (reason.includes("input_not_editable") || reason.includes("blocked_by_disabled_control") || reason.includes("ui_overlay_blocking") || reason.includes("not_editable") || reason.includes("readonly") || reason.includes("disabled") || reason.includes("overlay")) {
|
|
3603
|
+
return "[Interaction Blocked]";
|
|
3604
|
+
}
|
|
3605
|
+
if (reason.includes("validation_error_unhandled") || reason.includes("validation")) {
|
|
3606
|
+
return "[Validation Error]";
|
|
3607
|
+
}
|
|
3588
3608
|
if (reason.includes("network") || reason.includes("backend") || reason.includes("server") || reason.includes("internal_error") || reason.includes("500")) {
|
|
3589
3609
|
return "[Backend Bug]";
|
|
3590
3610
|
}
|
|
3591
3611
|
if (reason.includes("stale") || reason.includes("timeout") || reason.includes("selector") || reason.includes("idx")) {
|
|
3592
3612
|
return "[Flaky/Stale Selector]";
|
|
3593
3613
|
}
|
|
3594
|
-
if (reason.includes("render") || reason.includes("blank") || reason.includes("ui_validation")) {
|
|
3614
|
+
if (reason.includes("render") || reason.includes("blank") || reason.includes("ui_validation") || reason.includes("expected_element_never_appeared")) {
|
|
3595
3615
|
return "[UI Regression]";
|
|
3596
3616
|
}
|
|
3597
3617
|
return "[Frontend Bug]";
|
|
3598
3618
|
}
|
|
3599
3619
|
normalizeFailureClassification(raw) {
|
|
3600
3620
|
const text = (raw || "").trim();
|
|
3601
|
-
const allowed = [
|
|
3621
|
+
const allowed = [
|
|
3622
|
+
"[Backend Bug]",
|
|
3623
|
+
"[Frontend Bug]",
|
|
3624
|
+
"[UI Regression]",
|
|
3625
|
+
"[Flaky/Stale Selector]",
|
|
3626
|
+
"[Interaction Blocked]",
|
|
3627
|
+
"[Validation Error]"
|
|
3628
|
+
];
|
|
3602
3629
|
return allowed.find((category) => text.includes(category)) || null;
|
|
3603
3630
|
}
|
|
3604
3631
|
/**
|
|
@@ -3638,7 +3665,7 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
3638
3665
|
body: JSON.stringify({
|
|
3639
3666
|
model: process.env.CLAUDE_MODEL || "claude-4-5-sonnet-latest",
|
|
3640
3667
|
max_tokens: 32,
|
|
3641
|
-
system: "You classify terminal E2E QA failures. Reply with
|
|
3668
|
+
system: "You classify terminal E2E QA failures. Reply with EXACTLY one category string and no explanation. Categories:\n- [Interaction Blocked]: a target element exists but does not accept the required action (input readonly/disabled, action button disabled, an overlay/modal intercepts clicks).\n- [Validation Error]: the portal rejected the submitted data with a visible validation message that was never resolved.\n- [Backend Bug]: server/network failure, HTTP 500, XHR/fetch error, or a system error surfaced from the backend.\n- [UI Regression]: page/widgets failed to render, blank state, or an expected element never appeared.\n- [Flaky/Stale Selector]: element went stale, timed out, or the selector no longer resolves.\n- [Frontend Bug]: any other client-side defect not covered above.\nPrefer the most specific category. Use the fail_reason field as the strongest signal.",
|
|
3642
3669
|
messages: [
|
|
3643
3670
|
{
|
|
3644
3671
|
role: "user",
|
|
@@ -3658,6 +3685,79 @@ ${JSON.stringify(diagnosticPayload, null, 2)}`
|
|
|
3658
3685
|
return fallback;
|
|
3659
3686
|
}
|
|
3660
3687
|
}
|
|
3688
|
+
/**
|
|
3689
|
+
* DIAGNÓSTICO DE ATASCO (respaldo IA).
|
|
3690
|
+
* Cuando las sondas estáticas no identifican una causa clara, esta llamada analiza el
|
|
3691
|
+
* historial + señales del DOM y devuelve el `fail_reason` más específico posible en lugar
|
|
3692
|
+
* de dejar que todo recaiga en "loop_detected". Devuelve null si no hay IA disponible o falla.
|
|
3693
|
+
*/
|
|
3694
|
+
async diagnoseStallWithAI(payload) {
|
|
3695
|
+
const isProxyMode = !!process.env.ARCALITY_API_URL;
|
|
3696
|
+
if (!isProxyMode && !process.env.ANTHROPIC_API_KEY)
|
|
3697
|
+
return null;
|
|
3698
|
+
const allowedReasons = [
|
|
3699
|
+
"input_not_editable",
|
|
3700
|
+
"blocked_by_disabled_control",
|
|
3701
|
+
"validation_error_unhandled",
|
|
3702
|
+
"ui_overlay_blocking",
|
|
3703
|
+
"stale_element",
|
|
3704
|
+
"expected_element_never_appeared",
|
|
3705
|
+
"loop_detected"
|
|
3706
|
+
];
|
|
3707
|
+
try {
|
|
3708
|
+
const endpointUrl = isProxyMode ? `${process.env.ARCALITY_API_URL}/api/v1/ai/proxy` : "https://api.anthropic.com/v1/messages";
|
|
3709
|
+
const headers = { "Content-Type": "application/json" };
|
|
3710
|
+
if (isProxyMode) {
|
|
3711
|
+
headers["x-api-key"] = process.env.ARCALITY_API_KEY || "";
|
|
3712
|
+
const missionId = process.env.ARCALITY_MISSION_ID || "";
|
|
3713
|
+
if (missionId)
|
|
3714
|
+
headers["x-mission-id"] = missionId;
|
|
3715
|
+
} else {
|
|
3716
|
+
headers["x-api-key"] = process.env.ANTHROPIC_API_KEY;
|
|
3717
|
+
headers["anthropic-version"] = "2023-06-01";
|
|
3718
|
+
}
|
|
3719
|
+
const diagnosticPayload = {
|
|
3720
|
+
mission: payload.mission,
|
|
3721
|
+
loop_type: payload.loop_type || "unknown",
|
|
3722
|
+
loop_targets: payload.loop_targets || [],
|
|
3723
|
+
recent_history: payload.recent_history.slice(-12),
|
|
3724
|
+
dom_signals: payload.dom_signals || {},
|
|
3725
|
+
critical_network_error: this.criticalNetworkError || "None",
|
|
3726
|
+
critical_js_error: this.criticalJsError || "None"
|
|
3727
|
+
};
|
|
3728
|
+
const response = await fetch(endpointUrl, {
|
|
3729
|
+
method: "POST",
|
|
3730
|
+
headers,
|
|
3731
|
+
body: JSON.stringify({
|
|
3732
|
+
model: process.env.CLAUDE_MODEL || "claude-4-5-sonnet-latest",
|
|
3733
|
+
max_tokens: 300,
|
|
3734
|
+
system: 'You are the root-cause diagnostician of an autonomous E2E QA agent that got STUCK repeating actions. Do NOT assume it is a generic loop. Determine WHY the agent could not progress and pick the single most specific fail_reason from this list:\n- input_not_editable: the field the agent had to type into is readonly/disabled and never accepted the value.\n- blocked_by_disabled_control: the button/control the agent must click is disabled (e.g. a submit button stays disabled because the form is invalid).\n- validation_error_unhandled: the portal shows a visible validation/error message rejecting the data, and the agent could not resolve it.\n- ui_overlay_blocking: a modal/overlay/backdrop intercepts the clicks so the intended element is never reached.\n- stale_element: the target element keeps appearing/disappearing or the selector no longer resolves.\n- expected_element_never_appeared: the agent is waiting for an element/result (modal, message, next step) that never renders.\n- loop_detected: ONLY if none of the above fit \u2014 the agent genuinely repeats valid actions with no identifiable blocker.\nReply ONLY with strict JSON: {"fail_reason":"<one of the list>","reasoning":"<one concise sentence in Spanish naming the concrete element/state>"}. No markdown, no extra text.',
|
|
3735
|
+
messages: [
|
|
3736
|
+
{
|
|
3737
|
+
role: "user",
|
|
3738
|
+
content: `Diagnose why this Arcality QA agent got stuck:
|
|
3739
|
+
${JSON.stringify(diagnosticPayload, null, 2)}`
|
|
3740
|
+
}
|
|
3741
|
+
],
|
|
3742
|
+
...!isProxyMode ? { temperature: 0 } : {}
|
|
3743
|
+
})
|
|
3744
|
+
});
|
|
3745
|
+
if (!response.ok)
|
|
3746
|
+
return null;
|
|
3747
|
+
const data = await response.json();
|
|
3748
|
+
const rawText = (data.content?.[0]?.text || "").trim();
|
|
3749
|
+
const jsonMatch = rawText.match(/\{[\s\S]*\}/);
|
|
3750
|
+
if (!jsonMatch)
|
|
3751
|
+
return null;
|
|
3752
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
3753
|
+
const failReason = String(parsed.fail_reason || "").trim();
|
|
3754
|
+
if (!allowedReasons.includes(failReason))
|
|
3755
|
+
return null;
|
|
3756
|
+
return { failReason, reasoning: String(parsed.reasoning || "").trim() };
|
|
3757
|
+
} catch {
|
|
3758
|
+
return null;
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3661
3761
|
/**
|
|
3662
3762
|
* Busca una acci�n sugerida en la memoria SIN usar tokens.
|
|
3663
3763
|
* Compara URL, componentes visuales Y palabras clave del prompt.
|
|
@@ -3831,9 +3931,16 @@ ${JSON.stringify(diagnosticPayload, null, 2)}`
|
|
|
3831
3931
|
const state = await this.getPageState();
|
|
3832
3932
|
const screenshot = await this.page.screenshot({ type: "png" });
|
|
3833
3933
|
const base64Img = screenshot.toString("base64");
|
|
3834
|
-
const componentsList = state.components.map(
|
|
3835
|
-
|
|
3836
|
-
|
|
3934
|
+
const componentsList = state.components.map((c) => {
|
|
3935
|
+
let stateTag = "";
|
|
3936
|
+
if (c.disabled)
|
|
3937
|
+
stateTag = " | STATE:DISABLED (no se puede clicar/escribir)";
|
|
3938
|
+
else if (c.readonly)
|
|
3939
|
+
stateTag = " | STATE:READONLY (no se puede escribir directamente)";
|
|
3940
|
+
else if (c.editable === false && c.type === "FIELD")
|
|
3941
|
+
stateTag = " | STATE:NOT_EDITABLE";
|
|
3942
|
+
return `IDX:${c.idx} | TYPE:${c.type} | NAME: "${c.name}"${c.value ? ` | VAL:"${c.value}"` : ""}${stateTag} `;
|
|
3943
|
+
}).join("\n");
|
|
3837
3944
|
const rawTools = [
|
|
3838
3945
|
{
|
|
3839
3946
|
name: "perform_ui_actions",
|
|
@@ -5411,6 +5518,12 @@ function writeBatchMissionResult(payload) {
|
|
|
5411
5518
|
} catch {
|
|
5412
5519
|
}
|
|
5413
5520
|
}
|
|
5521
|
+
function hasVisualAttachment(testInfo) {
|
|
5522
|
+
const attachments = Array.isArray(testInfo?.attachments) ? testInfo.attachments : [];
|
|
5523
|
+
return attachments.some(
|
|
5524
|
+
(att) => typeof att?.contentType === "string" && (att.contentType.startsWith("image/") || att.contentType.startsWith("video/"))
|
|
5525
|
+
);
|
|
5526
|
+
}
|
|
5414
5527
|
(0, import_test.test)("Arcality AI Runner", async ({ page }, testInfo) => {
|
|
5415
5528
|
import_test.test.setTimeout(12e5);
|
|
5416
5529
|
const contextDir = process.env.CONTEXT_DIR;
|
|
@@ -5494,6 +5607,7 @@ function writeBatchMissionResult(payload) {
|
|
|
5494
5607
|
let bugClassification = "";
|
|
5495
5608
|
let databaseValidationReport = null;
|
|
5496
5609
|
const missionSummaryPaths = /* @__PURE__ */ new Set();
|
|
5610
|
+
const blockedInteractions = [];
|
|
5497
5611
|
const updateSavedMissionSummaries = () => {
|
|
5498
5612
|
for (const summaryPath of missionSummaryPaths) {
|
|
5499
5613
|
try {
|
|
@@ -5513,6 +5627,31 @@ function writeBatchMissionResult(payload) {
|
|
|
5513
5627
|
}
|
|
5514
5628
|
}
|
|
5515
5629
|
};
|
|
5630
|
+
const attachEvidenceFile = async (name, filePath, contentType, onlyIfMissingVisual = false) => {
|
|
5631
|
+
if (!testInfo || !fs7.existsSync(filePath))
|
|
5632
|
+
return false;
|
|
5633
|
+
if (onlyIfMissingVisual && hasVisualAttachment(testInfo))
|
|
5634
|
+
return false;
|
|
5635
|
+
try {
|
|
5636
|
+
await testInfo.attach(name, { path: filePath, contentType });
|
|
5637
|
+
return true;
|
|
5638
|
+
} catch {
|
|
5639
|
+
return false;
|
|
5640
|
+
}
|
|
5641
|
+
};
|
|
5642
|
+
const attachFallbackScreenshot = async (name, onlyIfMissingVisual = true) => {
|
|
5643
|
+
if (!testInfo || page.isClosed())
|
|
5644
|
+
return false;
|
|
5645
|
+
if (onlyIfMissingVisual && hasVisualAttachment(testInfo))
|
|
5646
|
+
return false;
|
|
5647
|
+
try {
|
|
5648
|
+
const buffer = await page.screenshot({ type: "png", fullPage: true });
|
|
5649
|
+
await testInfo.attach(name, { body: buffer, contentType: "image/png" });
|
|
5650
|
+
return true;
|
|
5651
|
+
} catch {
|
|
5652
|
+
return false;
|
|
5653
|
+
}
|
|
5654
|
+
};
|
|
5516
5655
|
const updateSavedMissionClassification = updateSavedMissionSummaries;
|
|
5517
5656
|
const runPostMissionDatabaseValidations = async () => {
|
|
5518
5657
|
if (!aiMarkedSuccess || hasCriticalError || databaseValidationReport)
|
|
@@ -5929,6 +6068,99 @@ ${readableHistory}`;
|
|
|
5929
6068
|
} catch (e) {
|
|
5930
6069
|
console.warn(`Error buscando patrones: ${e.message}`);
|
|
5931
6070
|
}
|
|
6071
|
+
const diagnoseStall = async (loopType, loopTargets) => {
|
|
6072
|
+
const targetsStr = loopTargets.join(", ");
|
|
6073
|
+
try {
|
|
6074
|
+
const evidencePath = path8.join(contextDir || ".", `stall-diagnosis-${stepCount}-${Date.now()}.png`);
|
|
6075
|
+
await page.screenshot({ path: evidencePath, fullPage: true }).catch(() => {
|
|
6076
|
+
});
|
|
6077
|
+
await attachEvidenceFile("stall_diagnosis", evidencePath, "image/png");
|
|
6078
|
+
} catch {
|
|
6079
|
+
}
|
|
6080
|
+
const recentBlocks = blockedInteractions.filter((b) => stepCount - b.turn <= 8);
|
|
6081
|
+
const blockedFill = recentBlocks.find((b) => b.action === "fill");
|
|
6082
|
+
if (blockedFill) {
|
|
6083
|
+
return {
|
|
6084
|
+
failReason: "input_not_editable",
|
|
6085
|
+
failReasoning: `El campo "${blockedFill.target}" est\xE1 ${blockedFill.state} y no admiti\xF3 el valor solicitado. Arcality intent\xF3 escribir ah\xED pero el portal no lo permite por dise\xF1o (readonly/disabled), por lo que la misi\xF3n no pudo avanzar. Esto NO es un bucle: es un campo requerido que no acepta entrada directa.`
|
|
6086
|
+
};
|
|
6087
|
+
}
|
|
6088
|
+
const blockedClick = recentBlocks.find((b) => b.action !== "fill");
|
|
6089
|
+
if (blockedClick) {
|
|
6090
|
+
return {
|
|
6091
|
+
failReason: "blocked_by_disabled_control",
|
|
6092
|
+
failReasoning: `El control "${blockedClick.target}" estaba deshabilitado (disabled/aria-disabled) al intentar interactuar. Arcality no pudo avanzar porque el portal mantiene el control bloqueado (ej. un bot\xF3n de acci\xF3n que sigue inhabilitado). No es un bucle sino un control bloqueado.`
|
|
6093
|
+
};
|
|
6094
|
+
}
|
|
6095
|
+
const domSignals = await page.evaluate(() => {
|
|
6096
|
+
const isVisible = (el) => {
|
|
6097
|
+
const r = el.getBoundingClientRect();
|
|
6098
|
+
const s = getComputedStyle(el);
|
|
6099
|
+
return r.width > 0 && r.height > 0 && s.visibility !== "hidden" && s.display !== "none" && s.opacity !== "0";
|
|
6100
|
+
};
|
|
6101
|
+
let validationMsg = "";
|
|
6102
|
+
const errSelectors = '[role="alert"], [aria-invalid="true"], .error, .invalid, .is-invalid, .field-error, .Mui-error, .validation-message, .help-block.error, .text-danger';
|
|
6103
|
+
for (const el of Array.from(document.querySelectorAll(errSelectors))) {
|
|
6104
|
+
if (!isVisible(el))
|
|
6105
|
+
continue;
|
|
6106
|
+
const txt = (el.innerText || "").trim();
|
|
6107
|
+
if (txt && txt.length > 1 && txt.length < 200) {
|
|
6108
|
+
validationMsg = txt;
|
|
6109
|
+
break;
|
|
6110
|
+
}
|
|
6111
|
+
}
|
|
6112
|
+
let overlayLabel = "";
|
|
6113
|
+
const overlaySelectors = '.modal.show, .modal[style*="display: block"], [role="dialog"], .cdk-overlay-backdrop, .overlay, .backdrop, .modal-backdrop, .p-dialog-mask, .mat-dialog-container';
|
|
6114
|
+
for (const el of Array.from(document.querySelectorAll(overlaySelectors))) {
|
|
6115
|
+
if (!isVisible(el))
|
|
6116
|
+
continue;
|
|
6117
|
+
const r = el.getBoundingClientRect();
|
|
6118
|
+
if (r.width >= window.innerWidth * 0.4 && r.height >= window.innerHeight * 0.4) {
|
|
6119
|
+
overlayLabel = (el.className || el.tagName || "overlay").toString().substring(0, 60);
|
|
6120
|
+
break;
|
|
6121
|
+
}
|
|
6122
|
+
}
|
|
6123
|
+
return { validationMsg, overlayLabel };
|
|
6124
|
+
}).catch(() => ({ validationMsg: "", overlayLabel: "" }));
|
|
6125
|
+
if (domSignals.validationMsg) {
|
|
6126
|
+
return {
|
|
6127
|
+
failReason: "validation_error_unhandled",
|
|
6128
|
+
failReasoning: `El portal muestra un mensaje de validaci\xF3n/error visible que no fue resuelto: "${domSignals.validationMsg}". El formulario rechaza los datos y Arcality no logr\xF3 corregirlos, por lo que no pudo continuar. No es un bucle sino una validaci\xF3n de formulario pendiente.`
|
|
6129
|
+
};
|
|
6130
|
+
}
|
|
6131
|
+
if (domSignals.overlayLabel) {
|
|
6132
|
+
return {
|
|
6133
|
+
failReason: "ui_overlay_blocking",
|
|
6134
|
+
failReasoning: `Un overlay/modal ("${domSignals.overlayLabel}") permanece visible y cubre la interfaz, interceptando las interacciones. Arcality repite acciones porque el elemento objetivo queda tapado por esta capa. No es un bucle l\xF3gico sino un overlay que bloquea la UI.`
|
|
6135
|
+
};
|
|
6136
|
+
}
|
|
6137
|
+
if (agent.criticalNetworkError) {
|
|
6138
|
+
return { failReason: "portal_network_error", failReasoning: `Durante el atasco se detect\xF3 un fallo de red del portal: ${agent.criticalNetworkError}` };
|
|
6139
|
+
}
|
|
6140
|
+
if (agent.criticalJsError) {
|
|
6141
|
+
return { failReason: "portal_js_crash", failReasoning: `Durante el atasco se detect\xF3 una excepci\xF3n JavaScript del portal: ${agent.criticalJsError}` };
|
|
6142
|
+
}
|
|
6143
|
+
try {
|
|
6144
|
+
const aiDiag = await agent.diagnoseStallWithAI({
|
|
6145
|
+
mission: prompt,
|
|
6146
|
+
loop_type: loopType,
|
|
6147
|
+
loop_targets: loopTargets,
|
|
6148
|
+
recent_history: history,
|
|
6149
|
+
dom_signals: domSignals
|
|
6150
|
+
});
|
|
6151
|
+
if (aiDiag && aiDiag.failReason && aiDiag.failReason !== "loop_detected") {
|
|
6152
|
+
return {
|
|
6153
|
+
failReason: aiDiag.failReason,
|
|
6154
|
+
failReasoning: aiDiag.reasoning || `Diagn\xF3stico IA del atasco: ${aiDiag.failReason} sobre ${targetsStr}.`
|
|
6155
|
+
};
|
|
6156
|
+
}
|
|
6157
|
+
} catch {
|
|
6158
|
+
}
|
|
6159
|
+
return {
|
|
6160
|
+
failReason: "loop_detected",
|
|
6161
|
+
failReasoning: `Arcality QA repiti\xF3 acciones de tipo ${loopType} sobre ${targetsStr} sin cambios significativos en el DOM y sin una causa de bloqueo identificable (campo/control bloqueado, validaci\xF3n, overlay o error del portal).`
|
|
6162
|
+
};
|
|
6163
|
+
};
|
|
5932
6164
|
const maxTotalIterations = maxSteps + 50;
|
|
5933
6165
|
let totalIterations = 0;
|
|
5934
6166
|
while (!isFinished && stepCount < maxSteps) {
|
|
@@ -6265,16 +6497,17 @@ ${patternContext}` : prompt;
|
|
|
6265
6497
|
const loopType = isClickLoop ? "click" : "fill";
|
|
6266
6498
|
const uniqueTargets = isClickLoop ? uniqueClicks : uniqueFills;
|
|
6267
6499
|
console.warn(`
|
|
6268
|
-
\u26A0
|
|
6500
|
+
\u26A0 Estancamiento detectado (${loopType}). Ejecutando diagn\xF3stico de causa ra\xEDz...`);
|
|
6269
6501
|
console.warn(` \xDAltimas acciones: ${lastActions.join(" \u2192 ")}`);
|
|
6270
|
-
|
|
6502
|
+
const diagnosis = await diagnoseStall(loopType, Array.from(uniqueTargets));
|
|
6503
|
+
console.warn(` >> Diagn\xF3stico: ${diagnosis.failReason}`);
|
|
6504
|
+
response.thought = `\u{1F6D1} ${diagnosis.failReason.toUpperCase()}: ${diagnosis.failReasoning}`;
|
|
6271
6505
|
response.finish = true;
|
|
6272
6506
|
response.actions = [];
|
|
6273
6507
|
aiMarkedSuccess = false;
|
|
6274
6508
|
hasCriticalError = true;
|
|
6275
|
-
failReason =
|
|
6276
|
-
failReasoning =
|
|
6277
|
-
Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(uniqueTargets).join(", ")}.`;
|
|
6509
|
+
failReason = diagnosis.failReason;
|
|
6510
|
+
failReasoning = diagnosis.failReasoning;
|
|
6278
6511
|
} else if ((isClickLoop || isFillLoop) && agentRecovered) {
|
|
6279
6512
|
console.log(`>>ARCALITY_STATUS>> \u{1F504} Loop candidato detectado pero Arcality se recuper\xF3 en este turno. Continuando misi\xF3n.`);
|
|
6280
6513
|
}
|
|
@@ -6293,6 +6526,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
6293
6526
|
console.error(` >> El portal carg\xF3 el shell (navegaci\xF3n, header) pero los WIDGETS/COMPONENTES quedaron en blanco.`);
|
|
6294
6527
|
console.error(` >> Evidencia guardada: ${blankEvidencePath}`);
|
|
6295
6528
|
console.error(` >> Tipo: portal_rendering_failure | Esto es un bug del portal, NO de Arcality.`);
|
|
6529
|
+
await attachEvidenceFile("portal_render_failure", blankEvidencePath, "image/png");
|
|
6296
6530
|
response.thought = `\u{1F6D1} [BUG DEL PORTAL DETECTADO] La p\xE1gina ${blankUrl} carg\xF3 su estructura pero los componentes principales (widgets, tablas, listas) NO se renderizaron. Arcality estuvo esperando ${consecutiveWaits} turnos sin mejora. Esto es un fallo de renderizado del portal.`;
|
|
6297
6531
|
response.finish = true;
|
|
6298
6532
|
response.actions = [];
|
|
@@ -6393,6 +6627,13 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
6393
6627
|
}
|
|
6394
6628
|
await loc.scrollIntoViewIfNeeded({ timeout: 5e3 }).catch(() => {
|
|
6395
6629
|
});
|
|
6630
|
+
const ctrlDisabled = await loc.evaluate(
|
|
6631
|
+
(el) => el.disabled === true || el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true"
|
|
6632
|
+
).catch(() => false);
|
|
6633
|
+
if (ctrlDisabled) {
|
|
6634
|
+
console.log(` \u{1F6AB} Control "${desc}" est\xE1 disabled al momento del click.`);
|
|
6635
|
+
blockedInteractions.push({ turn: stepCount, action: step.action, target: desc, state: "disabled" });
|
|
6636
|
+
}
|
|
6396
6637
|
if (step.action === "double_click") {
|
|
6397
6638
|
await loc.dblclick({ timeout: 4e3, force: true });
|
|
6398
6639
|
} else {
|
|
@@ -6537,9 +6778,22 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
6537
6778
|
} else {
|
|
6538
6779
|
await loc.click({ timeout: 5e3 }).catch(() => {
|
|
6539
6780
|
});
|
|
6540
|
-
|
|
6541
|
-
await loc.
|
|
6542
|
-
|
|
6781
|
+
const fillDesc = (step.description || step.selector || "").substring(0, 80);
|
|
6782
|
+
const writeState = await loc.evaluate((el) => ({
|
|
6783
|
+
disabled: el.disabled === true || el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true",
|
|
6784
|
+
readonly: el.readOnly === true || el.hasAttribute("readonly")
|
|
6785
|
+
})).catch(() => ({ disabled: false, readonly: false }));
|
|
6786
|
+
const isEditable = await loc.isEditable({ timeout: 1e3 }).catch(() => !(writeState.disabled || writeState.readonly));
|
|
6787
|
+
if (!isEditable || writeState.disabled || writeState.readonly) {
|
|
6788
|
+
const stateLabel = writeState.disabled ? "disabled" : writeState.readonly ? "readonly" : "not-editable";
|
|
6789
|
+
console.log(` \u{1F6AB} Campo "${fillDesc}" est\xE1 ${stateLabel}. No se puede escribir "${step.value}". Registrando bloqueo de interacci\xF3n.`);
|
|
6790
|
+
blockedInteractions.push({ turn: stepCount, action: "fill", target: fillDesc, state: stateLabel });
|
|
6791
|
+
history.push(`Turno ${stepCount}: \u{1F6AB} [CAMPO BLOQUEADO] El campo "${fillDesc}" est\xE1 ${stateLabel}; el valor "${step.value}" NO pudo capturarse. Si existe otra forma de entrada v\xE1lida (otro campo, un flujo alterno), \xFAsala. Si no la hay, este es un bloqueo del portal \u2014 NO repitas la misma acci\xF3n.`);
|
|
6792
|
+
} else {
|
|
6793
|
+
await loc.fill(step.value, { timeout: 1e4 });
|
|
6794
|
+
await loc.press("Tab").catch(() => {
|
|
6795
|
+
});
|
|
6796
|
+
}
|
|
6543
6797
|
}
|
|
6544
6798
|
await page.waitForTimeout(500);
|
|
6545
6799
|
} else if (step.action === "wait") {
|
|
@@ -6569,6 +6823,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
6569
6823
|
console.error(` >> ${emptyPanelCount} de ${panelCount} paneles est\xE1n en blanco.`);
|
|
6570
6824
|
console.error(` >> Tipo: portal_rendering_failure | Bug del portal confirmado.`);
|
|
6571
6825
|
console.error(` >> Evidencia: ${blankEvidencePath}`);
|
|
6826
|
+
await attachEvidenceFile("blank_dashboard_failure", blankEvidencePath, "image/png");
|
|
6572
6827
|
hasCriticalError = true;
|
|
6573
6828
|
failReason = "portal_rendering_failure";
|
|
6574
6829
|
isFinished = true;
|
|
@@ -6785,6 +7040,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
6785
7040
|
await runPostMissionDatabaseValidations();
|
|
6786
7041
|
if (aiMarkedSuccess) {
|
|
6787
7042
|
hasCriticalError = false;
|
|
7043
|
+
await attachFallbackScreenshot("final_success_state", true);
|
|
6788
7044
|
await persistCollectiveMemory();
|
|
6789
7045
|
const finalUsedDirectReuse = reusedPatternId !== null && guideIdx !== 999999;
|
|
6790
7046
|
if (!finalUsedDirectReuse && stepsData.length > 0) {
|
|
@@ -6891,13 +7147,28 @@ Evidence: ${JSON.stringify(v.evidence).substring(0, 400)}`,
|
|
|
6891
7147
|
console.warn(">>ARCALITY_STATUS>> \u{1F6E1}\uFE0F Escaneo de seguridad omitido porque el navegador o la pesta\xF1a colaps\xF3/cerr\xF3.");
|
|
6892
7148
|
}
|
|
6893
7149
|
if (!aiMarkedSuccess && !failReason) {
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
7150
|
+
try {
|
|
7151
|
+
const recentTargets = Array.from(new Set(
|
|
7152
|
+
history.slice(-10).map((h) => (h.match(/(?:click|fill) en "(.+?)"/) || [])[1]).filter(Boolean)
|
|
7153
|
+
));
|
|
7154
|
+
const diagnosis = await diagnoseStall("mixed", recentTargets);
|
|
7155
|
+
if (diagnosis.failReason && diagnosis.failReason !== "loop_detected") {
|
|
7156
|
+
failReason = diagnosis.failReason;
|
|
7157
|
+
failReasoning = diagnosis.failReasoning;
|
|
7158
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F50E} Diagn\xF3stico de cierre: ${failReason}`);
|
|
7159
|
+
}
|
|
7160
|
+
} catch {
|
|
7161
|
+
}
|
|
7162
|
+
if (!failReason) {
|
|
7163
|
+
if (stepCount >= maxSteps)
|
|
7164
|
+
failReason = "max_steps_reached";
|
|
7165
|
+
else
|
|
7166
|
+
failReason = "unknown_failure";
|
|
7167
|
+
}
|
|
6898
7168
|
}
|
|
6899
7169
|
saveMissionResults();
|
|
6900
7170
|
if (!aiMarkedSuccess) {
|
|
7171
|
+
await attachFallbackScreenshot("final_failure_state", true);
|
|
6901
7172
|
if (process.env.DEBUG)
|
|
6902
7173
|
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}`);
|
|
6903
7174
|
bugClassification = await agent.classifyFailure(prompt, failReason || "unknown_failure", failReasoning, history);
|