@arcadialdev/arcality 2.4.30 → 2.4.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/.agent/skills/form-expert.md +99 -0
  2. package/.agent/skills/investigation-protocol.md +61 -0
  3. package/.agent/skills/modal-master.md +41 -0
  4. package/.agent/skills/native-control-expert.md +82 -0
  5. package/.agents/skills/e2e-testing-expert/SKILL.md +28 -0
  6. package/.agents/skills/frontend-design/LICENSE.txt +177 -0
  7. package/.agents/skills/frontend-design/SKILL.md +42 -0
  8. package/.agents/skills/nodejs-backend-patterns/SKILL.md +639 -0
  9. package/.agents/skills/nodejs-backend-patterns/references/advanced-patterns.md +430 -0
  10. package/.agents/skills/playwright-best-practices/LICENSE.md +7 -0
  11. package/.agents/skills/playwright-best-practices/README.md +147 -0
  12. package/.agents/skills/playwright-best-practices/SKILL.md +303 -0
  13. package/.agents/skills/playwright-best-practices/advanced/authentication-flows.md +360 -0
  14. package/.agents/skills/playwright-best-practices/advanced/authentication.md +871 -0
  15. package/.agents/skills/playwright-best-practices/advanced/clock-mocking.md +364 -0
  16. package/.agents/skills/playwright-best-practices/advanced/mobile-testing.md +409 -0
  17. package/.agents/skills/playwright-best-practices/advanced/multi-context.md +288 -0
  18. package/.agents/skills/playwright-best-practices/advanced/multi-user.md +393 -0
  19. package/.agents/skills/playwright-best-practices/advanced/network-advanced.md +452 -0
  20. package/.agents/skills/playwright-best-practices/advanced/third-party.md +464 -0
  21. package/.agents/skills/playwright-best-practices/architecture/pom-vs-fixtures.md +363 -0
  22. package/.agents/skills/playwright-best-practices/architecture/test-architecture.md +369 -0
  23. package/.agents/skills/playwright-best-practices/architecture/when-to-mock.md +383 -0
  24. package/.agents/skills/playwright-best-practices/browser-apis/browser-apis.md +391 -0
  25. package/.agents/skills/playwright-best-practices/browser-apis/iframes.md +403 -0
  26. package/.agents/skills/playwright-best-practices/browser-apis/service-workers.md +504 -0
  27. package/.agents/skills/playwright-best-practices/browser-apis/websockets.md +403 -0
  28. package/.agents/skills/playwright-best-practices/core/annotations.md +424 -0
  29. package/.agents/skills/playwright-best-practices/core/assertions-waiting.md +361 -0
  30. package/.agents/skills/playwright-best-practices/core/configuration.md +452 -0
  31. package/.agents/skills/playwright-best-practices/core/fixtures-hooks.md +417 -0
  32. package/.agents/skills/playwright-best-practices/core/global-setup.md +434 -0
  33. package/.agents/skills/playwright-best-practices/core/locators.md +242 -0
  34. package/.agents/skills/playwright-best-practices/core/page-object-model.md +315 -0
  35. package/.agents/skills/playwright-best-practices/core/projects-dependencies.md +453 -0
  36. package/.agents/skills/playwright-best-practices/core/test-data.md +492 -0
  37. package/.agents/skills/playwright-best-practices/core/test-suite-structure.md +361 -0
  38. package/.agents/skills/playwright-best-practices/core/test-tags.md +298 -0
  39. package/.agents/skills/playwright-best-practices/debugging/console-errors.md +420 -0
  40. package/.agents/skills/playwright-best-practices/debugging/debugging.md +504 -0
  41. package/.agents/skills/playwright-best-practices/debugging/error-testing.md +360 -0
  42. package/.agents/skills/playwright-best-practices/debugging/flaky-tests.md +496 -0
  43. package/.agents/skills/playwright-best-practices/frameworks/angular.md +530 -0
  44. package/.agents/skills/playwright-best-practices/frameworks/nextjs.md +469 -0
  45. package/.agents/skills/playwright-best-practices/frameworks/react.md +531 -0
  46. package/.agents/skills/playwright-best-practices/frameworks/vue.md +574 -0
  47. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/ci-cd.md +468 -0
  48. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/docker.md +283 -0
  49. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/github-actions.md +546 -0
  50. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/gitlab.md +397 -0
  51. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/other-providers.md +521 -0
  52. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md +371 -0
  53. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/performance.md +453 -0
  54. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/reporting.md +424 -0
  55. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/test-coverage.md +497 -0
  56. package/.agents/skills/playwright-best-practices/testing-patterns/accessibility.md +359 -0
  57. package/.agents/skills/playwright-best-practices/testing-patterns/api-testing.md +719 -0
  58. package/.agents/skills/playwright-best-practices/testing-patterns/browser-extensions.md +506 -0
  59. package/.agents/skills/playwright-best-practices/testing-patterns/canvas-webgl.md +493 -0
  60. package/.agents/skills/playwright-best-practices/testing-patterns/component-testing.md +500 -0
  61. package/.agents/skills/playwright-best-practices/testing-patterns/drag-drop.md +576 -0
  62. package/.agents/skills/playwright-best-practices/testing-patterns/electron.md +509 -0
  63. package/.agents/skills/playwright-best-practices/testing-patterns/file-operations.md +377 -0
  64. package/.agents/skills/playwright-best-practices/testing-patterns/file-upload-download.md +562 -0
  65. package/.agents/skills/playwright-best-practices/testing-patterns/forms-validation.md +561 -0
  66. package/.agents/skills/playwright-best-practices/testing-patterns/graphql-testing.md +331 -0
  67. package/.agents/skills/playwright-best-practices/testing-patterns/i18n.md +508 -0
  68. package/.agents/skills/playwright-best-practices/testing-patterns/performance-testing.md +476 -0
  69. package/.agents/skills/playwright-best-practices/testing-patterns/security-testing.md +430 -0
  70. package/.agents/skills/playwright-best-practices/testing-patterns/visual-regression.md +634 -0
  71. package/.agents/skills/security-qa/SKILL.md +254 -0
  72. package/package.json +3 -1
  73. package/scripts/gen-and-run.mjs +33 -5
  74. package/scripts/init.mjs +2 -2
  75. package/scripts/setup.mjs +2 -2
  76. package/src/arcalityClient.mjs +4 -3
  77. package/tests/_helpers/agentic-runner.bundle.spec.js +161 -50
  78. package/scripts/cleanup-qmsdev.mjs +0 -63
  79. package/scripts/discover-view.mjs +0 -52
  80. package/scripts/extract-view.mjs +0 -64
  81. package/scripts/migrate-to-central-out.mjs +0 -157
