@arcadialdev/arcality 2.6.7 → 3.0.0
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/bin/arcality.mjs +11 -0
- package/package.json +1 -1
- package/playwright.config.js +24 -5
- package/scripts/gen-and-run.mjs +122 -100
- package/src/arcalityClient.mjs +128 -1
- package/src/configLoader.mjs +6 -2
- package/src/configManager.mjs +1 -1
- package/tests/_helpers/ArcalityReporter.js +234 -251
- package/tests/_helpers/agentic-runner.bundle.spec.js +563 -109
package/bin/arcality.mjs
CHANGED
|
@@ -15,6 +15,7 @@ let isRun = false;
|
|
|
15
15
|
let isInit = false;
|
|
16
16
|
let isSetup = false;
|
|
17
17
|
let isLogs = false;
|
|
18
|
+
let isRunAll = false; // CI/CD mode: runs all missions from API headlessly
|
|
18
19
|
|
|
19
20
|
// Buscamos los comandos en cualquier posición para evitar filtros de NPM en Windows
|
|
20
21
|
rawArgs.forEach(arg => {
|
|
@@ -22,6 +23,7 @@ rawArgs.forEach(arg => {
|
|
|
22
23
|
if (arg === 'init') isInit = true;
|
|
23
24
|
if (arg === 'setup') isSetup = true;
|
|
24
25
|
if (arg === '--logs') isLogs = true;
|
|
26
|
+
if (arg === '--run-all') isRunAll = true;
|
|
25
27
|
});
|
|
26
28
|
|
|
27
29
|
if (isLogs) {
|
|
@@ -38,6 +40,15 @@ if (isLogs) {
|
|
|
38
40
|
cwd: process.cwd(),
|
|
39
41
|
env: { ...process.env, ARCALITY_ROOT: PACKAGE_ROOT }
|
|
40
42
|
}).on('exit', code => process.exit(code || 0));
|
|
43
|
+
} else if (isRunAll) {
|
|
44
|
+
// CI/CD HEADLESS MODE: Descarga y ejecuta todas las misiones del proyecto desde la API
|
|
45
|
+
// Uso: arcality --run-all [--workers=4] [--tags=smoke,regression] [--project-id=xxx]
|
|
46
|
+
const mainScript = path.join(PACKAGE_ROOT, 'scripts', 'gen-and-run.mjs');
|
|
47
|
+
spawn('node', [mainScript, '--run-all', ...rawArgs.filter(a => a !== '--run-all')], {
|
|
48
|
+
stdio: 'inherit',
|
|
49
|
+
cwd: process.cwd(),
|
|
50
|
+
env: { ...process.env, ARCALITY_ROOT: PACKAGE_ROOT, CI: process.env.CI || 'true' }
|
|
51
|
+
}).on('exit', code => process.exit(code ?? 1));
|
|
41
52
|
} else if (isRun) {
|
|
42
53
|
// FORZAMOS EL MODO AGENTE: Esto saltará el menú interactivo en gen-and-run.mjs
|
|
43
54
|
const mainScript = path.join(PACKAGE_ROOT, 'scripts', 'gen-and-run.mjs');
|
package/package.json
CHANGED
package/playwright.config.js
CHANGED
|
@@ -3,19 +3,37 @@
|
|
|
3
3
|
const { defineConfig, devices } = require('@playwright/test');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
|
+
// ── Dynamic reporter: JUnit is added when running in CI (Azure DevOps, GitHub Actions) ──
|
|
7
|
+
// The JUnit XML file enables native test result publishing in your pipeline dashboard.
|
|
8
|
+
// In local/interactive mode, the JUnit reporter is skipped — only Arcality's HTML reporter runs.
|
|
9
|
+
const reportsDir = process.env.REPORTS_DIR || path.join(__dirname, 'tests-report');
|
|
10
|
+
const isCI = process.env.CI === 'true';
|
|
11
|
+
|
|
12
|
+
const reporters = [
|
|
13
|
+
['line'],
|
|
14
|
+
[path.join(__dirname, 'tests', '_helpers', 'ArcalityReporter.js'), { outputDir: reportsDir }],
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
if (isCI) {
|
|
18
|
+
reporters.push(['junit', { outputFile: path.join(reportsDir, 'results.xml') }]);
|
|
19
|
+
}
|
|
20
|
+
|
|
6
21
|
module.exports = defineConfig({
|
|
7
22
|
testDir: path.join(__dirname, 'tests', '_helpers'),
|
|
8
23
|
testMatch: ['agentic-runner.bundle.spec.js'],
|
|
9
|
-
reporter:
|
|
10
|
-
['line'],
|
|
11
|
-
[path.join(__dirname, 'tests', '_helpers', 'ArcalityReporter.js'),
|
|
12
|
-
{ outputDir: process.env.REPORTS_DIR || path.join(__dirname, 'tests-report') }]
|
|
13
|
-
],
|
|
24
|
+
reporter: reporters,
|
|
14
25
|
use: {
|
|
26
|
+
// ── Dynamic BASE_URL ──
|
|
27
|
+
// Priority: CLI --base-url flag → arcality.config baseUrl → BASE_URL env → default
|
|
28
|
+
// This allows pipelines to inject the target environment dynamically:
|
|
29
|
+
// arcality --run-all --base-url=https://staging.myapp.com
|
|
30
|
+
baseURL: process.env.BASE_URL || 'http://localhost:3000',
|
|
15
31
|
video: 'on',
|
|
16
32
|
screenshot: 'on',
|
|
17
33
|
trace: 'on',
|
|
18
34
|
colorScheme: 'dark',
|
|
35
|
+
// In CI: headless is enforced by Playwright automatically when CI=true
|
|
36
|
+
headless: isCI ? true : undefined,
|
|
19
37
|
contextOptions: {
|
|
20
38
|
reducedMotion: 'reduce',
|
|
21
39
|
},
|
|
@@ -32,3 +50,4 @@ module.exports = defineConfig({
|
|
|
32
50
|
},
|
|
33
51
|
],
|
|
34
52
|
});
|
|
53
|
+
|
package/scripts/gen-and-run.mjs
CHANGED
|
@@ -162,7 +162,6 @@ function setupEnvironment() {
|
|
|
162
162
|
|
|
163
163
|
function showBanner() {
|
|
164
164
|
console.clear();
|
|
165
|
-
const projectName = 'Arcality';
|
|
166
165
|
|
|
167
166
|
let version = 'unknown';
|
|
168
167
|
try {
|
|
@@ -170,19 +169,22 @@ function showBanner() {
|
|
|
170
169
|
version = pkg.version || 'unknown';
|
|
171
170
|
} catch { }
|
|
172
171
|
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
172
|
+
const sentinelAscii = `
|
|
173
|
+
█████ ██████ ██████ █████ ██ ██ ████████ ██ ██
|
|
174
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
175
|
+
███████ ██████ ██ ███████ ██ ██ ██ ████
|
|
176
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
177
|
+
██ ██ ██ ██ ██████ ██ ██ ███████ ██ ██ ██ `;
|
|
178
178
|
|
|
179
|
-
intro(chalk.
|
|
179
|
+
intro(chalk.gray(sentinelAscii));
|
|
180
180
|
|
|
181
181
|
note(
|
|
182
|
-
chalk.
|
|
182
|
+
chalk.gray('◉ Motor de Observación: ') + chalk.bold.white('EN LÍNEA') + '\n' +
|
|
183
|
+
chalk.gray('◉ Módulo de Recuperación de UI: ') + chalk.bold.white('ACTIVO') + '\n' +
|
|
184
|
+
chalk.gray('◉ Lógica Forense: ') + chalk.bold.white('ACTIVA') + '\n' +
|
|
183
185
|
chalk.gray('─'.repeat(50)) + '\n' +
|
|
184
|
-
chalk.
|
|
185
|
-
'
|
|
186
|
+
chalk.gray('Versión del Sistema: ') + chalk.white(`v${version}`),
|
|
187
|
+
'Estado Central de Arcality'
|
|
186
188
|
);
|
|
187
189
|
|
|
188
190
|
const { configName, baseUrl, techStack, configDir } = setupEnvironment();
|
|
@@ -190,30 +192,30 @@ function showBanner() {
|
|
|
190
192
|
// Show arcality.config status instead of multi-config switching
|
|
191
193
|
const hasConfig = !!projectConfig;
|
|
192
194
|
const configStatus = hasConfig
|
|
193
|
-
? chalk.green('
|
|
194
|
-
: chalk.red('
|
|
195
|
+
? chalk.green('◉ Sincronización Establecida')
|
|
196
|
+
: chalk.red('⚠ Desconectado — Ejecuta `arcality init`');
|
|
195
197
|
|
|
196
198
|
const { key: apiKeyVal, source: apiKeySource } = getApiKey();
|
|
197
199
|
const apiKeyDisplay = apiKeyVal
|
|
198
|
-
? chalk.green(maskApiKey(apiKeyVal)) + chalk.gray(`
|
|
199
|
-
: chalk.red('
|
|
200
|
+
? chalk.green(maskApiKey(apiKeyVal)) + chalk.gray(` [${apiKeySource}]`)
|
|
201
|
+
: chalk.red('NO VERIFICADO');
|
|
200
202
|
|
|
201
203
|
const infoLines = [
|
|
202
|
-
chalk.
|
|
203
|
-
chalk.
|
|
204
|
-
chalk.
|
|
204
|
+
chalk.gray('Nodo Objetivo: ') + chalk.white(process.env.NODE_ENV ?? 'development'),
|
|
205
|
+
chalk.gray('Token de Autenticación: ') + apiKeyDisplay,
|
|
206
|
+
chalk.gray('Stack: ') + chalk.white(techStack),
|
|
205
207
|
];
|
|
206
208
|
|
|
207
209
|
if (hasConfig) {
|
|
208
210
|
infoLines.splice(1, 0,
|
|
209
|
-
chalk.
|
|
210
|
-
chalk.
|
|
211
|
+
chalk.gray('Proyecto: ') + chalk.white(configName) + chalk.gray(` [${baseUrl}]`),
|
|
212
|
+
chalk.gray('ID de Identidad: ') + chalk.white(projectConfig.projectId || 'N/A'),
|
|
211
213
|
);
|
|
212
214
|
} else {
|
|
213
|
-
infoLines.push(chalk.
|
|
215
|
+
infoLines.push(chalk.gray('Configuración: ') + configStatus);
|
|
214
216
|
}
|
|
215
217
|
|
|
216
|
-
note(infoLines.join('\n'), '
|
|
218
|
+
note(infoLines.join('\n'), 'Telemetría del Objetivo');
|
|
217
219
|
}
|
|
218
220
|
|
|
219
221
|
const ARCALITY_BRAIN_URL = "https://api.anthropic.com/v1/messages";
|
|
@@ -496,21 +498,21 @@ async function main() {
|
|
|
496
498
|
const rootYamlFiles = fs.readdirSync(PROJECT_ROOT).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
|
|
497
499
|
|
|
498
500
|
const options = [
|
|
499
|
-
{ label: '
|
|
500
|
-
...(savedMissions.length ? [{ label: '
|
|
501
|
-
...(yamlOutputFiles.length ? [{ label: '
|
|
502
|
-
...(rootYamlFiles.length ? [{ label: '📂
|
|
503
|
-
{ label: '
|
|
504
|
-
{ label: '
|
|
501
|
+
{ label: '◉ Desplegar Arcality (Prompt)', value: 'agent' },
|
|
502
|
+
...(savedMissions.length ? [{ label: '⟳ Lanzar Misión Archivada (.yaml)', value: 'launch_saved' }] : []),
|
|
503
|
+
...(yamlOutputFiles.length ? [{ label: '⟶ Activar Modo GUÍA (Reusar YAML)', value: 'reuse_yaml' }] : []),
|
|
504
|
+
...(rootYamlFiles.length ? [{ label: '📂 Cargar Matriz de Patrones (YAML Raíz)', value: 'yaml_list' }] : []),
|
|
505
|
+
{ label: '⚙ Recalibrar Sistema (init)', value: 'reconfigure' },
|
|
506
|
+
{ label: '✕ Terminar Proceso', value: 'exit' }
|
|
505
507
|
];
|
|
506
508
|
|
|
507
509
|
const action = await select({
|
|
508
|
-
message: '
|
|
510
|
+
message: '¿Qué te gustaría hacer?',
|
|
509
511
|
options
|
|
510
512
|
});
|
|
511
513
|
|
|
512
514
|
if (isCancel(action) || action === 'exit') {
|
|
513
|
-
outro(chalk.cyan('
|
|
515
|
+
outro(chalk.cyan('¡Hasta luego!'));
|
|
514
516
|
break;
|
|
515
517
|
}
|
|
516
518
|
|
|
@@ -546,7 +548,7 @@ async function main() {
|
|
|
546
548
|
continue;
|
|
547
549
|
} else if (action === 'launch_saved') {
|
|
548
550
|
const sel = await select({
|
|
549
|
-
message: '
|
|
551
|
+
message: 'Elige la misión guardada:',
|
|
550
552
|
options: savedMissions.map(f => ({ label: f, value: f }))
|
|
551
553
|
});
|
|
552
554
|
if (isCancel(sel)) continue;
|
|
@@ -558,7 +560,7 @@ async function main() {
|
|
|
558
560
|
} else if (action === 'reuse_yaml') {
|
|
559
561
|
const yamlDir = getYamlOutputDir(projectConfig, ORIGINAL_CWD);
|
|
560
562
|
const sel = await select({
|
|
561
|
-
message: '
|
|
563
|
+
message: 'Elige el YAML a reusar:',
|
|
562
564
|
options: yamlOutputFiles.map(f => ({ label: f, value: f }))
|
|
563
565
|
});
|
|
564
566
|
if (isCancel(sel)) continue;
|
|
@@ -569,7 +571,7 @@ async function main() {
|
|
|
569
571
|
agentMode = true;
|
|
570
572
|
} else if (action === 'yaml_list') {
|
|
571
573
|
const sel = await select({
|
|
572
|
-
message: '
|
|
574
|
+
message: 'Elige el archivo YAML:',
|
|
573
575
|
options: rootYamlFiles.map(f => ({ label: f, value: f }))
|
|
574
576
|
});
|
|
575
577
|
if (isCancel(sel)) continue;
|
|
@@ -589,16 +591,16 @@ async function main() {
|
|
|
589
591
|
if (agentMode) {
|
|
590
592
|
if (!prompt || prompt.trim().length < 4) {
|
|
591
593
|
const p = await text({
|
|
592
|
-
message: chalk.cyan('🤖
|
|
593
|
-
placeholder: '
|
|
594
|
+
message: chalk.cyan('🤖 Misión para Arcality (Requerida):'),
|
|
595
|
+
placeholder: 'Ej., Llena el formulario de registro con datos aleatorios',
|
|
594
596
|
validate: v => {
|
|
595
|
-
if (!v || !v.trim()) return '
|
|
596
|
-
if (v.trim().length < 4) return '
|
|
597
|
+
if (!v || !v.trim()) return 'La misión es requerida para iniciar.';
|
|
598
|
+
if (v.trim().length < 4) return 'La misión debe ser más descriptiva (mínimo 4 caracteres).';
|
|
597
599
|
}
|
|
598
600
|
});
|
|
599
601
|
|
|
600
602
|
if (isCancel(p)) {
|
|
601
|
-
cancel('
|
|
603
|
+
cancel('Misión cancelada. Regresando al menú principal...');
|
|
602
604
|
agentMode = false;
|
|
603
605
|
prompt = "";
|
|
604
606
|
skipMenu = false;
|
|
@@ -611,19 +613,19 @@ async function main() {
|
|
|
611
613
|
const knownBaseUrl = projectConfig?.project?.baseUrl || process.env.BASE_URL;
|
|
612
614
|
const promptMsg = knownBaseUrl
|
|
613
615
|
? `🌐 Initial navigation path (relative to ${knownBaseUrl}):`
|
|
614
|
-
: '🌐
|
|
616
|
+
: '🌐 URL Completa de Navegación (ej., http://localhost:3000/):';
|
|
615
617
|
|
|
616
618
|
const d = await text({
|
|
617
619
|
message: chalk.cyan(promptMsg),
|
|
618
620
|
placeholder: knownBaseUrl ? '/' : 'http://localhost:3000/',
|
|
619
621
|
validate: v => {
|
|
620
|
-
if (!knownBaseUrl && !v.startsWith('http')) return '
|
|
622
|
+
if (!knownBaseUrl && !v.startsWith('http')) return 'Por favor provee una URL completa con http:// o https://';
|
|
621
623
|
return undefined;
|
|
622
624
|
}
|
|
623
625
|
});
|
|
624
626
|
|
|
625
627
|
if (isCancel(d)) {
|
|
626
|
-
cancel('
|
|
628
|
+
cancel('Misión abortada. Regresando al menú...');
|
|
627
629
|
agentMode = false;
|
|
628
630
|
prompt = "";
|
|
629
631
|
skipMenu = false;
|
|
@@ -653,12 +655,12 @@ async function main() {
|
|
|
653
655
|
}
|
|
654
656
|
if (!validation.valid) {
|
|
655
657
|
const errorMessages = {
|
|
656
|
-
'no_api_key': '🔑 API Key
|
|
657
|
-
'invalid_format': '🔑
|
|
658
|
-
'invalid_api_key': '🔑 API Key
|
|
659
|
-
'plan_expired': '💳
|
|
658
|
+
'no_api_key': '🔑 API Key no encontrada.\n Configúrala vía `arcality init` o en .env (ARCALITY_API_KEY)',
|
|
659
|
+
'invalid_format': '🔑 API Key inválida. Debe comenzar con "arc_"',
|
|
660
|
+
'invalid_api_key': '🔑 API Key no reconocida por el servidor',
|
|
661
|
+
'plan_expired': '💳 Tu plan ha expirado'
|
|
660
662
|
};
|
|
661
|
-
note(chalk.red(errorMessages[validation.error] || `Error: ${validation.error}`), '❌
|
|
663
|
+
note(chalk.red(errorMessages[validation.error] || `Error: ${validation.error}`), '❌ Autenticación');
|
|
662
664
|
agentMode = false;
|
|
663
665
|
prompt = "";
|
|
664
666
|
skipMenu = false;
|
|
@@ -666,15 +668,15 @@ async function main() {
|
|
|
666
668
|
}
|
|
667
669
|
|
|
668
670
|
// Show plan info
|
|
669
|
-
const modeLabel = validation.mode === 'mock' ? chalk.gray('
|
|
671
|
+
const modeLabel = validation.mode === 'mock' ? chalk.gray('[sandbox]') : chalk.cyanBright('[live-net]');
|
|
670
672
|
const dailyLimitDisplay = validation.daily_limit === -1 ? '∞' : (validation.daily_limit || 35);
|
|
671
673
|
const remainingDisplay = validation.remaining >= 999999 ? '∞' : validation.remaining;
|
|
672
674
|
note(
|
|
673
|
-
chalk.
|
|
674
|
-
chalk.
|
|
675
|
-
chalk.
|
|
676
|
-
chalk.
|
|
677
|
-
'Arcality
|
|
675
|
+
chalk.gray('Conexión: ') + chalk.cyanBright('SECURE ') + modeLabel + '\n' +
|
|
676
|
+
chalk.gray('Autorización: ') + chalk.white(validation.plan || 'internal') + '\n' +
|
|
677
|
+
chalk.gray('Misiones: ') + chalk.white(`${validation.daily_used || 0}/${dailyLimitDisplay}`) + '\n' +
|
|
678
|
+
chalk.gray('Reservas: ') + chalk.white(remainingDisplay),
|
|
679
|
+
'Enlace Arcality'
|
|
678
680
|
);
|
|
679
681
|
|
|
680
682
|
// --- RESOLVE PROJECT ID ---
|
|
@@ -697,21 +699,21 @@ async function main() {
|
|
|
697
699
|
const projects = data.projects || [];
|
|
698
700
|
|
|
699
701
|
const projOptions = projects.map(p => ({ label: p.name || p.Name, value: p.id || p.Id }));
|
|
700
|
-
projOptions.push({ label: '➕
|
|
702
|
+
projOptions.push({ label: '➕ Crear nuevo proyecto', value: 'new' });
|
|
701
703
|
|
|
702
704
|
const projSelection = await select({
|
|
703
|
-
message: chalk.cyan('
|
|
705
|
+
message: chalk.cyan('Elige un proyecto para esta misión:'),
|
|
704
706
|
options: projOptions
|
|
705
707
|
});
|
|
706
708
|
|
|
707
709
|
if (isCancel(projSelection)) {
|
|
708
|
-
cancel('
|
|
710
|
+
cancel('Misión abortada.');
|
|
709
711
|
agentMode = false; prompt = ""; skipMenu = false; continue;
|
|
710
712
|
}
|
|
711
713
|
|
|
712
714
|
if (projSelection === 'new') {
|
|
713
|
-
const newName = await text({ message: '
|
|
714
|
-
if (isCancel(newName)) { cancel('
|
|
715
|
+
const newName = await text({ message: 'Nombre del proyecto:', validate: v => !v ? 'Requerido' : undefined });
|
|
716
|
+
if (isCancel(newName)) { cancel('Abortado'); agentMode = false; prompt = ""; skipMenu = false; continue; }
|
|
715
717
|
|
|
716
718
|
const createRes = await fetch(`${apiBase}/api/v1/projects`, {
|
|
717
719
|
method: 'POST',
|
|
@@ -727,9 +729,9 @@ async function main() {
|
|
|
727
729
|
if (createRes.ok) {
|
|
728
730
|
const created = await createRes.json();
|
|
729
731
|
selectedProjectId = created.id || created.Id;
|
|
730
|
-
note(chalk.green(`✅
|
|
732
|
+
note(chalk.green(`✅ Proyecto creado: ${newName}`));
|
|
731
733
|
} else {
|
|
732
|
-
note(chalk.red(`❌
|
|
734
|
+
note(chalk.red(`❌ Falló al crear proyecto.`));
|
|
733
735
|
}
|
|
734
736
|
} else {
|
|
735
737
|
selectedProjectId = projSelection;
|
|
@@ -750,10 +752,10 @@ async function main() {
|
|
|
750
752
|
const mission = await requestMission(prompt, discoverPath || '/');
|
|
751
753
|
if (!mission.allowed) {
|
|
752
754
|
note(
|
|
753
|
-
chalk.red(`❌ ${mission.error === 'mission_limit_exceeded' ? '
|
|
755
|
+
chalk.red(`❌ ${mission.error === 'mission_limit_exceeded' ? 'Límite diario de misiones alcanzado' : mission.error}`) + '\n' +
|
|
754
756
|
chalk.yellow(`📊 Used: ${mission.daily_used}/${mission.daily_limit}`) + '\n' +
|
|
755
757
|
chalk.gray(`🔄 Resets: ${mission.resets_at ? new Date(mission.resets_at).toLocaleString() : 'tomorrow'}`),
|
|
756
|
-
'⚠️
|
|
758
|
+
'⚠️ Límite Alcanzado'
|
|
757
759
|
);
|
|
758
760
|
agentMode = false;
|
|
759
761
|
prompt = "";
|
|
@@ -763,9 +765,9 @@ async function main() {
|
|
|
763
765
|
|
|
764
766
|
const s = spinner();
|
|
765
767
|
if (!process.argv.includes('--debug')) {
|
|
766
|
-
s.start(
|
|
768
|
+
s.start(`◉ [ARCALITY INICIADO] Ejecutando directiva: "${prompt}" ${chalk.gray(`(${mission.daily_used}/${mission.daily_limit})`)}`);
|
|
767
769
|
} else {
|
|
768
|
-
console.log(chalk.cyan(`\n
|
|
770
|
+
console.log(chalk.cyan(`\n◉ [ARCALITY INICIADO] Ejecutando directiva: "${prompt}" ${chalk.gray(`(${mission.daily_used}/${mission.daily_limit})`)}`));
|
|
769
771
|
}
|
|
770
772
|
|
|
771
773
|
const finalProjectId = process.env.ARCALITY_PROJECT_ID || selectedProjectId;
|
|
@@ -821,9 +823,9 @@ async function main() {
|
|
|
821
823
|
}
|
|
822
824
|
userCanceledMission = true;
|
|
823
825
|
|
|
824
|
-
console.log(chalk.yellow('\n\n⚠️ [ARCALITY]
|
|
826
|
+
console.log(chalk.yellow('\n\n⚠️ [ARCALITY] Directiva abortada por el operador.'));
|
|
825
827
|
if (!process.argv.includes('--debug')) {
|
|
826
|
-
try { s.stop(chalk.yellow('⚠️
|
|
828
|
+
try { s.stop(chalk.yellow('⚠️ Directiva abortada.')); } catch { /* ignore */ }
|
|
827
829
|
}
|
|
828
830
|
|
|
829
831
|
// Terminar el proceso hijo de Playwright directamente (Windows-safe)
|
|
@@ -842,7 +844,7 @@ async function main() {
|
|
|
842
844
|
if (ctxDir && fs.existsSync(ctxDir)) {
|
|
843
845
|
const cancelLog = {
|
|
844
846
|
prompt,
|
|
845
|
-
history: ['[
|
|
847
|
+
history: ['[ABORTO DEL OPERADOR] La directiva fue detenida manualmente antes de completarse.'],
|
|
846
848
|
steps: 0,
|
|
847
849
|
success: false,
|
|
848
850
|
error: true,
|
|
@@ -850,7 +852,7 @@ async function main() {
|
|
|
850
852
|
canceled_by_user: true,
|
|
851
853
|
timestamp: new Date().toISOString()
|
|
852
854
|
};
|
|
853
|
-
fs.writeFileSync(path.join(ctxDir, `
|
|
855
|
+
fs.writeFileSync(path.join(ctxDir, `arcality-log-${Date.now()}.json`), JSON.stringify(cancelLog, null, 2));
|
|
854
856
|
}
|
|
855
857
|
} catch { /* silencioso */ }
|
|
856
858
|
|
|
@@ -958,21 +960,34 @@ async function main() {
|
|
|
958
960
|
console.log(chalk.gray(`[DEBUG] Using playwright CLI: ${playwrightCli}`));
|
|
959
961
|
}
|
|
960
962
|
} catch (e) {
|
|
961
|
-
console.error(chalk.red('\n[FATAL] Playwright
|
|
963
|
+
console.error(chalk.red('\n[FATAL] Núcleo de Playwright no encontrado. Asegúrate de que @playwright/test esté instalado.'));
|
|
962
964
|
console.error(chalk.red(e.message));
|
|
963
|
-
console.error(chalk.yellow('\
|
|
965
|
+
console.error(chalk.yellow('\nConsejo: Ejecuta `npm install @playwright/test` en tu directorio de proyecto.'));
|
|
964
966
|
_missionActive = false;
|
|
965
967
|
process.exit = _origProcessExit;
|
|
966
968
|
_origProcessExit(1);
|
|
967
969
|
}
|
|
968
970
|
|
|
969
|
-
const postProcessSigint = () => console.log(chalk.yellow('\n⏳ Finalizando
|
|
971
|
+
const postProcessSigint = () => console.log(chalk.yellow('\n⏳ Finalizando reporte de telemetría, por favor espera...'));
|
|
970
972
|
let missionResult = null;
|
|
971
973
|
let finalFailReason = null;
|
|
972
974
|
let finalUsageData = undefined;
|
|
973
975
|
let finalReportUrl = null;
|
|
974
976
|
let finalFailReasoning = null;
|
|
975
977
|
|
|
978
|
+
// ── Auto-compilar el runner antes de ejecutar Playwright para asegurar que los cambios de rama/merge estén aplicados ──
|
|
979
|
+
try {
|
|
980
|
+
if (!process.argv.includes('--debug')) {
|
|
981
|
+
s.message('⚙️ Compilando agente de pruebas (Arcality Runner)...');
|
|
982
|
+
} else {
|
|
983
|
+
console.log(chalk.cyan('⚙️ Compilando agente de pruebas (Arcality Runner)...'));
|
|
984
|
+
}
|
|
985
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
986
|
+
await run(npmCmd, ['run', 'build:runner'], { cwd: PROJECT_ROOT });
|
|
987
|
+
} catch (err) {
|
|
988
|
+
console.warn(chalk.yellow(`\n⚠️ Advertencia: No se pudo auto-compilar el runner: ${err.message}`));
|
|
989
|
+
}
|
|
990
|
+
|
|
976
991
|
try {
|
|
977
992
|
// No test file path is passed — playwright.config.js uses testMatch:'agentic-runner.spec.ts'
|
|
978
993
|
// This eliminates the Windows regex path issue permanently on any user's machine.
|
|
@@ -1005,26 +1020,26 @@ async function main() {
|
|
|
1005
1020
|
}
|
|
1006
1021
|
|
|
1007
1022
|
missionResult = 'success';
|
|
1008
|
-
if (!process.argv.includes('--debug')) s.stop(chalk.green('✅
|
|
1009
|
-
else console.log(chalk.green('✅
|
|
1023
|
+
if (!process.argv.includes('--debug')) s.stop(chalk.green('✅ Misión completada exitosamente.'));
|
|
1024
|
+
else console.log(chalk.green('✅ Misión completada exitosamente.'));
|
|
1010
1025
|
|
|
1011
1026
|
// ── Ping Project ──
|
|
1012
1027
|
await pingProject(finalProjectId, process.env.ARCALITY_API_KEY);
|
|
1013
1028
|
|
|
1014
1029
|
// ── Save Mission YAML ──
|
|
1015
1030
|
const saveDecision = await select({
|
|
1016
|
-
message: '
|
|
1031
|
+
message: '¿Quieres guardar esta misión para ejecutarla de nuevo más tarde?',
|
|
1017
1032
|
options: [
|
|
1018
|
-
{ label: '✅
|
|
1019
|
-
{ label: '❌ No,
|
|
1033
|
+
{ label: '✅ Sí, guardar como YAML', value: 'yes' },
|
|
1034
|
+
{ label: '❌ No, gracias', value: 'no' }
|
|
1020
1035
|
]
|
|
1021
1036
|
});
|
|
1022
1037
|
|
|
1023
1038
|
if (saveDecision === 'yes') {
|
|
1024
1039
|
const name = await text({
|
|
1025
|
-
message: '
|
|
1040
|
+
message: 'Nombre de la misión (ej., login_valido):',
|
|
1026
1041
|
placeholder: 'my_mission',
|
|
1027
|
-
validate: v => v.length < 3 ? '
|
|
1042
|
+
validate: v => v.length < 3 ? 'Nombre muy corto' : undefined
|
|
1028
1043
|
});
|
|
1029
1044
|
|
|
1030
1045
|
if (!isCancel(name)) {
|
|
@@ -1050,9 +1065,9 @@ async function main() {
|
|
|
1050
1065
|
const yamlOutputDir = ensureYamlOutputDir(projectConfig, ORIGINAL_CWD);
|
|
1051
1066
|
const yamlPathOutput = path.join(yamlOutputDir, `${safeName}.yaml`);
|
|
1052
1067
|
fs.writeFileSync(yamlPathOutput, yamlData);
|
|
1053
|
-
note(chalk.green(`✅
|
|
1068
|
+
note(chalk.green(`✅ Misión guardada en: ${yamlPathOutput}`), 'Persistencia Arcality');
|
|
1054
1069
|
} else {
|
|
1055
|
-
note(chalk.green(`✅
|
|
1070
|
+
note(chalk.green(`✅ Misión guardada en: ${yamlPathMissions}`), 'Persistencia Arcality');
|
|
1056
1071
|
}
|
|
1057
1072
|
}
|
|
1058
1073
|
}
|
|
@@ -1061,12 +1076,12 @@ async function main() {
|
|
|
1061
1076
|
if (userCanceledMission) {
|
|
1062
1077
|
missionResult = 'canceled';
|
|
1063
1078
|
finalFailReason = 'user_canceled';
|
|
1064
|
-
console.log(chalk.yellow('\n⚠️
|
|
1079
|
+
console.log(chalk.yellow('\n⚠️ Directiva abortada — generando reporte de evidencia...'));
|
|
1065
1080
|
} else {
|
|
1066
1081
|
missionResult = 'failed';
|
|
1067
1082
|
finalFailReason = 'timeout_or_crash';
|
|
1068
|
-
s.stop(chalk.red(`❌
|
|
1069
|
-
console.error(chalk.red(`\
|
|
1083
|
+
s.stop(chalk.red(`❌ Arcality no pudo completar la directiva.`));
|
|
1084
|
+
console.error(chalk.red(`\nDetalles del Error: ${e.message}`));
|
|
1070
1085
|
if (e.stack && process.argv.includes('--debug')) {
|
|
1071
1086
|
console.error(chalk.gray(e.stack));
|
|
1072
1087
|
}
|
|
@@ -1081,7 +1096,7 @@ async function main() {
|
|
|
1081
1096
|
if (userCanceledMission) {
|
|
1082
1097
|
missionResult = 'canceled';
|
|
1083
1098
|
finalFailReason = 'user_canceled';
|
|
1084
|
-
finalFailReasoning = 'La misión fue detenida manualmente por el
|
|
1099
|
+
finalFailReasoning = 'La misión fue detenida manualmente por el operador (Ctrl+C). Esto no es una falla técnica del sistema.';
|
|
1085
1100
|
await new Promise(r => setTimeout(r, 800));
|
|
1086
1101
|
}
|
|
1087
1102
|
|
|
@@ -1090,9 +1105,9 @@ async function main() {
|
|
|
1090
1105
|
|
|
1091
1106
|
const sRep = spinner();
|
|
1092
1107
|
if (!process.argv.includes('--debug')) {
|
|
1093
|
-
sRep.start('📊
|
|
1108
|
+
sRep.start('📊 Generando reporte...');
|
|
1094
1109
|
} else {
|
|
1095
|
-
console.log('📊
|
|
1110
|
+
console.log('📊 Generando reporte...');
|
|
1096
1111
|
}
|
|
1097
1112
|
|
|
1098
1113
|
await run("node", [path.join(PROJECT_ROOT, "scripts/rebrand-report.mjs")]).catch(() => { });
|
|
@@ -1107,7 +1122,7 @@ async function main() {
|
|
|
1107
1122
|
const ctxDir = process.env.CONTEXT_DIR;
|
|
1108
1123
|
if (fs.existsSync(ctxDir)) {
|
|
1109
1124
|
const files = fs.readdirSync(ctxDir);
|
|
1110
|
-
const logFiles = files.filter(f => f.startsWith('
|
|
1125
|
+
const logFiles = files.filter(f => f.startsWith('arcality-log-') && f.endsWith('.json')).sort();
|
|
1111
1126
|
if (logFiles.length > 0) {
|
|
1112
1127
|
const lastLog = path.join(ctxDir, logFiles[logFiles.length - 1]);
|
|
1113
1128
|
const logData = JSON.parse(fs.readFileSync(lastLog, 'utf8'));
|
|
@@ -1134,7 +1149,7 @@ async function main() {
|
|
|
1134
1149
|
|
|
1135
1150
|
if (sasData && actualSasUrl && actualBasePath) {
|
|
1136
1151
|
if (!process.argv.includes('--debug')) {
|
|
1137
|
-
sRep.start('☁️
|
|
1152
|
+
sRep.start('☁️ Subiendo evidencia a Azure...');
|
|
1138
1153
|
}
|
|
1139
1154
|
process.removeAllListeners('SIGINT');
|
|
1140
1155
|
process.removeAllListeners('SIGTERM');
|
|
@@ -1148,14 +1163,14 @@ async function main() {
|
|
|
1148
1163
|
finalReportUrl = `${parsedSas.origin}${parsedSas.pathname}/${actualBasePath}/index.html`;
|
|
1149
1164
|
} catch(e) {}
|
|
1150
1165
|
|
|
1151
|
-
if (!process.argv.includes('--debug')) sRep.stop(chalk.green('✅
|
|
1166
|
+
if (!process.argv.includes('--debug')) sRep.stop(chalk.green('✅ Evidencia subida exitosamente.'));
|
|
1152
1167
|
}
|
|
1153
1168
|
} catch (e) {
|
|
1154
1169
|
if (process.argv.includes('--debug')) console.log(chalk.yellow(`⚠️ Could not upload evidence: ${e.message}`));
|
|
1155
|
-
if (!process.argv.includes('--debug')) sRep.stop(chalk.yellow('⚠️
|
|
1170
|
+
if (!process.argv.includes('--debug')) sRep.stop(chalk.yellow('⚠️ Fallo al subir evidencia.'));
|
|
1156
1171
|
}
|
|
1157
1172
|
} else {
|
|
1158
|
-
console.log(chalk.gray(' >>
|
|
1173
|
+
console.log(chalk.gray(' >> Saltando subida de evidencia (la misión fue cancelada).'));
|
|
1159
1174
|
}
|
|
1160
1175
|
|
|
1161
1176
|
// ── End Mission — SIEMPRE se llama, independientemente de si la evidencia subió ──
|
|
@@ -1179,14 +1194,14 @@ async function main() {
|
|
|
1179
1194
|
}
|
|
1180
1195
|
|
|
1181
1196
|
if (fs.existsSync(arcalityPath)) {
|
|
1182
|
-
console.log(chalk.blue(`🌐
|
|
1197
|
+
console.log(chalk.blue(`🌐 Abriendo Reporte de Arcality: ${arcalityPath}`));
|
|
1183
1198
|
if (process.platform === "win32") {
|
|
1184
1199
|
exec(`start "" "${arcalityPath}"`);
|
|
1185
1200
|
} else {
|
|
1186
1201
|
exec(`open "${arcalityPath}"`);
|
|
1187
1202
|
}
|
|
1188
1203
|
} else {
|
|
1189
|
-
console.log(chalk.yellow(`⚠️
|
|
1204
|
+
console.log(chalk.yellow(`⚠️ Reporte no generado (la prueba pudo haber fallado antes de completarse).`));
|
|
1190
1205
|
}
|
|
1191
1206
|
|
|
1192
1207
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
@@ -1197,10 +1212,14 @@ async function main() {
|
|
|
1197
1212
|
process.env.ARCALITY_API_KEY
|
|
1198
1213
|
);
|
|
1199
1214
|
|
|
1215
|
+
// Limpiar handlers del post-proceso ANTES de esperar Enter, ya que toda la telemetría y reportes ya finalizaron
|
|
1216
|
+
process.removeListener('SIGINT', postProcessSigint);
|
|
1217
|
+
process.removeListener('SIGTERM', postProcessSigint);
|
|
1218
|
+
|
|
1200
1219
|
// Esperar Enter para regresar al menú
|
|
1201
1220
|
// Usamos escucha directa de stdin (raw mode) porque @clack/text
|
|
1202
1221
|
// no procesa Enter correctamente cuando setRawMode(true) está activo.
|
|
1203
|
-
console.log(chalk.gray('\n
|
|
1222
|
+
console.log(chalk.gray('\n Presiona Enter para regresar al menú...'));
|
|
1204
1223
|
await new Promise(resolve => {
|
|
1205
1224
|
const onKey = (key) => {
|
|
1206
1225
|
const keyStr = String(key);
|
|
@@ -1211,10 +1230,17 @@ async function main() {
|
|
|
1211
1230
|
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
1212
1231
|
process.stdin.setRawMode(false);
|
|
1213
1232
|
}
|
|
1214
|
-
|
|
1233
|
+
// FIX: pausar stdin si vamos a salir para que el event loop no se quede colgado.
|
|
1234
|
+
// Si volvemos al menú, no lo pausamos (para evitar el bug de exit en clack).
|
|
1235
|
+
if (skipMenu) {
|
|
1236
|
+
process.stdin.pause();
|
|
1237
|
+
}
|
|
1215
1238
|
|
|
1216
|
-
if (keyStr === '\u0003')
|
|
1217
|
-
|
|
1239
|
+
if (keyStr === '\u0003') {
|
|
1240
|
+
process.exit(0); // Ctrl+C = salir inmediatamente
|
|
1241
|
+
} else {
|
|
1242
|
+
resolve();
|
|
1243
|
+
}
|
|
1218
1244
|
}
|
|
1219
1245
|
};
|
|
1220
1246
|
|
|
@@ -1232,17 +1258,13 @@ async function main() {
|
|
|
1232
1258
|
});
|
|
1233
1259
|
|
|
1234
1260
|
if (skipMenu) {
|
|
1235
|
-
outro(chalk.cyan('
|
|
1261
|
+
outro(chalk.cyan('¡Misión terminada! Hasta luego.'));
|
|
1236
1262
|
return;
|
|
1237
1263
|
}
|
|
1238
1264
|
|
|
1239
1265
|
agentMode = false;
|
|
1240
1266
|
prompt = "";
|
|
1241
1267
|
firstRun = false;
|
|
1242
|
-
|
|
1243
|
-
// Limpiar handlers del post-proceso
|
|
1244
|
-
process.removeListener('SIGINT', postProcessSigint);
|
|
1245
|
-
process.removeListener('SIGTERM', postProcessSigint);
|
|
1246
1268
|
}
|
|
1247
1269
|
}
|
|
1248
1270
|
firstRun = false;
|