@arcadialdev/arcality 2.4.31 → 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.
@@ -0,0 +1,99 @@
1
+ ---
2
+ name: form-expert
3
+ description: Metodología experta para explorar, identificar y llenar formularios complejos en aplicaciones web.
4
+ ---
5
+
6
+ # Form Expert Skill
7
+
8
+ Esta habilidad define el protocolo que el agente DEBE seguir cuando la misión involucra llenar, crear o editar registros a través de formularios.
9
+
10
+ ## Principio Fundamental
11
+ **NUNCA interactúes con un campo que no hayas inspeccionado primero.** Si un componente tiene un nombre genérico, un tag inferred como `[INFERRED]`, o simplemente no estás seguro de qué es, usa `inspect_element_details` ANTES de hacer click o fill.
12
+
13
+ ## Protocolo de Formularios (4 fases obligatorias)
14
+
15
+ ### Fase 1: RECONOCIMIENTO (Scan)
16
+ Antes de llenar cualquier campo, dedica tu primer turno en el formulario a **analizar todos los componentes visibles**:
17
+
18
+ 1. **Lee la lista completa de CURRENT COMPONENTS** — identifica:
19
+ - Campos de tipo FIELD (inputs, textareas, selects)
20
+ - Campos con label visible (prioritarios)
21
+ - Campos sin label o con nombre genérico (requieren inspección)
22
+ - Botones de acción (GUARDAR, CANCELAR, etc.)
23
+ - Tabs o pestañas si el formulario tiene secciones
24
+
25
+ 2. **Clasifica los campos** en:
26
+ - ✅ **Confiables**: Tienen un nombre claro como `[input] Título del documento`
27
+ - ⚠️ **Sospechosos**: Tienen nombre genérico como `[input] Editar` o `[INFERRED] xxx`
28
+ - ❌ **Desconocidos**: Solo tienen tag genérico como `[input]`
29
+
30
+ 3. **Para campos sospechosos o desconocidos**: Usa `inspect_element_details` para obtener:
31
+ - El placeholder text
32
+ - El aria-label
33
+ - El atributo name o id
34
+ - Si está disabled o readonly
35
+
36
+ ### Fase 2: PLANIFICACIÓN (Plan)
37
+ Con la información del reconocimiento, planifica la secuencia de acciones:
38
+
39
+ 1. **Prioriza los campos obligatorios** — los que tienen asterisco (*), clase "required", o están marcados en rojo.
40
+ 2. **Genera datos únicos** — Si la misión dice "genérico" o "aleatorio", genera datos que incluyan un timestamp o ID único para evitar duplicados. Ejemplo: `TEST-${timestamp}` o `Prueba QA Feb24`.
41
+ 3. **Planifica el orden**: Llena los campos de arriba a abajo, izquierda a derecha, siguiendo el orden del DOM.
42
+
43
+ ### Fase 3: EJECUCIÓN (Act)
44
+ Ejecuta las acciones planificadas:
45
+
46
+ 1. **Llena múltiples campos por turno** — No pierdas un turno por campo. Agrupa 3-5 fills en una sola llamada a `perform_ui_actions`.
47
+ 2. **Para cada campo fill**: click + fill (en ese orden). El click asegura que el campo tiene focus.
48
+ 3. **Para dropdowns/selects**: Click para abrir → espera el siguiente turno → selecciona la opción.
49
+ 4. **Si un fill falla**: NO reintentes inmediatamente. Usa `inspect_element_details` para ver si el campo es un select, un datepicker, o tiene un overlay encima.
50
+
51
+ ### Fase 4: VERIFICACIÓN (Verify)
52
+ Antes de hacer GUARDAR:
53
+
54
+ 1. **Usa `validate_element_state`** en el botón de GUARDAR para confirmar que está enabled.
55
+ 2. **Revisa visualmente** si hay campos en rojo o mensajes de error.
56
+ 3. **Solo entonces** haz click en GUARDAR.
57
+ 4. **Después de GUARDAR**: Espera la respuesta de la UI. Si aparece un toast de éxito o regresas a la lista, la misión fue exitosa.
58
+
59
+ ## Manejo de Errores en Formularios
60
+
61
+ ### Si un campo falla con Timeout:
62
+ ```
63
+ 1. NO reintentes el mismo click/fill inmediatamente
64
+ 2. Usa inspect_element_details en ese IDX
65
+ 3. Analiza si:
66
+ - El elemento está cubierto por un overlay/modal
67
+ - El elemento es readonly/disabled
68
+ - El IDX cambió (la página se re-renderizó)
69
+ 4. Si el elemento no sirve, busca un campo alternativo con nombre similar
70
+ ```
71
+
72
+ ### Si aparece un error de "duplicado" o "ya existe":
73
+ ```
74
+ 1. Identifica qué campo causó el error (claves, códigos, nombres)
75
+ 2. Cambia el valor a algo único (agrega sufijo numérico o timestamp)
76
+ 3. Reintenta GUARDAR
77
+ ```
78
+
79
+ ### Si el formulario tiene tabs o secciones:
80
+ ```
81
+ 1. Llena los campos de la sección actual PRIMERO.
82
+ 2. Identifica el elemento de la pestaña: Busca el texto de la pestaña (ej: "CAMPOS DE LA FORMA") en componentes tipo ACTION.
83
+ 3. PRECISIÓN: Evita hacer click en contenedores grandes que agrupan varias pestañas. Prefiere el elemento más pequeño (span o label) que contenga el texto exacto.
84
+ 4. Haz click en la pestaña. Si un click simple no funciona tras 2 intentos, usa 'double_click'.
85
+ 5. Espera el siguiente turno para ver los nuevos campos.
86
+ ```
87
+
88
+ ## Uso de Double Click
89
+ - Úsalo SOLO cuando:
90
+ - La misión lo pida explícitamente (ej: "haz doble clic en la celda").
91
+ - Un elemento (como una pestaña o un icono) no responda tras dos intentos de click simple.
92
+ - Quieras activar modos de edición en tablas o componentes complejos.
93
+
94
+ ## Anti-patrones (NUNCA hagas esto)
95
+ - ❌ Hacer click en un campo `[INFERRED]` sin inspeccionarlo primero
96
+ - ❌ Intentar llenar TODO el formulario en el primer turno sin saber qué campos hay
97
+ - ❌ Hacer click en el botón GUARDAR sin haber verificado que los campos están llenos
98
+ - ❌ Reintentar la misma acción fallida sin investigar la causa
99
+ - ❌ Asumir que un componente con nombre largo (>50 chars) es un campo — probablemente es un contenedor
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: investigation-protocol
3
+ description: Protocolo obligatorio de investigación cuando una acción falla o un elemento es ambiguo.
4
+ ---
5
+
6
+ # Investigation Protocol Skill
7
+
8
+ ## Regla de Oro
9
+ **NUNCA reintentes una acción fallida sin investigar primero.** Si algo falla, SIEMPRE ejecuta el protocolo de investigación antes de intentar de nuevo.
10
+
11
+ ## Protocolo de Investigación (Obligatorio tras cualquier fallo)
12
+
13
+ ### Paso 1: Inspección del elemento
14
+ ```
15
+ Usa: inspect_element_details (idx del elemento que falló)
16
+ Objetivo: Determinar POR QUÉ falló
17
+ ```
18
+
19
+ Revisa en el resultado:
20
+ - ¿El elemento es `disabled` o `readonly`?
21
+ - ¿El elemento está cubierto por otro (`pointer-events: none`, overlay)?
22
+ - ¿El elemento es un `<select>` que requiere interacción diferente a fill?
23
+ - ¿El elemento está en un iframe diferente (`frameIdx`)?
24
+
25
+ ### Paso 2: Auditoría de consola
26
+ ```
27
+ Usa: capture_console_errors
28
+ Objetivo: Buscar errores JavaScript silenciosos
29
+ ```
30
+
31
+ Si la UI no responde o está congelada, puede haber un error JS no visible. Revisa:
32
+ - Errores de red (fetch failed, 404, 500)
33
+ - Errores de JS (TypeError, undefined)
34
+ - Warnings de frameworks (React, Angular)
35
+
36
+ ### Paso 3: Captura de evidencia
37
+ ```
38
+ Usa: create_test_evidence (type: annotated_screenshot)
39
+ Objetivo: Documentar el estado actual para debugging
40
+ ```
41
+
42
+ Si identificas un bug real de la aplicación (no un error tuyo), documéntalo con evidencia anotada.
43
+
44
+ ### Paso 4: Decisión
45
+ Después de investigar, toma UNA de estas decisiones:
46
+ 1. **Reintentar con variación**: Si descubriste que el campo es un select, usa click en vez de fill.
47
+ 2. **Buscar alternativa**: Si el elemento está cubierto, busca otro camino al mismo objetivo.
48
+ 3. **Reportar bug**: Si es un bug de la aplicación, usa `report_application_bug`.
49
+ 4. **Reportar incapacidad**: Si no puedes avanzar, usa `report_inability_to_proceed`.
50
+
51
+ ## Cuándo usar cada herramienta de investigación
52
+
53
+ | Situación | Herramienta | Obligatoria |
54
+ |-----------|------------|-------------|
55
+ | Acción falló con Timeout | `inspect_element_details` | ✅ SÍ |
56
+ | Elemento con nombre [INFERRED] | `inspect_element_details` | ✅ SÍ |
57
+ | UI no responde / congelada | `capture_console_errors` | ✅ SÍ |
58
+ | Antes de hacer GUARDAR/SAVE | `validate_element_state` | ✅ SÍ |
59
+ | Después de GUARDAR exitoso | `extract_table_data` | Recomendada |
60
+ | Bug encontrado | `create_test_evidence` | ✅ SÍ |
61
+ | Fin de misión exitosa | `create_test_evidence` | Recomendada |
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: modal-master
3
+ description: Gestión experta de ventanas modales, diálogos y popups en pruebas de QA.
4
+ ---
5
+
6
+ # Modal Master Skill
7
+
8
+ Esta habilidad permite al agente detectar, navegar y cerrar ventanas modales de manera eficiente, evitando que el flujo del test se bloquee por elementos superpuestos.
9
+
10
+ ## Cuándo usar esta habilidad
11
+ - Cuando el agente realiza una acción y aparece un diálogo de confirmación (ej: "¿Desea guardar los cambios?").
12
+ - Cuando un popup de error o notificación bloquea el acceso al resto de la página.
13
+ - Cuando la misión requiere interactuar específicamente con elementos dentro de un modal.
14
+
15
+ ## Instrucciones para el Agente
16
+
17
+ ### 1. Detección de Modales
18
+ Antes de intentar interactuar con un elemento del fondo, verifica si hay un modal activo buscando:
19
+ - Elementos con `role="dialog"` o `role="alertdialog"`.
20
+ - Atributos `aria-modal="true"`.
21
+ - Overlays oscuros que cubren la pantalla (ej: clases tipo `backdrop`, `overlay`, `modal-open`).
22
+ - Títulos destacados que no estaban antes de la acción.
23
+
24
+ ### 2. Prioridad de Interacción
25
+ Si un modal está abierto:
26
+ - **Prioridad 1**: Si el modal es informativo (Warning/Error), léelo y repórtalo en tu pensamiento antes de cerrarlo.
27
+ - **Prioridad 2**: Si el modal es de confirmación para la acción que acabas de realizar, haz clic en el botón afirmativo (ej: "Aceptar", "Sí", "Confirmar").
28
+ - **Prioridad 3**: Si el modal es intrusivo y no permite seguir, busca el botón de cierre (usualmente una 'X', un botón "Cerrar" o "Cancelar").
29
+
30
+ ### 3. Estrategias de Cierre
31
+ Si no encuentras un botón de cierre claro:
32
+ - Busca botones con clases `close`, `btn-close` o que tengan un ícono dentro.
33
+ - Usa `send_keyboard_event` con key `Escape` para intentar cerrar el modal/popup/dropdown.
34
+ - Verifica si el modal desaparece al hacer clic en el backdrop (el área fuera del cuadro blanco).
35
+
36
+ ### 4. Flujo de Trabajo (Ejemplo)
37
+ 1. **Acción**: Clic en "Eliminar".
38
+ 2. **Scan**: Detecto un modal con el texto "¿Está seguro?".
39
+ 3. **Validación**: Uso `validate_element_state` para verificar que el botón "Eliminar permanentemente" es visible dentro del modal.
40
+ 4. **Ejecución**: Clic en el botón del modal.
41
+ 5. **Verificación**: Espero a que el modal desaparezca antes de realizar la siguiente acción de la misión.
@@ -0,0 +1,82 @@
1
+ ---
2
+ name: native-control-expert
3
+ description: Protocolo experto para interactuar con controles nativos del navegador (time pickers, date pickers, selects, etc.)
4
+ ---
5
+
6
+ # Native Control Expert Skill
7
+
8
+ Esta habilidad define el protocolo que el agente DEBE seguir cuando la misión involucra interactuar con controles nativos del navegador que **NO responden bien** a la acción estándar `fill`.
9
+
10
+ ## Controles Nativos que REQUIEREN esta skill
11
+
12
+ | Control | Ejemplo | Acción preferida |
13
+ |---------|---------|-----------------|
14
+ | `<input type="time">` | Campo de hora `HH:mm` | `interact_native_control` con `control_type: "time"` |
15
+ | `<input type="date">` | Campo de fecha `YYYY-MM-DD` | `interact_native_control` con `control_type: "date"` |
16
+ | `<input type="datetime-local">` | Fecha y hora combinados | `interact_native_control` con `control_type: "datetime"` |
17
+ | `<input type="color">` | Selector de color | `interact_native_control` con `control_type: "color"` |
18
+ | `<input type="range">` | Slider numérico | `interact_native_control` con `control_type: "range"` |
19
+ | `<select>` nativo | Dropdown estándar HTML | `interact_native_control` con `control_type: "select"` |
20
+ | Combobox/Autocomplete | Campos con `role="combobox"` | `click` + `fill` letra + `send_keyboard_event` ArrowDown + Enter |
21
+ | Time picker con diálogo | Campos que abren un popup al hacer click | `send_keyboard_event` con Arrow keys |
22
+
23
+ ## Protocolo de Interacción con Time Pickers
24
+
25
+ ### ❌ LO QUE NUNCA DEBES HACER:
26
+ - Intentar `fill` repetidamente si falla la primera vez
27
+ - Hacer click una y otra vez sin cambiar de estrategia (esto causa loop detection)
28
+ - Asumir que un time picker funciona como un input de texto normal
29
+
30
+ ### ✅ LO QUE SÍ DEBES HACER:
31
+
32
+ **Estrategia 1: interact_native_control (PREFERIDA)**
33
+ ```
34
+ 1. Usa inspect_element_details para confirmar que es type="time"
35
+ 2. Usa interact_native_control con control_type: "time", value: "13:05"
36
+ 3. Verifica el resultado
37
+ ```
38
+
39
+ **Estrategia 2: Keyboard Navigation (FALLBACK)**
40
+ ```
41
+ 1. Haz click en el campo de hora
42
+ 2. Usa send_keyboard_event con key: "Control+a" para seleccionar todo
43
+ 3. Usa send_keyboard_event para escribir los dígitos del tiempo
44
+ 4. Ejemplo: Para "13:05", envía las teclas: "1", "3", "0", "5"
45
+ 5. Usa send_keyboard_event con key: "Tab" para navegar al siguiente campo
46
+ ```
47
+
48
+ **Estrategia 3: Arrow Keys (ULTIMO RECURSO)**
49
+ ```
50
+ 1. Haz click en el segmento de horas del campo
51
+ 2. Usa send_keyboard_event con key: "ArrowUp" o "ArrowDown" para ajustar la hora
52
+ 3. Usa send_keyboard_event con key: "Tab" para mover al segmento de minutos
53
+ 4. Repite con ArrowUp/ArrowDown para los minutos
54
+ ```
55
+
56
+ ## Protocolo de Interacción con Date Pickers
57
+
58
+ ### ✅ Estrategia preferida:
59
+ ```
60
+ 1. Confirma con inspect_element_details que es type="date"
61
+ 2. Usa interact_native_control con control_type: "date", value: "2026-03-25"
62
+ 3. IMPORTANTE: El formato SIEMPRE es YYYY-MM-DD
63
+ ```
64
+
65
+ ## Detección: ¿Cuándo usar esta skill?
66
+
67
+ Debes activar esta skill cuando:
68
+ 1. Ves un componente FIELD con nombre que incluye "HH:mm" o "hora" o "time"
69
+ 2. Un campo tiene `role="combobox"` y `aria-expanded`
70
+ 3. Un `fill()` falla en un campo de fecha/hora con Timeout o sin efecto
71
+ 4. El inspector muestra `type="time"` o `type="date"` en los atributos
72
+
73
+ ## Keyboard Shortcuts Útiles
74
+
75
+ | Tecla | Efecto en controles nativos |
76
+ |-------|-----------------------------|
77
+ | `Tab` | Mover al siguiente segmento (horas → minutos → AM/PM) |
78
+ | `ArrowUp` | Incrementar valor del segmento actual |
79
+ | `ArrowDown` | Decrementar valor del segmento actual |
80
+ | `Escape` | Cerrar dropdown/calendar que se abrió |
81
+ | `Enter` | Confirmar selección en calendarios |
82
+ | `Control+a` | Seleccionar todo el contenido del campo |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcadialdev/arcality",
3
- "version": "2.4.31",
3
+ "version": "2.4.32",
4
4
  "description": "AI-powered QA testing tool — Autonomous web testing agent by Arcadial",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -18,6 +18,7 @@