@@ -939,7 +939,11 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
939
939
  const role = htmlEl.getAttribute("role") || "";
940
940
  const text = (htmlEl.innerText || htmlEl.getAttribute("aria-label") || htmlEl.getAttribute("title") || "").trim().replace(/\s+/g, " ");
941
941
  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("existe") || textLower.includes("inv\xE1lido") || textLower.includes("incorrecto") || textLower.includes("ligado") || textLower.includes("depende") || textLower.includes("duplicado") || textLower.includes("repetido")) && text.length < 200;
942
+ const isCriticalFailure = (textLower.includes("error") || textLower.includes("fall\xF3") || textLower.includes("no se puede") || textLower.includes("ya existe") || textLower.includes("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
+ 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
+ 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") || textLower.includes("al menos") || textLower.includes("como m\xE1ximo") || /* Generic ZOD/form required */
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("recuerda llenar") || textLower.includes("completa los campos") || textLower.includes("campos obligatorios") || textLower.includes("formato incorrecto") || textLower.includes("formato inv\xE1lido") || textLower.includes("valor no permitido")) && text.length < 250;
943
947
  const isCriticalSuccess = (textLower.includes("exitosamente") || textLower.includes("guardado") || textLower.includes("creado") || textLower.includes("success") || textLower.includes("correctamente") && text.length < 100) && text.length < 200;
944
948
  const isCriticalFeedback = isCriticalFailure || isCriticalSuccess;
945
949
  if (!isCriticalFeedback && (tag === "span" || tag === "p" || tag === "label" || tag === "i" || tag === "svg" || tag === "img" || tag === "div")) {
@@ -1445,6 +1449,13 @@ These are NOT optional. You MUST use these tools in these situations:
1445
1449
  4. **PAGE TRANSITIONS**: If URL contains '/edit' or you see a 'Save' button, you ARE in the edit view. Don't go back.
1446
1450
  5. **MENUS & DROPDOWNS**: After clicking a toggle/menu, WAIT for the next turn. Never click the same menu icon twice in a row.
1447
1451
  6. **ERROR HANDLING**: If you see '\u{1F6D1} [ERROR_CR\xCDTICO]', STOP everything. Analyze the error, fix the data, then retry.
1452
+ 6b. **DUPLICATE / REPEATED VALUE ERROR \u2014 MANDATORY PROTOCOL**:
1453
+ - If HISTORY contains a line with "ERROR VISIBLE EN PANTALLA" or "ERROR POST-GUARDADO" with words like "repetido", "duplicado", "ya existe", or "already exists":
1454
+ 1. You MUST identify WHICH field caused the duplication (usually the main name/title field).
1455
+ 2. You MUST use \`fill\` to REPLACE that field's value with a NEW unique value. Add a numeric suffix like \`_${(/* @__PURE__ */ new Date()).getMinutes()}${(/* @__PURE__ */ new Date()).getSeconds()}\` or a short random number.
1456
+ 3. ONLY AFTER changing the value, attempt to save again.
1457
+ 4. **NEVER attempt to save with the same value that was already rejected.** This is FORBIDDEN.
1458
+ 5. If you cannot determine which field is the duplicate, use \`capture_console_errors\` to get hints from the application.
1448
1459
  7. **DATA UNIQUENESS**: When generating test data, ALWAYS include a unique suffix (timestamp, counter). Never use "Test123" or "Prueba" alone.
1449
1460
  8. **FINISH CORRECTLY**: Use \`finish: true\` ONLY when the mission objective is FULLY achieved.
1450
1461
  - **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.
@@ -1453,8 +1464,34 @@ These are NOT optional. You MUST use these tools in these situations:
1453
1464
  10. **STUCK DETECTION**: If history shows 3+ similar actions without progress, use \`report_inability_to_proceed\`.
1454
1465
  11. **NEGATIVE TESTING**: If mission expects an error (e.g., "verifica que no se pueda guardar sin campos"), and you see that error, the mission is SUCCESS. Use \`finish: true\`.
1455
1466
  12. **GUIDE CONFLICT**: If SUCCESS MEMORY disagrees with what you see on screen, ABANDON the memory and trust your eyes.
1456
- 13. **NATIVE CONTROLS**: For \`<input type="time">\`, \`<input type="date">\`, or similar native pickers, NEVER use \`fill\` repeatedly. Use \`interact_native_control\` or \`send_keyboard_event\` instead. If \`fill\` fails once, switch strategy immediately.`,
1457
- cache_control: { type: "ephemeral" }
1467
+ 13. **NATIVE CONTROLS**: For \`<input type="time">\`, \`<input type="date">\`, or similar native pickers, NEVER use \`fill\` repeatedly. Use \`interact_native_control\` or \`send_keyboard_event\` instead. If \`fill\` fails once, switch strategy immediately.
1468
+
1469
+ # WIZARD & MULTI-STEP FORMS (CRITICAL)
1470
+ When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 Step 3):
1471
+
1472
+ 1. **VALIDATION ERROR ON SAVE = STEP HAS INVALID DATA**: If you see a \u{1F6D1} [ERROR_CR\xCDTICO] after clicking Save/Next and you are on step N, it means a PREVIOUS step contains bad data. You MUST navigate BACK to that step.
1473
+ - Look for a "Back" / "Anterior" / "Periodo" / step-number button and click it to return.
1474
+ - NEVER retry the Save button without first fixing the invalid data in the previous step.
1475
+
1476
+ 2. **DATE VALIDATION ERRORS \u2014 MANDATORY PROTOCOL (ZOD-AWARE)**:
1477
+ - ZOD generates Spanish validation messages such as:
1478
+ - "La fecha de inicio debe ser posterior o igual a la fecha actual."
1479
+ - "La fecha debe ser una fecha futura."
1480
+ - "Debe ser posterior a la fecha de inicio."
1481
+ - "El valor debe ser mayor o igual a X."
1482
+ - ANY \u{1F6D1} [ERROR_CR\xCDTICO] containing FECHA + (POSTERIOR/ANTERIOR/ACTUAL/PASADA/FUTURA) is a **ZOD DATE VALIDATION ERROR**.
1483
+ - **RECOVERY PROTOCOL** (follow in order, NEVER skip):
1484
+ 1. STOP \u2014 do NOT click Save/Siguiente again with the same data.
1485
+ 2. Identify which wizard step contains the failing date field (check HISTORY \u2014 usually Step 1 or Step 2).
1486
+ 3. Click the Back/Anterior/step-number button to return to that step.
1487
+ 4. Locate the date input using \`validate_element_state\` with 'has_value' to confirm its current (bad) value.
1488
+ 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.
1489
+ 6. TODAY: \`${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}\` \u2014 use this exact expression to compute the date dynamically.
1490
+ 7. After fixing ALL date fields, advance through each wizard step again.
1491
+
1492
+ 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.
1493
+
1494
+ 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.`
1458
1495
  }
1459
1496
  ];
