@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.
- package/.agent/skills/form-expert.md +99 -0
- package/.agent/skills/investigation-protocol.md +61 -0
- package/.agent/skills/modal-master.md +41 -0
- package/.agent/skills/native-control-expert.md +82 -0
- package/package.json +2 -1
- package/scripts/gen-and-run.mjs +31 -3
- package/src/arcalityClient.mjs +3 -2
- package/tests/_helpers/agentic-runner.bundle.spec.js +83 -47
|
@@ -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.
|
|
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",
|
package/scripts/gen-and-run.mjs
CHANGED
|
@@ -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-
|
|
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
|
-
|
|
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
|
-
|
|
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();
|
package/src/arcalityClient.mjs
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
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 (
|
|
2527
|
-
console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
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 (
|
|
2540
|
-
console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Regla
|
|
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
|
-
|
|
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
|
});
|