18
18
  "bin/",
19
19
  "scripts/",
20
20
  "src/",
21
+ ".agent/",
21
22
  ".agents/",
22
23
  "tests/_helpers/agentic-runner.bundle.spec.js",
23
24
  "tests/_helpers/ArcalityReporter.js",
@@ -194,7 +194,7 @@ function showBanner() {
194
194
  }
195
195
 
196
196
  const ARCALITY_BRAIN_URL = "https://api.anthropic.com/v1/messages";
197
- const ARCALITY_MODEL = process.env.CLAUDE_MODEL || "claude-3-5-sonnet-20241022";
197
+ const ARCALITY_MODEL = process.env.CLAUDE_MODEL || "claude-4-5-sonnet-latest";
198
198
  const OUT_DIR = path.join(PROJECT_ROOT, "tests");
199
199
  const LOGS_DIR = path.join(PROJECT_ROOT, "logs");
200
200
 
@@ -807,7 +807,22 @@ async function main() {
807
807
 
808
808
  // ── End Mission ──
809
809
  const { endMission } = await import('../src/arcalityClient.mjs');
810
- await endMission(mission.mission_id, 'success');
810
+
811
+ let finalUsage = undefined;
812
+ try {
813
+ const ctxDir = process.env.CONTEXT_DIR;
814
+ if (fs.existsSync(ctxDir)) {
815
+ const files = fs.readdirSync(ctxDir);
816
+ const logFiles = files.filter(f => f.startsWith('agent-log-') && f.endsWith('.json')).sort();
817
+ if (logFiles.length > 0) {
818
+ const lastLog = path.join(ctxDir, logFiles[logFiles.length - 1]);
819
+ const logData = JSON.parse(fs.readFileSync(lastLog, 'utf8'));
820
+ finalUsage = logData.usage;
821
+ }
822
+ }
823
+ } catch(e) {}
824
+
825
+ await endMission(mission.mission_id, 'success', finalUsage);
811
826
 
812
827
  // ── Ping Project ──
813
828
  await pingProject(finalProjectId, process.env.ARCALITY_API_KEY);
@@ -866,7 +881,20 @@ async function main() {
866
881
  // End mission with failure
867
882
  try {
868
883
  const { endMission } = await import('../src/arcalityClient.mjs');
869
- await endMission(mission.mission_id, 'failed');
884
+ let finalUsage = undefined;
885
+ try {
886
+ const ctxDir = process.env.CONTEXT_DIR;
887
+ if (fs.existsSync(ctxDir)) {
888
+ const files = fs.readdirSync(ctxDir);
889
+ const logFiles = files.filter(f => f.startsWith('agent-log-') && f.endsWith('.json')).sort();
890
+ if (logFiles.length > 0) {
891
+ const lastLog = path.join(ctxDir, logFiles[logFiles.length - 1]);
892
+ const logData = JSON.parse(fs.readFileSync(lastLog, 'utf8'));
893
+ finalUsage = logData.usage;
894
+ }
895
+ }
896
+ } catch(e) {}
897
+ await endMission(mission.mission_id, 'failed', finalUsage);
870
898
  } catch { }
871
899
  } finally {
872
900
  const sRep = spinner();
@@ -234,8 +234,9 @@ export async function startMission(prompt, targetUrl) {
234
234
  * Marks a mission as completed.
235
235
  * @param {string} missionId
236
236
  * @param {'success'|'failed'|'cancelled'} result
237
+ * @param {object} usage - Optional usage statistics directly from the AI
237
238
  */
238
- export async function endMission(missionId, result) {
239
+ export async function endMission(missionId, result, usage = null) {
239
240
  const apiBase = getEffectiveApiBase();
240
241
  if (!apiBase || !missionId) return { ok: true };
241
242
 
@@ -247,7 +248,7 @@ export async function endMission(missionId, result) {
247
248
  'Content-Type': 'application/json',
248
249
  'x-api-key': key
249
250
  },
250
- body: JSON.stringify({ result })
251
+ body: JSON.stringify({ result, usage })
251
252
  });
252
253
  } catch { }
253
254
  return { ok: true };
@@ -1491,8 +1491,7 @@ When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 St
1491
1491
 
1492
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
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.`,
1495
- cache_control: { type: "ephemeral" }
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.`
1496
1495
  }
1497
1496
  ];