1460
1497
  if (this.testInfo) {
@@ -1473,8 +1510,7 @@ These are NOT optional. You MUST use these tools in these situations:
1473
1510
  type: "base64",
1474
1511
  media_type: "image/png",
1475
1512
  data: base64Img
1476
- },
1477
- cache_control: { type: "ephemeral" }
1513
+ }
1478
1514
  },
1479
1515
  {
1480
1516
  type: "text",
@@ -1499,14 +1535,14 @@ ${history.slice(-25).join("\n") || "None"}`
1499
1535
  },
1500
1536
  {
1501
1537
  type: "text",
1502
- text: `Please analyze the current state and provide the next step.`,
1503
- cache_control: { type: "ephemeral" }
1538
+ text: `Please analyze the current state and provide the next step.`
1504
1539
  }
1505
1540
  ]
1506
1541
  }
1507
1542
  ];
1508
1543
  console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Arcality est\xE1 procesando la visi\xF3n...`);
1509
1544
  const startTime = Date.now();
1545
+ let totalUsage = { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 };
1510
1546
  for (let turn = 0; turn < 10; turn++) {
1511
1547
  let response;
1512
1548
  let retryCount = 0;
@@ -1536,7 +1572,7 @@ ${history.slice(-25).join("\n") || "None"}`
1536
1572
  headers,
1537
1573
  signal: controller.signal,
1538
1574
  body: JSON.stringify({
1539
- model: process.env.CLAUDE_MODEL || "claude-3-5-sonnet-20241022",
1575
+ model: process.env.CLAUDE_MODEL || "claude-4-5-sonnet-latest",
1540
1576
  max_tokens: 4096,
1541
1577
  system: systemPromptBlocks,
1542
1578
  tools: rawTools.map((t) => ({
@@ -1573,6 +1609,12 @@ ${history.slice(-25).join("\n") || "None"}`
1573
1609
  }
