@arcadialdev/arcality 2.4.31 → 2.4.33

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/bin/arcality.mjs CHANGED
@@ -14,15 +14,24 @@ const rawArgs = process.argv.slice(2);
14
14
  let isRun = false;
15
15
  let isInit = false;
16
16
  let isSetup = false;
17
+ let isLogs = false;
17
18
 
18
19
  // Buscamos los comandos en cualquier posición para evitar filtros de NPM en Windows
19
20
  rawArgs.forEach(arg => {
20
21
  if (arg === 'run') isRun = true;
21
22
  if (arg === 'init') isInit = true;
22
23
  if (arg === 'setup') isSetup = true;
24
+ if (arg === '--logs') isLogs = true;
23
25
  });
24
26
 
25
- if (isInit) {
27
+ if (isLogs) {
28
+ const logsScript = path.join(PACKAGE_ROOT, 'scripts', 'arcality-logs.mjs');
29
+ spawn('node', [logsScript], {
30
+ stdio: 'inherit',
31
+ cwd: process.cwd(),
32
+ env: { ...process.env, ARCALITY_ROOT: PACKAGE_ROOT }
33
+ }).on('exit', code => process.exit(code || 0));
34
+ } else if (isInit) {
26
35
  const initScript = path.join(PACKAGE_ROOT, 'scripts', 'init.mjs');
27
36
  spawn('node', [initScript, ...rawArgs.filter(a => a !== 'init')], {
28
37
  stdio: 'inherit',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcadialdev/arcality",
3
- "version": "2.4.31",
3
+ "version": "2.4.33",
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",
@@ -7,6 +7,7 @@ module.exports = defineConfig({
7
7
  testDir: path.join(__dirname, 'tests', '_helpers'),
8
8
  testMatch: ['agentic-runner.bundle.spec.js'],
9
9
  reporter: [
10
+ ['line'],
10
11
  [path.join(__dirname, 'tests', '_helpers', 'ArcalityReporter.js'),
11
12
  { outputDir: process.env.REPORTS_DIR || path.join(__dirname, 'tests-report') }]
12
13
  ],
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import chalk from "chalk";
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ const PROJECT_ROOT = process.env.ARCALITY_ROOT || path.join(__dirname, "..");
10
+
11
+ const contextDir = path.join(process.cwd(), "out");
12
+ const centralLog = path.join(contextDir, "arcality-history.log");
13
+
14
+ console.log(chalk.cyan(`\n🔍 Arcality Logs Viewer`));
15
+ console.log(chalk.gray(`=========================\n`));
16
+
17
+ if (!fs.existsSync(centralLog)) {
18
+ console.log(chalk.yellow(`No se encontró un historial de logs en este proyecto.`));
19
+ console.log(chalk.gray(`(Directorio actual: ${contextDir})\n`));
20
+ console.log(`Ejecuta algunas misiones de prueba primero usando el comando arcality.\n`);
21
+ process.exit(0);
22
+ }
23
+
24
+ try {
25
+ const rawData = fs.readFileSync(centralLog, 'utf8');
26
+ const lines = rawData.split('\n').filter(l => l.trim().length > 0);
27
+
28
+ if (lines.length === 0) {
29
+ console.log(chalk.yellow(`El archivo de historial de misiones está vacío.\n`));
30
+ process.exit(0);
31
+ }
32
+
33
+ console.log(chalk.bold(`Registros encontrados: `) + chalk.cyan(lines.length));
34
+ console.log(chalk.gray(`Mostrando los últimos 20 resultados:`));
35
+ console.log();
36
+
37
+ const recentLines = lines.slice(-20);
38
+ let totalInput = 0;
39
+ let totalOutput = 0;
40
+
41
+ recentLines.forEach((line, index) => {
42
+ try {
43
+ const data = JSON.parse(line);
44
+
45
+ // Icono según resultado
46
+ const statusIcon = data.success ? chalk.green('✅') : chalk.red('❌');
47
+
48
+ // Fecha bonita
49
+ const date = new Date(data.ts).toLocaleString();
50
+
51
+ // Contabilizar tokens
52
+ const inT = data.usage?.input_tokens || 0;
53
+ const caT = data.usage?.cache_creation_input_tokens || 0;
54
+ const crT = data.usage?.cache_read_input_tokens || 0;
55
+ const totalIn = inT + caT + crT;
56
+ const totalOut = data.usage?.output_tokens || 0;
57
+
58
+ totalInput += totalIn;
59
+ totalOutput += totalOut;
60
+
61
+ const costStr = chalk.yellow(`[IN: ${totalIn} | OUT: ${totalOut}]`);
62
+ const targetStr = data.target ? chalk.gray(` @ ${data.target}`) : '';
63
+
64
+ console.log(`${statusIcon} ${chalk.blue(date)} ${costStr}`);
65
+ console.log(` ${chalk.white(data.mission)}${targetStr}`);
66
+ console.log(` ${chalk.gray(`Pasos dados: ${data.steps}`)}\n`);
67
+ } catch (e) {
68
+ console.log(chalk.red(`⚠️ Error parseando línea: ${line.substring(0, 50)}...\n`));
69
+ }
70
+ });
71
+
72
+ console.log(chalk.gray(`--------------------------------------------------\n`));
73
+
74
+ } catch (err) {
75
+ console.error(chalk.red(`❌ Ocurrió un error al leer los logs:`), err.message);
76
+ }
@@ -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 };
@@ -0,0 +1,61 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ // Mapeo curado de tecnologías clave a sus skills de pruebas/qa/patrones de autoskills.sh
5
+ // Son recursos muy ligeros en formato Markdown
6
+ const SKILLS_MAP: Record<string, string> = {
7
+ 'next': 'https://skills.sh/vercel-labs/next-skills/next-best-practices/raw',
8
+ 'vue': 'https://skills.sh/antfu/skills/vue-best-practices/raw',
9
+ 'tailwindcss': 'https://skills.sh/giuseppe-trisciuoglio/developer-kit/tailwind-css-patterns/raw',
10
+ 'react': 'https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices/raw',
11
+ 'angular': 'https://skills.sh/angular/skills/angular-developer/raw',
12
+ 'nuxt': 'https://skills.sh/antfu/skills/nuxt/raw',
13
+ 'svelte': 'https://skills.sh/ejirocodes/agent-skills/svelte5-best-practices/raw',
14
+ 'astro': 'https://skills.sh/astrolicious/agent-skills/astro/raw',
15
+ 'prisma': 'https://skills.sh/prisma/skills/prisma-postgres/raw'
16
+ };
17
+
18
+ export async function detectAndFetchEphemeralSkills(projectRoot: string): Promise<string> {
19
+ const pkgPath = path.join(projectRoot, 'package.json');
20
+ if (!fs.existsSync(pkgPath)) return '';
21
+
22
+ try {
23
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
24
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
25
+
26
+ const matchedUrls: string[] = [];
27
+ const matchedTechs: string[] = [];
28
+
29
+ for (const [lib, url] of Object.entries(SKILLS_MAP)) {
30
+ // Buscamos coincidencia parcial (ej. si usan @angular/core, next, react-dom)
31
+ const hasLib = Object.keys(deps).some(dep => dep === lib || dep.includes(`/${lib}`));
32
+ if (hasLib) {
33
+ matchedUrls.push(url);
34
+ matchedTechs.push(lib);
35
+ }
36
+ }
37
+
38
+ if (matchedUrls.length === 0) return '';
39
+
40
+ console.log(`>>ARCALITY_STATUS>> ⚡ Stack detectado (${matchedTechs.join(', ')}). QA Skills inyectadas en memoria.`);
41
+
42
+ // Fetch strings in parallel with simple timeout
43
+ const skillsPromises = matchedUrls.map(url => {
44
+ const controller = new AbortController();
45
+ const id = setTimeout(() => controller.abort(), 2000); // Max 2s per request
46
+ return fetch(url, { signal: controller.signal })
47
+ .then(r => r.ok ? r.text() : '')
48
+ .catch(() => '')
49
+ .finally(() => clearTimeout(id));
50
+ });
51
+
52
+ const skillsTexts = await Promise.all(skillsPromises);
53
+
54
+ const validTexts = skillsTexts.filter(Boolean);
55
+ if (validTexts.length === 0) return '';
56
+
57
+ return `\n<TECH_STACK_CONTEXT>\nEl proyecto objetivo utiliza las siguientes tecnologías. Usa estas directrices técnicas para entender el DOM, rutas y posibles bugs visuales:\n\n${validTexts.join('\n\n---\n\n')}\n</TECH_STACK_CONTEXT>\n`;
58
+ } catch {
59
+ return ''; // Fails silently
60
+ }
61
+ }
@@ -1375,6 +1375,14 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
1375
1375
  } catch (e) {
1376
1376
  console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Error en hook de contexto: ${e.message}`);
1377
1377
  }
1378
+ let autoskillsContext = "";
1379
+ try {
1380
+ const path3 = require("path");
1381
+ const toolsRoot = process.env.ARCALITY_ROOT || path3.join(__dirname, "..", "..");
1382
+ const { detectAndFetchEphemeralSkills } = require(path3.join(toolsRoot, "src", "services", "autoskillsService"));
1383
+ autoskillsContext = await detectAndFetchEphemeralSkills(process.cwd());
1384
+ } catch (e) {
1385
+ }
1378
1386
  const systemPromptBlocks = [
1379
1387
  {
1380
1388
  type: "text",
@@ -1386,6 +1394,7 @@ ${prompt}
1386
1394
 
1387
1395
  ${projectInfo}
1388
1396
  ${skillsContext}
1397
+ ${autoskillsContext}
1389
1398
  ${memoryContext}
1390
1399
  ${credentialsContext}
1391
1400
  ${memoryBackendContext}`
@@ -1491,8 +1500,7 @@ When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 St
1491
1500
 
1492
1501
  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
1502
 
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" }
1503
+ 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
1504
  }
1497
1505
  ];
1498
1506
  if (this.testInfo) {
@@ -1511,8 +1519,7 @@ When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 St
1511
1519
  type: "base64",
1512
1520
  media_type: "image/png",
1513
1521
  data: base64Img
1514
- },
1515
- cache_control: { type: "ephemeral" }
1522
+ }
1516
1523
  },
1517
1524
  {
1518
1525
  type: "text",
@@ -1537,14 +1544,14 @@ ${history.slice(-25).join("\n") || "None"}`
1537
1544
  },
1538
1545
  {
1539
1546
  type: "text",
1540
- text: `Please analyze the current state and provide the next step.`,
1541
- cache_control: { type: "ephemeral" }
1547
+ text: `Please analyze the current state and provide the next step.`
1542
1548
  }
1543
1549
  ]
1544
1550
  }
1545
1551
  ];
1546
1552
  console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Arcality est\xE1 procesando la visi\xF3n...`);
1547
1553
  const startTime = Date.now();
1554
+ let totalUsage = { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 };
1548
1555
  for (let turn = 0; turn < 10; turn++) {
1549
1556
  let response;
1550
1557
  let retryCount = 0;
@@ -1611,6 +1618,12 @@ ${history.slice(-25).join("\n") || "None"}`
1611
1618
  }
1612
1619
  const data = await response.json();
1613
1620
  const message = data;
1621
+ if (data.usage) {
1622
+ totalUsage.input_tokens += data.usage.input_tokens || 0;
1623
+ totalUsage.output_tokens += data.usage.output_tokens || 0;
1624
+ totalUsage.cache_creation_input_tokens += data.usage.cache_creation_input_tokens || 0;
1625
+ totalUsage.cache_read_input_tokens += data.usage.cache_read_input_tokens || 0;
1626
+ }
1614
1627
  anthropicMessages.push({
1615
1628
  role: "assistant",
1616
1629
  content: message.content
@@ -1619,7 +1632,7 @@ ${history.slice(-25).join("\n") || "None"}`
1619
1632
  const textResponse = message.content.find((c) => c.type === "text")?.text || "";
1620
1633
  if (toolCalls.length === 0) {
1621
1634
  console.log(`(${((Date.now() - startTime) / 1e3).toFixed(1)}s)`);
1622
- return { thought: textResponse || "No action decided", actions: [], usage: data.usage };
1635
+ return { thought: textResponse || "No action decided", actions: [], usage: totalUsage };
1623
1636
  }
1624
1637
  const toolResults = [];
1625
1638
  let uiActionCall = null;
@@ -1679,7 +1692,7 @@ ${history.slice(-25).join("\n") || "None"}`
1679
1692
  \u{1F4A1} SUGERENCIA: ${toolInput.prompt_suggestion}`,
1680
1693
  actions: [],
1681
1694
  finish: true,
1682
- usage: data.usage
1695
+ usage: totalUsage
1683
1696
  };
1684
1697
  } else if (toolName === "validate_element_state") {
1685
1698
  const { idx, validations } = toolInput;
@@ -2187,7 +2200,7 @@ ${report}` });
2187
2200
  return { ...a, value: finalValue, selector: c?.selector, frameIdx: c?.frameIdx, description: c?.name, type: c?.type };
2188
2201
  }),
2189
2202
  finish: input.finish,
2190
- usage: data.usage
2203
+ usage: totalUsage
2191
2204
  };
2192
2205
  }
2193
2206
  }
@@ -2450,6 +2463,7 @@ function captureValidationRule(errorMessage, context) {
2450
2463
  const maxSteps = 30;
2451
2464
  let lastSeenSuccessToast = "";
2452
2465
  let accumulatedCost = 0;
2466
+ let totalMissionUsage = { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 };
2453
2467
  const saveMissionResults = () => {
2454
2468
  if (resultsSaved)
2455
2469
  return;
@@ -2471,73 +2485,102 @@ function captureValidationRule(errorMessage, context) {
2471
2485
  }
2472
2486
  }
2473
2487
  }
2488
+ const effectiveLocalSteps = stepsData.length > 0 ? stepsData : stepsDataBackup;
2474
2489
  const missionData = {
2475
2490
  prompt,
2476
2491
  steps: history,
2477
- steps_data: stepsData,
2492
+ steps_data: effectiveLocalSteps,
2478
2493
  success: finalSuccess,
2479
2494
  error: hasCriticalError,
2480
2495
  timestamp: Date.now()
2481
2496
  };
2482
2497
  memories.push(missionData);
2483
2498
  fs2.writeFileSync(memoryFile, JSON.stringify(memories, null, 2));
2484
- console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Memoria guardada: ${stepsData.length} pasos nuevos`);
2499
+ console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Memoria guardada: ${effectiveLocalSteps.length} pasos nuevos`);
2485
2500
  } catch (err) {
2486
2501
  console.error("\u274C Fall\xF3 al guardar memoria:", err);
2487
2502
  }
2488
2503
  try {
2489
2504
  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));
2505
+ fs2.writeFileSync(logPath, JSON.stringify({ prompt, history, steps: stepCount, success: finalSuccess, error: hasCriticalError, usage: totalMissionUsage, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
2506
+ try {
2507
+ fs2.appendFileSync(path2.join(contextDir, "arcality-history.log"), JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), mission: prompt.substring(0, 150), success: finalSuccess, steps: stepCount, usage: totalMissionUsage }) + "\n");
2508
+ } catch (e) {
2509
+ }
2491
2510
  } catch (err) {
2492
2511
  }
2493
2512
  };
2494
2513
  let collectiveMemoryPersisted = false;
2495
- const persistCollectiveMemory = async () => {
2496
- if (collectiveMemoryPersisted)
2497
- return;
2498
- collectiveMemoryPersisted = true;
2514
+ const persistCollectiveMemory = async (isFailed = false) => {
2499
2515
  const pid = process.env.ARCALITY_PROJECT_ID || "";
2500
2516
  const apiUrl = process.env.ARCALITY_API_URL || "";
2501
2517
  const apiKey = process.env.ARCALITY_API_KEY || "";
2502
2518
  if (!pid || !apiUrl || !apiKey) {
2503
- console.warn(`[CollectiveMemory] \u26A0\uFE0F Vars faltantes \u2014 project_id='${pid}' api_url='${apiUrl}' api_key='${apiKey ? "OK" : "MISSING"}'`);
2519
+ console.warn(`[CollectiveMemory] \u26A0\uFE0F Vars faltantes \u2014 project_id='${pid}' api_url='${apiUrl}' api_key='${apiKey ? "OK" : "MISSING"}' \u2192 NO persiste.`);
2504
2520
  return;
2505
2521
  }
2506
- console.log(`\u{1F9E0} [COLLECTIVE MEMORY] Persistiendo conocimiento (project: ${pid})...`);
2522
+ if (collectiveMemoryPersisted && !isFailed) {
2523
+ console.log(`[CollectiveMemory] \u2139\uFE0F Ya persistido como \xE9xito. Skip duplicado.`);
2524
+ return;
2525
+ }
2526
+ if (!isFailed && stepsData.length === 0 && stepsDataBackup.length === 0) {
2527
+ console.warn(`[CollectiveMemory] \u26A0\uFE0F stepsData vac\xEDo en path de \xE9xito \u2014 intentando usar backup...`);
2528
+ }
2529
+ const effectiveSteps = stepsData.length > 0 ? stepsData : stepsDataBackup;
2530
+ console.log(`
2531
+ ======================================================`);
2532
+ console.log(`\u{1F9E0} [MEMORIA COLECTIVA - INGESTA DE APRENDIZAJE]`);
2533
+ console.log(`>>arcality>> Persistiendo conocimiento (project: ${pid}, steps: ${effectiveSteps.length}, isFailed: ${isFailed})...`);
2534
+ console.log(`======================================================
2535
+ `);
2507
2536
  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"
2537
+ const stepsNarrative = effectiveSteps.map((s) => `${s.action_data?.action || "action"} "${s.componentName}" en ${s.url}`).join(" \u2192 ").substring(0, 500);
2538
+ if (!isFailed) {
2539
+ collectiveMemoryPersisted = true;
2540
+ const knowledgeId = await pushKnowledge({
2541
+ doc_type: "PROCESS",
2542
+ title: `Flujo exitoso: ${prompt.substring(0, 100)}`,
2543
+ content: stepsNarrative || prompt.substring(0, 500)
2525
2544
  });
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)}`,
2545
+ if (knowledgeId)
2546
+ console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Knowledge guardado: ${knowledgeId}`);
2547
+ else
2548
+ console.warn(`[CollectiveMemory] \u26A0\uFE0F pushKnowledge retorn\xF3 null (revisar HTTP status arriba)`);
2549
+ const uniqueUrls = [...new Set(effectiveSteps.map((s) => s.url).filter(Boolean))];
2550
+ if (uniqueUrls.length > 1) {
2551
+ const ruleId = await pushRule({
2552
+ rule_type: "NAVIGATION",
2553
+ title: `Flujo de navegaci\xF3n: ${prompt.substring(0, 80)}`,
2554
+ description: `Agente naveg\xF3 ${uniqueUrls.length} p\xE1ginas: ${uniqueUrls.join(" \u2192 ").substring(0, 400)}`,
2555
+ severity: "suggestion"
2556
+ });
2557
+ if (ruleId)
2558
+ console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Regla NAVIGATION guardada: ${ruleId}`);
2559
+ }
2560
+ const submitStep = effectiveSteps.find(
2561
+ (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"))
2562
+ );
2563
+ if (submitStep) {
2564
+ const ruleId2 = await pushRule({
2565
+ rule_type: "UI_UX",
2566
+ title: `Patr\xF3n de guardado: ${prompt.substring(0, 60)}`,
2567
+ description: `Flujo exitoso incluy\xF3 guardado/creaci\xF3n. Pasos: ${stepsNarrative.substring(0, 400)}`,
2568
+ severity: "important"
2569
+ });
2570
+ if (ruleId2)
2571
+ console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Regla UI_UX guardada: ${ruleId2}`);
2572
+ }
2573
+ } else {
2574
+ const failRuleId = await pushRule({
2575
+ rule_type: "VALIDATION",
2576
+ title: `Misi\xF3n fallida: ${prompt.substring(0, 80)}`,
2577
+ description: `El agente no pudo completar: "${prompt.substring(0, 200)}". \xDAltimos pasos: ${stepsNarrative.substring(0, 300)}`,
2537
2578
  severity: "important"
2538
2579
  });
2539
- if (ruleId2)
2540
- console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Regla UI_UX guardada: ${ruleId2}`);
2580
+ if (failRuleId)
2581
+ console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Regla de fallo guardada: ${failRuleId}`);
2582
+ else
2583
+ console.warn(`[CollectiveMemory] \u26A0\uFE0F No se pudo persistir misi\xF3n fallida`);
2541
2584
  }
2542
2585
  } catch (err) {
2543
2586
  console.warn(`[CollectiveMemory] \u274C Error inesperado en persistCollectiveMemory: ${err?.message}`);
@@ -2679,7 +2722,12 @@ function captureValidationRule(errorMessage, context) {
2679
2722
  response = null;
2680
2723
  } else {
2681
2724
  guideStepCount++;
2682
- console.log(`>>ARCALITY_STATUS>> \u2728 Gu\xEDa de \xE9xito: Paso validado (Gu\xEDa #${guideStepCount}, NO consume turno IA)`);
2725
+ console.log(`
2726
+ ======================================================`);
2727
+ console.log(`\u{1F680} [USANDO GU\xCDA DE \xC9XITO - MEMORIA COLECTIVA AHORRANDO TOKENS]`);
2728
+ console.log(`>>arcality>> Paso validado (Gu\xEDa #${guideStepCount}, NO consume turno IA)`);
2729
+ console.log(`======================================================
2730
+ `);
2683
2731
  }
2684
2732
  }