1498
1497
  if (this.testInfo) {
@@ -1511,8 +1510,7 @@ When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 St
1511
1510
  type: "base64",
1512
1511
  media_type: "image/png",
1513
1512
  data: base64Img
1514
- },
1515
- cache_control: { type: "ephemeral" }
1513
+ }
1516
1514
  },
1517
1515
  {
1518
1516
  type: "text",
@@ -1537,14 +1535,14 @@ ${history.slice(-25).join("\n") || "None"}`
1537
1535
  },
1538
1536
  {
1539
1537
  type: "text",
1540
- text: `Please analyze the current state and provide the next step.`,
1541
- cache_control: { type: "ephemeral" }
1538
+ text: `Please analyze the current state and provide the next step.`
1542
1539
  }
1543
1540
  ]
1544
1541
  }
1545
1542
  ];
1546
1543
  console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Arcality est\xE1 procesando la visi\xF3n...`);
1547
1544
  const startTime = Date.now();
1545
+ let totalUsage = { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 };
1548
1546
  for (let turn = 0; turn < 10; turn++) {
1549
1547
  let response;
1550
1548
  let retryCount = 0;
@@ -1611,6 +1609,12 @@ ${history.slice(-25).join("\n") || "None"}`
1611
1609
  }
1612
1610
  const data = await response.json();
1613
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
+ }
1614
1618
  anthropicMessages.push({
1615
1619
  role: "assistant",
1616
1620
  content: message.content
@@ -1619,7 +1623,7 @@ ${history.slice(-25).join("\n") || "None"}`
1619
1623
  const textResponse = message.content.find((c) => c.type === "text")?.text || "";
1620
1624
  if (toolCalls.length === 0) {
1621
1625
  console.log(`(${((Date.now() - startTime) / 1e3).toFixed(1)}s)`);
1622
- return { thought: textResponse || "No action decided", actions: [], usage: data.usage };
1626
+ return { thought: textResponse || "No action decided", actions: [], usage: totalUsage };
1623
1627
  }
1624
1628
  const toolResults = [];
1625
1629
  let uiActionCall = null;
@@ -1679,7 +1683,7 @@ ${history.slice(-25).join("\n") || "None"}`
1679
1683
  \u{1F4A1} SUGERENCIA: ${toolInput.prompt_suggestion}`,
1680
1684
  actions: [],
1681
1685
  finish: true,
1682
- usage: data.usage
1686
+ usage: totalUsage
1683
1687
  };