1574
1610
  const data = await response.json();
1575
1611
  const message = data;
1612
+ if (data.usage) {
1613
+ totalUsage.input_tokens += data.usage.input_tokens || 0;
1614
+ totalUsage.output_tokens += data.usage.output_tokens || 0;
1615
+ totalUsage.cache_creation_input_tokens += data.usage.cache_creation_input_tokens || 0;
1616
+ totalUsage.cache_read_input_tokens += data.usage.cache_read_input_tokens || 0;
1617
+ }
1576
1618
  anthropicMessages.push({
1577
1619
  role: "assistant",
1578
1620
  content: message.content
@@ -1581,7 +1623,7 @@ ${history.slice(-25).join("\n") || "None"}`
1581
1623
  const textResponse = message.content.find((c) => c.type === "text")?.text || "";
1582
1624
  if (toolCalls.length === 0) {
1583
1625
  console.log(`(${((Date.now() - startTime) / 1e3).toFixed(1)}s)`);
1584
- return { thought: textResponse || "No action decided", actions: [] };
1626
+ return { thought: textResponse || "No action decided", actions: [], usage: totalUsage };
1585
1627
  }
1586
1628
  const toolResults = [];
1587
1629
  let uiActionCall = null;
@@ -1640,7 +1682,8 @@ ${history.slice(-25).join("\n") || "None"}`
1640
1682
 
1641
1683
  \u{1F4A1} SUGERENCIA: ${toolInput.prompt_suggestion}`,
1642
1684
  actions: [],
1643
- finish: true
1685
+ finish: true,
1686
+ usage: totalUsage
1644
1687
  };
1645
1688
  } else if (toolName === "validate_element_state") {
1646
1689
  const { idx, validations } = toolInput;
@@ -2147,7 +2190,8 @@ ${report}` });
2147
2190
  }
2148
2191
  return { ...a, value: finalValue, selector: c?.selector, frameIdx: c?.frameIdx, description: c?.name, type: c?.type };
2149
2192
  }),
2150
- finish: input.finish
2193
+ finish: input.finish,
2194
+ usage: totalUsage
2151
2195
  };
2152
2196
  }
2153
2197
  }
@@ -2178,7 +2222,7 @@ ${report}` });
2178
2222
  method: "POST",
2179
2223
  headers,
2180
2224
  body: JSON.stringify({
2181
- model: process.env.CLAUDE_MODEL || "claude-3-5-sonnet-20241022",
2225
+ model: process.env.CLAUDE_MODEL || "claude-4-5-sonnet-latest",
2182
2226
  max_tokens: 300,
2183
2227
  system: "Eres un Senior QA personalizador de reportes. Tu tarea es resumir en una sola frase corta, profesional y amigable por qu\xE9 una misi\xF3n de testing fue exitosa bas\xE1ndote en el historial de pasos. Usa un tono de victoria. No menciones detalles t\xE9cnicos como 'selectores' o 'DOM'. El resumen debe empezar con algo como '\xA1Misi\xF3n cumplida! ...'",
2184
2228
  messages: [
@@ -2226,7 +2270,7 @@ ${history.join("\n")}` }
2226
2270
  method: "POST",
2227
2271
  headers,
