@arcadialdev/arcality 4.1.4 → 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.
@@ -648,7 +648,14 @@ function simpleHash(str) {
648
648
  }
649
649
  function normalizeBugClassification(value) {
650
650
  const text = String(value || "").trim();
651
- const allowed = ["[Backend Bug]", "[Frontend Bug]", "[UI Regression]", "[Flaky/Stale Selector]"];
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 = ["[Backend Bug]", "[Frontend Bug]", "[UI Regression]", "[Flaky/Stale Selector]"];
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 exactly one category string and no explanation: [Backend Bug], [Frontend Bug], [UI Regression], or [Flaky/Stale Selector].",
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
- (c) => `IDX:${c.idx} | TYPE:${c.type} | NAME: "${c.name}"${c.value ? ` | VAL:"${c.value}"` : ""} `
3836
- ).join("\n");
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 Bucle repetitivo de ${loopType} detectado:`);
6500
+ \u26A0 Estancamiento detectado (${loopType}). Ejecutando diagn\xF3stico de causa ra\xEDz...`);
6269
6501
  console.warn(` \xDAltimas acciones: ${lastActions.join(" \u2192 ")}`);
6270
- response.thought = `\u{1F6D1} ERROR: Bucle repetitivo en ${loopType} sobre: ${Array.from(uniqueTargets).join(", ")}.`;
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 = "loop_detected";
6276
- failReasoning = `Arcality QA entr\xF3 en un bucle l\xF3gico intentando ejecutar la misma acci\xF3n repetidamente sin observar cambios significativos en el DOM.
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
- await loc.fill(step.value, { timeout: 1e4, force: true });
6541
- await loc.press("Tab").catch(() => {
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
- if (stepCount >= maxSteps)
6895
- failReason = "max_steps_reached";
6896
- else
6897
- failReason = "unknown_failure";
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);