1684
1688
  } else if (toolName === "validate_element_state") {
1685
1689
  const { idx, validations } = toolInput;
@@ -2187,7 +2191,7 @@ ${report}` });
2187
2191
  return { ...a, value: finalValue, selector: c?.selector, frameIdx: c?.frameIdx, description: c?.name, type: c?.type };
2188
2192
  }),
2189
2193
  finish: input.finish,
2190
- usage: data.usage
2194
+ usage: totalUsage
2191
2195
  };
2192
2196
  }
2193
2197
  }
@@ -2450,6 +2454,7 @@ function captureValidationRule(errorMessage, context) {
2450
2454
  const maxSteps = 30;
2451
2455
  let lastSeenSuccessToast = "";
2452
2456
  let accumulatedCost = 0;
2457
+ let totalMissionUsage = { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 };
2453
2458
  const saveMissionResults = () => {
2454
2459
  if (resultsSaved)
2455
2460
  return;
@@ -2487,57 +2492,76 @@ function captureValidationRule(errorMessage, context) {
2487
2492
  }
2488
2493
  try {
2489
2494
  const logPath = path2.join(contextDir, `agent-log-${Date.now()}.json`);
2490
- 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));
2491
2496
  } catch (err) {
2492
2497
  }
2493
2498
  };
2494
2499
  let collectiveMemoryPersisted = false;
2495
- const persistCollectiveMemory = async () => {
2496
- if (collectiveMemoryPersisted)
2497
- return;
2498
- collectiveMemoryPersisted = true;
2500
+ const persistCollectiveMemory = async (isFailed = false) => {
2499
2501
  const pid = process.env.ARCALITY_PROJECT_ID || "";
2500
2502
  const apiUrl = process.env.ARCALITY_API_URL || "";
2501
2503
  const apiKey = process.env.ARCALITY_API_KEY || "";
2502
2504
  if (!pid || !apiUrl || !apiKey) {
2503
- 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.`);
2504
2506
  return;