2228
2272
  body: JSON.stringify({
2229
- model: process.env.CLAUDE_MODEL || "claude-3-5-sonnet-20241022",
2273
+ model: process.env.CLAUDE_MODEL || "claude-4-5-sonnet-latest",
2230
2274
  max_tokens: 1e3,
2231
2275
  system: `You are an elite QA Engineer. Your task is to transform a raw execution history into a clean, human-friendly English YAML Mission Card.
2232
2276
 
@@ -2409,6 +2453,8 @@ function captureValidationRule(errorMessage, context) {
2409
2453
  let stepsDataBackup = [];
2410
2454
  const maxSteps = 30;
2411
2455
  let lastSeenSuccessToast = "";
2456
+ let accumulatedCost = 0;
2457
+ let totalMissionUsage = { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 };
2412
2458
  const saveMissionResults = () => {
2413
2459
  if (resultsSaved)
2414
2460
  return;
@@ -2446,57 +2492,76 @@ function captureValidationRule(errorMessage, context) {
2446
2492
  }
2447
2493
  try {
2448
2494
  const logPath = path2.join(contextDir, `agent-log-${Date.now()}.json`);
2449
- fs2.writeFileSync(logPath, JSON.stringify({ prompt, history, steps: stepCount, success: finalSuccess, error: hasCriticalError }, null, 2));
2495
+ fs2.writeFileSync(logPath, JSON.stringify({ prompt, history, steps: stepCount, success: finalSuccess, error: hasCriticalError, usage: totalMissionUsage }, null, 2));
2450
2496
  } catch (err) {
2451
2497
  }
2452
2498
  };
2453
2499
  let collectiveMemoryPersisted = false;
2454
- const persistCollectiveMemory = async () => {
2455
- if (collectiveMemoryPersisted)
2456
- return;
2457
- collectiveMemoryPersisted = true;
2500
+ const persistCollectiveMemory = async (isFailed = false) => {
2458
2501
  const pid = process.env.ARCALITY_PROJECT_ID || "";
2459
2502
  const apiUrl = process.env.ARCALITY_API_URL || "";
2460
2503
  const apiKey = process.env.ARCALITY_API_KEY || "";
2461
2504
  if (!pid || !apiUrl || !apiKey) {
2462
- console.warn(`[CollectiveMemory] \u26A0\uFE0F Vars faltantes \u2014 project_id='${pid}' api_url='${apiUrl}' api_key='${apiKey ? "OK" : "MISSING"}'`);
2505
+ console.warn(`[CollectiveMemory] \u26A0\uFE0F Vars faltantes \u2014 project_id='${pid}' api_url='${apiUrl}' api_key='${apiKey ? "OK" : "MISSING"}' \u2192 NO persiste.`);
2506
+ return;
2507
+ }
2508
+ if (collectiveMemoryPersisted && !isFailed) {
2509
+ console.log(`[CollectiveMemory] \u2139\uFE0F Ya persistido como \xE9xito. Skip duplicado.`);
2463
2510
  return;
2464
2511
  }
2465
- console.log(`\u{1F9E0} [COLLECTIVE MEMORY] Persistiendo conocimiento (project: ${pid})...`);
2512
+ if (!isFailed && stepsData.length === 0 && stepsDataBackup.length === 0) {
2513
+ console.warn(`[CollectiveMemory] \u26A0\uFE0F stepsData vac\xEDo en path de \xE9xito \u2014 intentando usar backup...`);
2514
+ }
2515
+ const effectiveSteps = stepsData.length > 0 ? stepsData : stepsDataBackup;
2516
+ console.log(`\u{1F9E0} [COLLECTIVE MEMORY] Persistiendo conocimiento (project: ${pid}, steps: ${effectiveSteps.length}, isFailed: ${isFailed})...`);
2466
2517
  try {
2467
- const stepsNarrative = stepsData.map((s) => `${s.action_data?.action || "action"} "${s.componentName}" en ${s.url}`).join(" \u2192 ").substring(0, 500);
2468
- const knowledgeId = await pushKnowledge({
2469
- doc_type: "PROCESS",
2470
- title: `Flujo exitoso: ${prompt.substring(0, 100)}`,
2471
- content: stepsNarrative || prompt.substring(0, 500)
2472
- });
2473
- if (knowledgeId)
2474
- console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Knowledge guardado: ${knowledgeId}`);
2475
- else
2476
- console.warn(`[CollectiveMemory] \u26A0\uFE0F pushKnowledge retorn\xF3 null (revisar HTTP status arriba)`);
2477
- const uniqueUrls = [...new Set(stepsData.map((s) => s.url).filter(Boolean))];
2478
- if (uniqueUrls.length > 1) {
2479
- const ruleId = await pushRule({
2480
- rule_type: "NAVIGATION",
2481
- title: `Flujo de navegaci\xF3n: ${prompt.substring(0, 80)}`,
2482
- description: `Agente naveg\xF3 ${uniqueUrls.length} p\xE1ginas: ${uniqueUrls.join(" \u2192 ").substring(0, 400)}`,
2483
- severity: "suggestion"
2518
+ const stepsNarrative = effectiveSteps.map((s) => `${s.action_data?.action || "action"} "${s.componentName}" en ${s.url}`).join(" \u2192 ").substring(0, 500);
2519
+ if (!isFailed) {
2520
+ collectiveMemoryPersisted = true;
2521
+ const knowledgeId = await pushKnowledge({
2522
+ doc_type: "PROCESS",
2523
+ title: `Flujo exitoso: ${prompt.substring(0, 100)}`,
2524
+ content: stepsNarrative || prompt.substring(0, 500)
2484
2525
  });
2485
- if (ruleId)
2486
- console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Regla NAVIGATION guardada: ${ruleId}`);
2487
- }
2488
- const submitStep = stepsData.find(
2489
- (s) => s.action_data?.action === "click" && (s.componentName?.toLowerCase().includes("guardar") || s.componentName?.toLowerCase().includes("save") || s.componentName?.toLowerCase().includes("crear") || s.componentName?.toLowerCase().includes("registrar"))
2490
- );
2491
- if (submitStep) {
2492
- const ruleId2 = await pushRule({
2493
- rule_type: "UI_UX",
2494
- title: `Patr\xF3n de guardado: ${prompt.substring(0, 60)}`,
2495
- description: `Flujo exitoso incluy\xF3 guardado/creaci\xF3n. Pasos: ${stepsNarrative.substring(0, 400)}`,
2526
+ if (knowledgeId)
2527
+ console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Knowledge guardado: ${knowledgeId}`);
2528
+ else
2529
+ console.warn(`[CollectiveMemory] \u26A0\uFE0F pushKnowledge retorn\xF3 null (revisar HTTP status arriba)`);
2530
+ const uniqueUrls = [...new Set(effectiveSteps.map((s) => s.url).filter(Boolean))];
2531
+ if (uniqueUrls.length > 1) {
2532
+ const ruleId = await pushRule({
2533
+ rule_type: "NAVIGATION",
2534
+ title: `Flujo de navegaci\xF3n: ${prompt.substring(0, 80)}`,
2535
+ description: `Agente naveg\xF3 ${uniqueUrls.length} p\xE1ginas: ${uniqueUrls.join(" \u2192 ").substring(0, 400)}`,
2536
+ severity: "suggestion"
2537
+ });
2538
+ if (ruleId)
2539
+ console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Regla NAVIGATION guardada: ${ruleId}`);
2540
+ }
2541
+ const submitStep = effectiveSteps.find(
2542
+ (s) => s.action_data?.action === "click" && (s.componentName?.toLowerCase().includes("guardar") || s.componentName?.toLowerCase().includes("save") || s.componentName?.toLowerCase().includes("crear") || s.componentName?.toLowerCase().includes("registrar"))
2543
+ );
2544
+ if (submitStep) {
2545
+ const ruleId2 = await pushRule({
2546
+ rule_type: "UI_UX",
2547
+ title: `Patr\xF3n de guardado: ${prompt.substring(0, 60)}`,
2548
+ description: `Flujo exitoso incluy\xF3 guardado/creaci\xF3n. Pasos: ${stepsNarrative.substring(0, 400)}`,
2549
+ severity: "important"
2550
+ });
2551
+ if (ruleId2)
2552
+ console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Regla UI_UX guardada: ${ruleId2}`);
2553
+ }
2554
+ } else {
2555
+ const failRuleId = await pushRule({
2556
+ rule_type: "VALIDATION",
2557
+ title: `Misi\xF3n fallida: ${prompt.substring(0, 80)}`,
2558
+ description: `El agente no pudo completar: "${prompt.substring(0, 200)}". \xDAltimos pasos: ${stepsNarrative.substring(0, 300)}`,
2496
2559
  severity: "important"
2497
2560
  });
2498
- if (ruleId2)
2499
- console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Regla UI_UX guardada: ${ruleId2}`);
2561
+ if (failRuleId)
2562
+ console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Regla de fallo guardada: ${failRuleId}`);
2563
+ else
2564
+ console.warn(`[CollectiveMemory] \u26A0\uFE0F No se pudo persistir misi\xF3n fallida`);
2500
2565
  }
2501
2566
  } catch (err) {
2502
2567
  console.warn(`[CollectiveMemory] \u274C Error inesperado en persistCollectiveMemory: ${err?.message}`);
@@ -2612,6 +2677,22 @@ function captureValidationRule(errorMessage, context) {
2612
2677
  stepsDataBackup = [...stepsData];
2613
2678
  stepsData.length = 0;
2614
2679
  }
2680
+ try {
2681
+ const errorEls = await page.locator('.toast, .alert, [role="alert"], [role="status"], .snackbar, .notification, .error-message, .mat-snack-bar-container').all();
2682
+ for (const el of errorEls) {
2683
+ if (await el.isVisible().catch(() => false)) {
2684
+ const errTxt = await el.innerText().catch(() => "");
2685
+ if (errTxt && failureKeywords.test(errTxt.toLowerCase())) {
2686
+ const isDuplicateError = /repetido|duplicado|ya existe|already exists/i.test(errTxt);
2687
+ const correctionHint = isDuplicateError ? `\u{1F6D1} ACCI\xD3N REQUERIDA: El valor que ingresaste ya existe en el sistema. DEBES regresar al campo que caus\xF3 el error y cambiar su valor por uno \xDANICO (agrega un sufijo como _${Date.now().toString().slice(-4)} o un n\xFAmero aleatorio). NO intentes guardar sin cambiar el valor primero.` : `\u{1F6D1} ACCI\xD3N REQUERIDA: El sistema mostr\xF3 un error de validaci\xF3n. Analiza el error y corrige el dato antes de reintentar.`;
2688
+ history.push(`Turno ${stepCount}: \u{1F6D1} ERROR VISIBLE EN PANTALLA: "${errTxt.trim().substring(0, 200)}". ${correctionHint}`);
2689
+ console.log(`>>ARCALITY_STATUS>> \u{1F6D1} Error inyectado en historial: "${errTxt.substring(0, 80)}"`);
2690
+ break;
2691
+ }
2692
+ }
2693
+ }
2694
+ } catch {
2695
+ }
2615
2696
  }