2685
2733
  if (!response) {
@@ -2688,8 +2736,15 @@ function captureValidationRule(errorMessage, context) {
2688
2736
  response = await agent.askIA(prompt, history, stepCount);
2689
2737
  if (response.usage) {
2690
2738
  const inputs = response.usage.input_tokens || 0;
2739
+ const cacheCreates = response.usage.cache_creation_input_tokens || 0;
2740
+ const cacheReads = response.usage.cache_read_input_tokens || 0;
2691
2741
  const outputs = response.usage.output_tokens || 0;
2692
- const cost = inputs / 1e6 * 3 + outputs / 1e6 * 15;
2742
+ totalMissionUsage.input_tokens += inputs;
2743
+ totalMissionUsage.output_tokens += outputs;
2744
+ totalMissionUsage.cache_creation_input_tokens += cacheCreates;
2745
+ totalMissionUsage.cache_read_input_tokens += cacheReads;
2746
+ const totalInputs = inputs + cacheCreates;
2747
+ const cost = totalInputs / 1e6 * 3 + cacheReads / 1e6 * 0.3 + outputs / 1e6 * 15;
2693
2748
  accumulatedCost += cost;
2694
2749
  if (accumulatedCost >= 6) {
2695
2750
  console.warn(`
@@ -2702,7 +2757,12 @@ function captureValidationRule(errorMessage, context) {
2702
2757
  }
2703
2758
  }
2704
2759
  }
2705
- console.log(`\u{1F9E0} Pensamiento: ${response.thought}`);
2760
+ console.log(`
2761
+ ======================================================`);
2762
+ console.log(`\u{1F916} [RAZONAMIENTO DEL AGENTE]`);
2763
+ console.log(`${response.thought}`);
2764
+ console.log(`======================================================
2765
+ `);
2706
2766
  if (!response.finish && history.length >= 6) {
2707
2767
  const lastSixActions = history.slice(-6);
2708
2768
  const clickActions = lastSixActions.filter((h) => h.includes("click en"));
@@ -3073,6 +3133,11 @@ Evidence: ${JSON.stringify(v.evidence).substring(0, 400)}`,
3073
3133
  }
3074
3134
  saveMissionResults();
3075
3135
  if (!aiMarkedSuccess) {
3136
+ 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}`);
3137
+ await persistCollectiveMemory(true).catch(() => {
3138
+ });
3076
3139
  throw new Error("Misi\xF3n no completada o finalizada con errores.");
3140
+ } else {
3141
+ 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
3142
  }
3078
3143
  });