2505
2507
  }
2506
- console.log(`\u{1F9E0} [COLLECTIVE MEMORY] Persistiendo conocimiento (project: ${pid})...`);
2508
+ if (collectiveMemoryPersisted && !isFailed) {
2509
+ console.log(`[CollectiveMemory] \u2139\uFE0F Ya persistido como \xE9xito. Skip duplicado.`);
2510
+ return;
2511
+ }
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})...`);
2507
2517
  try {
2508
- const stepsNarrative = stepsData.map((s) => `${s.action_data?.action || "action"} "${s.componentName}" en ${s.url}`).join(" \u2192 ").substring(0, 500);
2509
- const knowledgeId = await pushKnowledge({
2510
- doc_type: "PROCESS",
2511
- title: `Flujo exitoso: ${prompt.substring(0, 100)}`,
2512
- content: stepsNarrative || prompt.substring(0, 500)
2513
- });
2514
- if (knowledgeId)
2515
- console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Knowledge guardado: ${knowledgeId}`);
2516
- else
2517
- console.warn(`[CollectiveMemory] \u26A0\uFE0F pushKnowledge retorn\xF3 null (revisar HTTP status arriba)`);
2518
- const uniqueUrls = [...new Set(stepsData.map((s) => s.url).filter(Boolean))];
2519
- if (uniqueUrls.length > 1) {
2520
- const ruleId = await pushRule({
2521
- rule_type: "NAVIGATION",
2522
- title: `Flujo de navegaci\xF3n: ${prompt.substring(0, 80)}`,
2523
- description: `Agente naveg\xF3 ${uniqueUrls.length} p\xE1ginas: ${uniqueUrls.join(" \u2192 ").substring(0, 400)}`,
2524
- 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)
2525
2525
  });