2616
2697
  if (response) {
2617
2698
  const guideActionDesc = response.actions?.[0] ? `${response.actions[0].action} en "${response.actions[0].description || ""}"`.toLowerCase() : "";
@@ -2629,6 +2710,28 @@ function captureValidationRule(errorMessage, context) {
2629
2710
  stepCount++;
2630
2711
  console.log(`>>ARCALITY_STATUS>> \u23F3 Turno IA ${stepCount} de ${maxSteps} (Gu\xEDa us\xF3 ${guideStepCount} turnos gratis)...`);
2631
2712
  response = await agent.askIA(prompt, history, stepCount);
2713
+ if (response.usage) {
2714
+ const inputs = response.usage.input_tokens || 0;
2715
+ const cacheCreates = response.usage.cache_creation_input_tokens || 0;
2716
+ const cacheReads = response.usage.cache_read_input_tokens || 0;
2717
+ const outputs = response.usage.output_tokens || 0;
2718
+ totalMissionUsage.input_tokens += inputs;
2719
+ totalMissionUsage.output_tokens += outputs;
2720
+ totalMissionUsage.cache_creation_input_tokens += cacheCreates;
2721
+ totalMissionUsage.cache_read_input_tokens += cacheReads;
2722
+ const totalInputs = inputs + cacheCreates;
2723
+ const cost = totalInputs / 1e6 * 3 + cacheReads / 1e6 * 0.3 + outputs / 1e6 * 15;
2724
+ accumulatedCost += cost;
2725
+ if (accumulatedCost >= 6) {
2726
+ console.warn(`
2727
+ \u{1F6D1} [AUTO-STOP] Capacidad m\xE1xima de exploraci\xF3n t\xE9cnica alcanzada para esta prueba.`);
2728
+ response.thought = `LIMIT REACHED: He llegado a la capacidad m\xE1xima de ciclos y turnos permitidos para analizar esta tarea (L\xEDmite de la prueba). Detendr\xE9 esta misi\xF3n para evitar sobrecarga del sistema.`;
2729
+ response.finish = true;
2730
+ response.actions = [];
2731
+ aiMarkedSuccess = false;
2732
+ hasCriticalError = true;
2733
+ }
2734
+ }
2632
2735
  }
2633
2736
  console.log(`\u{1F9E0} Pensamiento: ${response.thought}`);
2634
2737
  if (!response.finish && history.length >= 6) {
@@ -2771,6 +2874,9 @@ function captureValidationRule(errorMessage, context) {
2771
2874
  } else if (failureKeywords.test(fbText.toLowerCase())) {
2772
2875
  console.log(`>>ARCALITY_STATUS>> \u{1F4DA} [BUSINESS RULE] Error de validaci\xF3n capturado: "${fbText.substring(0, 80)}"`);
2773
2876
  captureValidationRule(fbText.trim(), `Acci\xF3n: click en "${desc}" en ${page.url()}`);
2877
+ const isDuplicateError = /repetido|duplicado|ya existe|already exists/i.test(fbText);
2878
+ 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.`;
2879
+ history.push(`Turno ${stepCount}: \u{1F6D1} ERROR POST-GUARDADO: "${fbText.trim().substring(0, 200)}". ${correctionHint}`);
2774
2880
  }
2775
2881
  }
2776
2882
  }
@@ -2998,6 +3104,11 @@ Evidence: ${JSON.stringify(v.evidence).substring(0, 400)}`,
2998
3104
  }
2999
3105
  saveMissionResults();
3000
3106
  if (!aiMarkedSuccess) {
3107
+ 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}`);
3108
+ await persistCollectiveMemory(true).catch(() => {
3109
+ });
3001
3110
  throw new Error("Misi\xF3n no completada o finalizada con errores.");
3111
+ } else {
3112
+ 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}`);
3002
3113
  }