2526
- if (ruleId)
2527
- console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Regla NAVIGATION guardada: ${ruleId}`);
2528
- }
2529
- const submitStep = stepsData.find(
2530
- (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"))
2531
- );
2532
- if (submitStep) {
2533
- const ruleId2 = await pushRule({
2534
- rule_type: "UI_UX",
2535
- title: `Patr\xF3n de guardado: ${prompt.substring(0, 60)}`,
2536
- 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)}`,
2537
2559
  severity: "important"
2538
2560
  });
2539
- if (ruleId2)
2540
- 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`);
2541
2565
  }
2542
2566
  } catch (err) {
2543
2567
  console.warn(`[CollectiveMemory] \u274C Error inesperado en persistCollectiveMemory: ${err?.message}`);
@@ -2688,8 +2712,15 @@ function captureValidationRule(errorMessage, context) {
2688
2712
  response = await agent.askIA(prompt, history, stepCount);
2689
2713
  if (response.usage) {
2690
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;
2691
2717
  const outputs = response.usage.output_tokens || 0;
2692
- const cost = inputs / 1e6 * 3 + outputs / 1e6 * 15;
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;
2693
2724
  accumulatedCost += cost;
2694
2725
  if (accumulatedCost >= 6) {
2695
2726
  console.warn(`
@@ -3073,6 +3104,11 @@ Evidence: ${JSON.stringify(v.evidence).substring(0, 400)}`,
3073
3104
  }
3074
3105
  saveMissionResults();
3075
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
+ });
3076
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}`);
3077
3113
  }
3078
3114
  });