3003
3114
  });
@@ -1,63 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
-
4
- const baseDir = 'c:/Users/apulido/OneDrive - Block Networks/Documentos/Arcadial Projects/Char.Dev/out/qmsDev';
5
- const nestedDir = path.join(baseDir, 'context', 'qmsDev');
6
- const targetContextDir = path.join(baseDir, 'context');
7
- const targetMissionsDir = path.join(baseDir, 'missions');
8
-
9
- function cleanup() {
10
- if (!fs.existsSync(nestedDir)) {
11
- console.log('Nested directory not found, skipping.');
12
- return;
13
- }
14
-
15
- const files = fs.readdirSync(nestedDir);
16
-
17
- files.forEach(file => {
18
- const filePath = path.join(nestedDir, file);
19
- if (file.startsWith('agent-log-')) {
20
- console.log(`Moving ${file} to context/`);
21
- fs.renameSync(filePath, path.join(targetContextDir, file));
22
- } else if (file === 'memoria-agente.json') {
23
- const targetMemory = path.join(targetContextDir, 'memoria-agente.json');
24
- if (fs.existsSync(targetMemory)) {
25
- console.log('Merging memoria-agente.json...');
26
- const mainMemory = JSON.parse(fs.readFileSync(targetMemory, 'utf8'));
27
- const nestedMemory = JSON.parse(fs.readFileSync(filePath, 'utf8'));
28
- const combined = [...mainMemory, ...nestedMemory];
29
- fs.writeFileSync(targetMemory, JSON.stringify(combined, null, 2));
30
- fs.unlinkSync(filePath);
31
- } else {
32
- console.log('Moving memoria-agente.json...');
33
- fs.renameSync(filePath, targetMemory);
34
- }
35
- } else if (file === 'saved_missions') {
36
- const missions = fs.readdirSync(filePath);
37
- missions.forEach(m => {
38
- if (m.endsWith('.yaml') || m.endsWith('.yml')) {
39
- console.log(`Moving mission ${m} to missions/`);
40
- fs.renameSync(path.join(filePath, m), path.join(targetMissionsDir, m));
41
- }
42
- });
43
- fs.rmdirSync(filePath, { recursive: true });
44
- } else if (fs.statSync(filePath).isFile()) {
45
- console.log(`Moving other file ${file} to context/`);
46
- fs.renameSync(filePath, path.join(targetContextDir, file));
47
- }
48
- });
49
-
50
- // Try to remove the empty nested dir
51
- try {
52
- if (fs.readdirSync(nestedDir).length === 0) {
53
- fs.rmdirSync(nestedDir);
54
- console.log('Removed empty nested directory.');
55
- } else {
56
- console.warn('Nested directory not empty, keeping it.');
57
- }
58
- } catch (e) {
59
- console.error('Error removing nested dir:', e.message);
60
- }
61
- }
62
-
63
- cleanup();
@@ -1,52 +0,0 @@
1
- #!/usr/bin/env node
2
- import 'dotenv/config';
3
- import fs from 'node:fs';
4
- import { spawn } from 'node:child_process';
5
- import path from 'node:path';
6
-
7
- const targetPath = process.argv[2] || '/';
8
- const isWin = process.platform === 'win32';
9
- const cmd = isWin ? 'npx.cmd' : 'npx';
10
- const testFile = 'tests/_helpers/discover-view.spec.ts';
11
-
12
- function parseDotEnvFile(p) {
13
- try {
14
- const raw = fs.readFileSync(p, 'utf8');
15
- const lines = raw.split(/\r?\n/);
16
- const out = {};
17
- for (const line of lines) {
18
- const trimmed = line.trim();
19
- if (!trimmed || trimmed.startsWith('#')) continue;
20
- const idx = trimmed.indexOf('=');
21
- if (idx === -1) continue;
22
- let k = trimmed.slice(0, idx).trim();
23
- let v = trimmed.slice(idx + 1).trim();
24
- if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
25
- out[k] = v;
26
- }
27
- return out;
28
- } catch {
29
- return {};
30
- }
31
- }
32
-
33
- const fileEnv = parseDotEnvFile(path.join(process.cwd(), '.env'));
34
- const env = { ...process.env, ...fileEnv, TARGET_PATH: targetPath };
35
-
36
- const args = ['playwright', 'test', testFile, '--project=chromium'];
37
- // Debug printing removed to avoid leaking placeholders; command is spawned directly
38
-
39
- const child = spawn(cmd, args, {
40
- stdio: 'inherit',
41
- shell: isWin,
42
- env,
43
- });
44
-
45
- child.on('exit', (code) => {
46
- process.exit(code ?? 0);
47
- });
48
-
49
- child.on('error', (err) => {
50
- console.error('Failed to run playwright:', err);
51
- process.exit(1);
52
- });
@@ -1,64 +0,0 @@
1
- #!/usr/bin/env node
2
- import 'dotenv/config';
3
- import fs from 'node:fs';
4
- import { spawn } from 'node:child_process';
5
- import path from 'node:path';
6
-
7
- const targetPath = process.argv[2] || '/';
8
- const isWin = process.platform === 'win32';
9
- const cmd = isWin ? 'npx.cmd' : 'npx';
10
-
11
- // Use POSIX-style forward slashes so Playwright reliably matches the file on all platforms
12
- const testFile = 'tests/_helpers/extract-view.spec.ts';
13
-
14
- function parseDotEnvFile(p) {
15
- try {
16
- const raw = fs.readFileSync(p, 'utf8');
17
- const lines = raw.split(/\r?\n/);
18
- const out = {};
19
- for (const line of lines) {
20
- const trimmed = line.trim();
21
- if (!trimmed || trimmed.startsWith('#')) continue;
22
- const idx = trimmed.indexOf('=');
23
- if (idx === -1) continue;
24
- let k = trimmed.slice(0, idx).trim();
25
- let v = trimmed.slice(idx + 1).trim();
26
- if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
27
- out[k] = v;
28
- }
29
- return out;
30
- } catch {
31
- return {};
32
- }
33
- }
34
-
35
- const fileEnv = parseDotEnvFile(path.join(process.cwd(), '.env'));
36
- const env = { ...process.env, ...fileEnv, TARGET_PATH: targetPath };
37
-
38
- const args = ['playwright', 'test', testFile, '--project=chromium', '--headed'];
39
-
40
- // Debug printing removed to avoid leaking placeholders; command is spawned directly
41
-
42
- const child = spawn(cmd, args, {
43
- stdio: 'inherit',
44
- shell: isWin,
45
- env,
46
- });
47
-
48
- child.on('exit', (code) => {
49
- // DESACTIVADO POR PETICIÓN DE USUARIO: Evitar pantallas extras/consolas
50
- /*
51
- try {
52
- const showCmd = isWin ? 'npx.cmd' : 'npx';
53
- spawn(showCmd, ['playwright', 'show-report'], { stdio: 'ignore', shell: isWin, detached: true });
54
- } catch (e) {
55
- // ignore
56
- }
57
- */
58
- process.exit(code ?? 0);
59
- });
60
-
61
- child.on('error', (err) => {
62
- console.error('Failed to run playwright:', err);
63
- process.exit(1);
64
- });