@arcadialdev/arcality 2.6.0 → 2.6.2
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/package.json
CHANGED
package/scripts/gen-and-run.mjs
CHANGED
|
@@ -740,19 +740,51 @@ async function main() {
|
|
|
740
740
|
|
|
741
741
|
const finalProjectId = process.env.ARCALITY_PROJECT_ID || selectedProjectId;
|
|
742
742
|
|
|
743
|
-
// ──
|
|
744
|
-
//
|
|
745
|
-
//
|
|
746
|
-
|
|
743
|
+
// ── Cancelación Manual (Ctrl+C) — Estrategia bulletproof ──
|
|
744
|
+
// El problema: @clack/prompts registra su propio SIGINT handler cada tick del spinner,
|
|
745
|
+
// que llama a process.exit(0) directamente. removeAllListeners() no es suficiente porque
|
|
746
|
+
// Clack lo re-registra en cada frame de animación.
|
|
747
|
+
//
|
|
748
|
+
// La solución: interceptar process.exit() antes de que Clack lo llame,
|
|
749
|
+
// y usar prependListener para que NUESTRO handler siempre se ejecute primero.
|
|
750
|
+
|
|
751
|
+
let activeChildProcess = null;
|
|
747
752
|
let userCanceledMission = false;
|
|
748
|
-
|
|
749
|
-
|
|
753
|
+
let sigintCount = 0;
|
|
754
|
+
let _missionActive = true; // Mientras true, bloqueamos el process.exit de Clack
|
|
755
|
+
|
|
756
|
+
// Guardar el process.exit original y reemplazarlo temporalmente
|
|
757
|
+
const _origProcessExit = process.exit.bind(process);
|
|
758
|
+
process.exit = (code) => {
|
|
759
|
+
if (_missionActive) {
|
|
760
|
+
// Clack (u otra lib) está intentando matar el proceso — lo bloqueamos.
|
|
761
|
+
// Nuestro cancelHandler ya habrá sido invocado vía prependListener.
|
|
762
|
+
// El proceso se cerrará naturalmente cuando el child process termine.
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
_origProcessExit(code);
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
const cancelHandler = () => {
|
|
769
|
+
sigintCount++;
|
|
770
|
+
if (sigintCount > 3) {
|
|
771
|
+
console.log(chalk.red('\n🛑 Forzando salida inmediata...'));
|
|
772
|
+
_missionActive = false;
|
|
773
|
+
_origProcessExit(1);
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (userCanceledMission) {
|
|
778
|
+
console.log(chalk.yellow('\n⏳ Cancelación en progreso, esperando reporte... (Ctrl+C x4 para forzar cierre)'));
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
750
781
|
userCanceledMission = true;
|
|
751
782
|
|
|
752
783
|
console.log(chalk.yellow('\n\n⚠️ [ARCALITY] Misión cancelada por el usuario.'));
|
|
753
|
-
if (!process.argv.includes('--debug'))
|
|
784
|
+
if (!process.argv.includes('--debug')) {
|
|
785
|
+
try { s.stop(chalk.yellow('⚠️ Misión cancelada.')); } catch { /* ignore */ }
|
|
786
|
+
}
|
|
754
787
|
|
|
755
|
-
// 1. Escribir un agent-log de cancelación para que el reporte lo refleje
|
|
756
788
|
try {
|
|
757
789
|
const ctxDir = process.env.CONTEXT_DIR;
|
|
758
790
|
if (ctxDir && fs.existsSync(ctxDir)) {
|
|
@@ -770,28 +802,15 @@ async function main() {
|
|
|
770
802
|
}
|
|
771
803
|
} catch { /* silencioso */ }
|
|
772
804
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
// 3. Matar el proceso hijo de Playwright — hacerlo AL FINAL para dar tiempo
|
|
777
|
-
// a que el agente-log y endMission terminen antes del kill.
|
|
778
|
-
// NO llamar process.exit() aquí: el finally del try/catch principal
|
|
779
|
-
// se encarga de generar el reporte visual y abrirlo en el browser.
|
|
780
|
-
if (activeChildProcess) {
|
|
781
|
-
try {
|
|
782
|
-
if (process.platform === 'win32') {
|
|
783
|
-
spawn('taskkill', ['/pid', activeChildProcess.pid, '/f', '/t'], { stdio: 'ignore', windowsHide: true });
|
|
784
|
-
} else {
|
|
785
|
-
activeChildProcess.kill('SIGTERM');
|
|
786
|
-
}
|
|
787
|
-
} catch { /* silencioso */ }
|
|
788
|
-
}
|
|
789
|
-
// El bloque finally del try/catch de run() tomará el control
|
|
790
|
-
// a partir de aquí (el proceso hijo ya terminó → run() rechaza → catch → finally).
|
|
805
|
+
console.log(chalk.gray(' >> Esperando que Playwright cierre y genere el reporte...'));
|
|
806
|
+
// Playwright también recibe el SIGINT nativamente desde la terminal Windows
|
|
807
|
+
// y cerrará su proceso limpiamente, lo que resolverá la Promise del run() → finally
|
|
791
808
|
};
|
|
792
809
|
|
|
793
|
-
|
|
794
|
-
process.
|
|
810
|
+
// prependListener asegura que nuestro handler se ejecuta ANTES que el de Clack
|
|
811
|
+
process.prependListener('SIGINT', cancelHandler);
|
|
812
|
+
process.prependListener('SIGTERM', cancelHandler);
|
|
813
|
+
|
|
795
814
|
|
|
796
815
|
// Build env for the runner — merge arcality.config values.
|
|
797
816
|
// ARCALITY_API_URL must be explicit — it comes from the library's internal config,
|
|
@@ -831,23 +850,55 @@ async function main() {
|
|
|
831
850
|
const testFile = path.join(PROJECT_ROOT, 'tests', '_helpers', 'agentic-runner.spec.ts');
|
|
832
851
|
const configFile = path.join(PROJECT_ROOT, 'playwright.config.js');
|
|
833
852
|
|
|
834
|
-
// Resolve playwright CLI
|
|
835
|
-
//
|
|
853
|
+
// Resolve playwright CLI — CRITICAL: must use the same @playwright/test instance
|
|
854
|
+
// that the agentic-runner.bundle.spec.js was compiled against. If the user's project
|
|
855
|
+
// has its own @playwright/test, we must use THAT one to avoid the singleton conflict
|
|
856
|
+
// ("Playwright Test did not expect test() to be called here").
|
|
857
|
+
//
|
|
858
|
+
// Resolution order:
|
|
859
|
+
// 1. ORIGINAL_CWD (user's project) — preferred, avoids singleton mismatch
|
|
860
|
+
// 2. PROJECT_ROOT (arcality package) — fallback for clean installs
|
|
836
861
|
let playwrightCli;
|
|
837
862
|
try {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
863
|
+
const searchPaths = [ORIGINAL_CWD, PROJECT_ROOT];
|
|
864
|
+
let testRunnerPath = null;
|
|
865
|
+
let resolveError = null;
|
|
866
|
+
|
|
867
|
+
for (const searchPath of searchPaths) {
|
|
868
|
+
try {
|
|
869
|
+
testRunnerPath = require.resolve('@playwright/test', { paths: [searchPath] });
|
|
870
|
+
break;
|
|
871
|
+
} catch (e) {
|
|
872
|
+
resolveError = e;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
if (!testRunnerPath) throw resolveError;
|
|
877
|
+
|
|
878
|
+
// Navigate from @playwright/test/index.js → playwright/cli.js (sibling package)
|
|
841
879
|
playwrightCli = path.join(testRunnerPath, '..', '..', '..', 'playwright', 'cli.js');
|
|
880
|
+
|
|
881
|
+
// Some versions nest it differently — try alternate location
|
|
842
882
|
if (!fs.existsSync(playwrightCli)) {
|
|
843
|
-
|
|
883
|
+
const alt = path.join(testRunnerPath, '..', '..', 'playwright', 'cli.js');
|
|
884
|
+
if (fs.existsSync(alt)) playwrightCli = alt;
|
|
885
|
+
else throw new Error(`Playwright CLI not found. Tried:\n ${playwrightCli}\n ${alt}`);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if (process.argv.includes('--debug')) {
|
|
889
|
+
console.log(chalk.gray(`[DEBUG] Using playwright CLI: ${playwrightCli}`));
|
|
844
890
|
}
|
|
845
891
|
} catch (e) {
|
|
846
|
-
console.error(chalk.red('\n[FATAL] Playwright core not found
|
|
892
|
+
console.error(chalk.red('\n[FATAL] Playwright core not found. Make sure @playwright/test is installed.'));
|
|
847
893
|
console.error(chalk.red(e.message));
|
|
894
|
+
console.error(chalk.yellow('\nTip: Run `npm install @playwright/test` in your project directory.'));
|
|
895
|
+
_missionActive = false;
|
|
896
|
+
process.exit = _origProcessExit;
|
|
848
897
|
process.exit(1);
|
|
849
898
|
}
|
|
850
899
|
|
|
900
|
+
|
|
901
|
+
const postProcessSigint = () => console.log(chalk.yellow('\n⏳ Finalizando tareas de reporte, por favor espera...'));
|
|
851
902
|
let missionResult = null;
|
|
852
903
|
let finalFailReason = null;
|
|
853
904
|
let finalUsageData = undefined;
|
|
@@ -869,9 +920,10 @@ async function main() {
|
|
|
869
920
|
onSpawn: (child) => { activeChildProcess = child; } // ← Capturar referencia para cancelHandler
|
|
870
921
|
});
|
|
871
922
|
|
|
872
|
-
|
|
873
|
-
process.
|
|
874
|
-
process.
|
|
923
|
+
process.removeAllListeners('SIGINT');
|
|
924
|
+
process.removeAllListeners('SIGTERM');
|
|
925
|
+
process.on('SIGINT', postProcessSigint);
|
|
926
|
+
process.on('SIGTERM', postProcessSigint);
|
|
875
927
|
|
|
876
928
|
if (userCanceledMission) {
|
|
877
929
|
missionResult = 'canceled';
|
|
@@ -932,15 +984,10 @@ async function main() {
|
|
|
932
984
|
}
|
|
933
985
|
}
|
|
934
986
|
} catch (e) {
|
|
935
|
-
//
|
|
936
|
-
process.off('SIGINT', cancelHandler);
|
|
937
|
-
process.off('SIGTERM', cancelHandler);
|
|
938
|
-
|
|
987
|
+
// SIGINT ya fue manejado por cancelHandler — solo clasificamos el resultado
|
|
939
988
|
if (userCanceledMission) {
|
|
940
989
|
missionResult = 'canceled';
|
|
941
990
|
finalFailReason = 'user_canceled';
|
|
942
|
-
// El cancelHandler ya registró la cancelación en el servidor.
|
|
943
|
-
// Solo mostramos un mensaje y dejamos que el finally genere el reporte.
|
|
944
991
|
console.log(chalk.yellow('\n⚠️ Misión cancelada — generando reporte de evidencia...'));
|
|
945
992
|
} else {
|
|
946
993
|
missionResult = 'failed';
|
|
@@ -952,20 +999,34 @@ async function main() {
|
|
|
952
999
|
}
|
|
953
1000
|
}
|
|
954
1001
|
} finally {
|
|
1002
|
+
// Desactivar el bloqueo de process.exit y restaurar el original
|
|
1003
|
+
_missionActive = false;
|
|
1004
|
+
process.exit = _origProcessExit;
|
|
1005
|
+
process.removeListener('SIGINT', cancelHandler);
|
|
1006
|
+
process.removeListener('SIGTERM', cancelHandler);
|
|
1007
|
+
|
|
955
1008
|
if (userCanceledMission) {
|
|
956
1009
|
missionResult = 'canceled';
|
|
957
1010
|
finalFailReason = 'user_canceled';
|
|
958
1011
|
await new Promise(r => setTimeout(r, 800));
|
|
959
1012
|
}
|
|
960
1013
|
|
|
1014
|
+
process.on('SIGINT', postProcessSigint);
|
|
1015
|
+
process.on('SIGTERM', postProcessSigint);
|
|
1016
|
+
|
|
961
1017
|
const sRep = spinner();
|
|
962
|
-
|
|
1018
|
+
if (!process.argv.includes('--debug')) {
|
|
1019
|
+
sRep.start('📊 Generating report...');
|
|
1020
|
+
} else {
|
|
1021
|
+
console.log('📊 Generating report...');
|
|
1022
|
+
}
|
|
1023
|
+
|
|
963
1024
|
await run("node", [path.join(PROJECT_ROOT, "scripts/rebrand-report.mjs")]).catch(() => { });
|
|
964
1025
|
|
|
965
1026
|
const reportDir = process.env.REPORTS_DIR || 'tests-report';
|
|
966
1027
|
const arcalityPath = path.resolve(reportDir, 'index.html');
|
|
967
1028
|
|
|
968
|
-
sRep.stop(
|
|
1029
|
+
sRep.stop();
|
|
969
1030
|
|
|
970
1031
|
// Read final usage and failReason from agent-log
|
|
971
1032
|
try {
|
|
@@ -990,14 +1051,20 @@ async function main() {
|
|
|
990
1051
|
const { uploadEvidence } = await import('../src/EvidenceUploader.mjs');
|
|
991
1052
|
|
|
992
1053
|
const sasData = await getEvidenceSasToken(mission.mission_id, finalProjectId);
|
|
993
|
-
console.log(`[DEBUG] sasData received:`, sasData);
|
|
1054
|
+
if (process.argv.includes('--debug')) console.log(`[DEBUG] sasData received:`, sasData);
|
|
994
1055
|
|
|
995
1056
|
// Soporte para camelCase y snake_case debido al serializador JSON del backend
|
|
996
1057
|
const actualSasUrl = sasData?.sasTokenUrl || sasData?.sasUrl || sasData?.sas_url;
|
|
997
1058
|
const actualBasePath = sasData?.basePath || sasData?.base_path;
|
|
998
1059
|
|
|
999
1060
|
if (sasData && actualSasUrl && actualBasePath) {
|
|
1000
|
-
if (!process.argv.includes('--debug'))
|
|
1061
|
+
if (!process.argv.includes('--debug')) {
|
|
1062
|
+
sRep.start('☁️ Uploading evidence to Azure...');
|
|
1063
|
+
}
|
|
1064
|
+
process.removeAllListeners('SIGINT');
|
|
1065
|
+
process.removeAllListeners('SIGTERM');
|
|
1066
|
+
process.on('SIGINT', postProcessSigint);
|
|
1067
|
+
process.on('SIGTERM', postProcessSigint);
|
|
1001
1068
|
|
|
1002
1069
|
await uploadEvidence(reportDir, actualSasUrl, actualBasePath);
|
|
1003
1070
|
|
|
@@ -1040,7 +1107,6 @@ async function main() {
|
|
|
1040
1107
|
}
|
|
1041
1108
|
|
|
1042
1109
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1043
|
-
sRep.stop(chalk.cyan('Report process initialized.'));
|
|
1044
1110
|
|
|
1045
1111
|
// Ping project after report
|
|
1046
1112
|
await pingProject(
|
|
@@ -1061,6 +1127,10 @@ async function main() {
|
|
|
1061
1127
|
agentMode = false;
|
|
1062
1128
|
prompt = "";
|
|
1063
1129
|
firstRun = false;
|
|
1130
|
+
|
|
1131
|
+
// Limpiar handlers del post-proceso
|
|
1132
|
+
process.removeListener('SIGINT', postProcessSigint);
|
|
1133
|
+
process.removeListener('SIGTERM', postProcessSigint);
|
|
1064
1134
|
}
|
|
1065
1135
|
}
|
|
1066
1136
|
firstRun = false;
|
package/src/KnowledgeService.ts
CHANGED
|
@@ -144,7 +144,7 @@ export class KnowledgeService {
|
|
|
144
144
|
if (data.status === 'skipped_duplicate_dom') {
|
|
145
145
|
// console.log(chalk.gray(`[Knowledge] Ingesta omitida (Duplicado).`));
|
|
146
146
|
} else {
|
|
147
|
-
console.log(chalk.cyan(`[Knowledge] Ingesta exitosa (Project: ${payload.project_id})`));
|
|
147
|
+
if (process.env.DEBUG) console.log(chalk.cyan(`[Knowledge] Ingesta exitosa (Project: ${payload.project_id})`));
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
} catch (e: any) {
|
|
@@ -73,15 +73,15 @@ export async function pushRule(rule: {
|
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
if (!res.ok) {
|
|
76
|
-
console.warn(`[CollectiveMemory] pushRule HTTP ${res.status}: ${await res.text().catch(() => '')}`);
|
|
76
|
+
if (process.env.DEBUG) console.warn(`[CollectiveMemory] pushRule HTTP ${res.status}: ${await res.text().catch(() => '')}`);
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
const data = await res.json();
|
|
81
|
-
console.log(`\x1b[32m[CollectiveMemory] ✅ Regla guardada: "${rule.title}"\x1b[0m`);
|
|
81
|
+
if (process.env.DEBUG) console.log(`\x1b[32m[CollectiveMemory] ✅ Regla guardada: "${rule.title}"\x1b[0m`);
|
|
82
82
|
return data.id ?? null;
|
|
83
83
|
} catch (err: any) {
|
|
84
|
-
console.warn(`[CollectiveMemory] pushRule error: ${err?.message}`);
|
|
84
|
+
if (process.env.DEBUG) console.warn(`[CollectiveMemory] pushRule error: ${err?.message}`);
|
|
85
85
|
return null;
|
|
86
86
|
}
|
|
87
87
|
}
|
|
@@ -164,15 +164,15 @@ export async function pushKnowledge(knowledge: {
|
|
|
164
164
|
});
|
|
165
165
|
|
|
166
166
|
if (!res.ok) {
|
|
167
|
-
console.warn(`[CollectiveMemory] pushKnowledge HTTP ${res.status}: ${await res.text().catch(() => '')}`);
|
|
167
|
+
if (process.env.DEBUG) console.warn(`[CollectiveMemory] pushKnowledge HTTP ${res.status}: ${await res.text().catch(() => '')}`);
|
|
168
168
|
return null;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
const data = await res.json();
|
|
172
|
-
console.log(`\x1b[32m[CollectiveMemory] ✅ Conocimiento guardado: "${knowledge.title}"\x1b[0m`);
|
|
172
|
+
if (process.env.DEBUG) console.log(`\x1b[32m[CollectiveMemory] ✅ Conocimiento guardado: "${knowledge.title}"\x1b[0m`);
|
|
173
173
|
return data.id ?? null;
|
|
174
174
|
} catch (err: any) {
|
|
175
|
-
console.warn(`[CollectiveMemory] pushKnowledge error: ${err?.message}`);
|
|
175
|
+
if (process.env.DEBUG) console.warn(`[CollectiveMemory] pushKnowledge error: ${err?.message}`);
|
|
176
176
|
return null;
|
|
177
177
|
}
|
|
178
178
|
}
|
|
@@ -228,7 +228,7 @@ export async function savePromptPattern(patternData: any): Promise<boolean> {
|
|
|
228
228
|
mission_id: getMissionId(),
|
|
229
229
|
...patternData
|
|
230
230
|
};
|
|
231
|
-
console.log(`[PatternSave] 📤 POST ${apiUrl} | org=${payload.organization_id} proj=${payload.project_id} mission=${payload.mission_id}`);
|
|
231
|
+
if (process.env.DEBUG) console.log(`[PatternSave] 📤 POST ${apiUrl} | org=${payload.organization_id} proj=${payload.project_id} mission=${payload.mission_id}`);
|
|
232
232
|
|
|
233
233
|
const res = await fetch(apiUrl, {
|
|
234
234
|
method: 'POST',
|
|
@@ -240,13 +240,13 @@ export async function savePromptPattern(patternData: any): Promise<boolean> {
|
|
|
240
240
|
});
|
|
241
241
|
|
|
242
242
|
if (!res.ok) {
|
|
243
|
-
console.warn(`[PatternSave] ⚠️ HTTP ${res.status} al guardar patrón en ${apiUrl}: ${await res.text().catch(() => '')}`);
|
|
243
|
+
if (process.env.DEBUG) console.warn(`[PatternSave] ⚠️ HTTP ${res.status} al guardar patrón en ${apiUrl}: ${await res.text().catch(() => '')}`);
|
|
244
244
|
} else {
|
|
245
|
-
console.log(`[PatternSave] ✅ Patrón guardado exitosamente.`);
|
|
245
|
+
if (process.env.DEBUG) console.log(`[PatternSave] ✅ Patrón guardado exitosamente.`);
|
|
246
246
|
}
|
|
247
247
|
return res.ok;
|
|
248
248
|
} catch (err: any) {
|
|
249
|
-
console.warn(`[PatternSave] ⚠️ Error guardando patrón: ${err?.message || 'unknown'}`);
|
|
249
|
+
if (process.env.DEBUG) console.warn(`[PatternSave] ⚠️ Error guardando patrón: ${err?.message || 'unknown'}`);
|
|
250
250
|
return false;
|
|
251
251
|
}
|
|
252
252
|
}
|
|
@@ -23,7 +23,7 @@ export class SecurityScanner {
|
|
|
23
23
|
* @returns A promise that resolves to an array of found vulnerabilities.
|
|
24
24
|
*/
|
|
25
25
|
async runAllScans(): Promise<SecurityFinding[]> {
|
|
26
|
-
console.log('[QA-SEC] Starting comprehensive security scan...');
|
|
26
|
+
if (process.env.DEBUG) console.log('[QA-SEC] Starting comprehensive security scan...');
|
|
27
27
|
|
|
28
28
|
// Scan 1: Audit HTTP Security Headers
|
|
29
29
|
const headerVulnerabilities = await audit_http_headers(this.page);
|
|
@@ -38,7 +38,7 @@ export class SecurityScanner {
|
|
|
38
38
|
|
|
39
39
|
// More scans like auth bypass for known admin URLs could be added here.
|
|
40
40
|
|
|
41
|
-
console.log(`[QA-SEC] Comprehensive scan finished. Found ${this.report.length} potential vulnerabilities.`);
|
|
41
|
+
if (process.env.DEBUG) console.log(`[QA-SEC] Comprehensive scan finished. Found ${this.report.length} potential vulnerabilities.`);
|
|
42
42
|
return this.getReport();
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -55,7 +55,7 @@ export class SecurityScanner {
|
|
|
55
55
|
const payload = 'https://www.evil-redirect.com';
|
|
56
56
|
url.searchParams.set(param, payload);
|
|
57
57
|
|
|
58
|
-
console.log(`[QA-SEC] Testing Open Redirect on param: ${param}`);
|
|
58
|
+
if (process.env.DEBUG) console.log(`[QA-SEC] Testing Open Redirect on param: ${param}`);
|
|
59
59
|
|
|
60
60
|
try {
|
|
61
61
|
await this.page.goto(url.toString(), { waitUntil: 'networkidle' });
|
|
@@ -89,7 +89,7 @@ export class SecurityScanner {
|
|
|
89
89
|
await this.page.goBack();
|
|
90
90
|
}
|
|
91
91
|
} catch (error) {
|
|
92
|
-
console.log(`[QA-SEC] Error during open redirect test, this might be expected if the navigation was blocked. Continuing scan.`);
|
|
92
|
+
if (process.env.DEBUG) console.log(`[QA-SEC] Error during open redirect test, this might be expected if the navigation was blocked. Continuing scan.`);
|
|
93
93
|
// It's possible the navigation to the evil site fails, which is good.
|
|
94
94
|
// We should ensure we can recover and continue.
|
|
95
95
|
await this.page.goBack(); // Try to recover state
|
|
@@ -305,7 +305,8 @@ var qaAdvancedTools = [
|
|
|
305
305
|
|
|
306
306
|
// tests/_helpers/qa-security-tools.ts
|
|
307
307
|
async function audit_http_headers(page) {
|
|
308
|
-
|
|
308
|
+
if (process.env.DEBUG)
|
|
309
|
+
console.log(`[QA-SEC] Auditing HTTP Headers for: ${page.url()}`);
|
|
309
310
|
const response = await page.reload({ waitUntil: "domcontentloaded" });
|
|
310
311
|
const headers = response?.headers();
|
|
311
312
|
const vulnerabilities = [];
|
|
@@ -364,11 +365,13 @@ async function audit_http_headers(page) {
|
|
|
364
365
|
});
|
|
365
366
|
}
|
|
366
367
|
}
|
|
367
|
-
|
|
368
|
+
if (process.env.DEBUG)
|
|
369
|
+
console.log(`[QA-SEC] Found ${vulnerabilities.length} missing security headers.`);
|
|
368
370
|
return vulnerabilities;
|
|
369
371
|
}
|
|
370
372
|
async function scan_sensitive_data_exposure(page) {
|
|
371
|
-
|
|
373
|
+
if (process.env.DEBUG)
|
|
374
|
+
console.log(`[QA-SEC] Scanning localStorage and sessionStorage for sensitive data.`);
|
|
372
375
|
const vulnerabilities = [];
|
|
373
376
|
const sensitiveKeywords = ["token", "password", "secret", "key", "jwt", "auth"];
|
|
374
377
|
const storages = {
|
|
@@ -406,7 +409,8 @@ async function scan_sensitive_data_exposure(page) {
|
|
|
406
409
|
}
|
|
407
410
|
}
|
|
408
411
|
}
|
|
409
|
-
|
|
412
|
+
if (process.env.DEBUG)
|
|
413
|
+
console.log(`[QA-SEC] Found ${vulnerabilities.length} instances of sensitive data in browser storage.`);
|
|
410
414
|
return vulnerabilities;
|
|
411
415
|
}
|
|
412
416
|
|
|
@@ -516,7 +520,8 @@ var KnowledgeService = class _KnowledgeService {
|
|
|
516
520
|
const data = await res.json();
|
|
517
521
|
if (data.status === "skipped_duplicate_dom") {
|
|
518
522
|
} else {
|
|
519
|
-
|
|
523
|
+
if (process.env.DEBUG)
|
|
524
|
+
console.log(import_chalk.default.cyan(`[Knowledge] Ingesta exitosa (Project: ${payload.project_id})`));
|
|
520
525
|
}
|
|
521
526
|
}
|
|
522
527
|
} catch (e) {
|
|
@@ -691,14 +696,17 @@ async function pushRule(rule) {
|
|
|
691
696
|
})
|
|
692
697
|
});
|
|
693
698
|
if (!res.ok) {
|
|
694
|
-
|
|
699
|
+
if (process.env.DEBUG)
|
|
700
|
+
console.warn(`[CollectiveMemory] pushRule HTTP ${res.status}: ${await res.text().catch(() => "")}`);
|
|
695
701
|
return null;
|
|
696
702
|
}
|
|
697
703
|
const data = await res.json();
|
|
698
|
-
|
|
704
|
+
if (process.env.DEBUG)
|
|
705
|
+
console.log(`\x1B[32m[CollectiveMemory] \u2705 Regla guardada: "${rule.title}"\x1B[0m`);
|
|
699
706
|
return data.id ?? null;
|
|
700
707
|
} catch (err) {
|
|
701
|
-
|
|
708
|
+
if (process.env.DEBUG)
|
|
709
|
+
console.warn(`[CollectiveMemory] pushRule error: ${err?.message}`);
|
|
702
710
|
return null;
|
|
703
711
|
}
|
|
704
712
|
}
|
|
@@ -755,14 +763,17 @@ async function pushKnowledge(knowledge) {
|
|
|
755
763
|
})
|
|
756
764
|
});
|
|
757
765
|
if (!res.ok) {
|
|
758
|
-
|
|
766
|
+
if (process.env.DEBUG)
|
|
767
|
+
console.warn(`[CollectiveMemory] pushKnowledge HTTP ${res.status}: ${await res.text().catch(() => "")}`);
|
|
759
768
|
return null;
|
|
760
769
|
}
|
|
761
770
|
const data = await res.json();
|
|
762
|
-
|
|
771
|
+
if (process.env.DEBUG)
|
|
772
|
+
console.log(`\x1B[32m[CollectiveMemory] \u2705 Conocimiento guardado: "${knowledge.title}"\x1B[0m`);
|
|
763
773
|
return data.id ?? null;
|
|
764
774
|
} catch (err) {
|
|
765
|
-
|
|
775
|
+
if (process.env.DEBUG)
|
|
776
|
+
console.warn(`[CollectiveMemory] pushKnowledge error: ${err?.message}`);
|
|
766
777
|
return null;
|
|
767
778
|
}
|
|
768
779
|
}
|
|
@@ -811,7 +822,8 @@ async function savePromptPattern(patternData) {
|
|
|
811
822
|
mission_id: getMissionId(),
|
|
812
823
|
...patternData
|
|
813
824
|
};
|
|
814
|
-
|
|
825
|
+
if (process.env.DEBUG)
|
|
826
|
+
console.log(`[PatternSave] \u{1F4E4} POST ${apiUrl} | org=${payload.organization_id} proj=${payload.project_id} mission=${payload.mission_id}`);
|
|
815
827
|
const res = await fetch(apiUrl, {
|
|
816
828
|
method: "POST",
|
|
817
829
|
headers: {
|
|
@@ -821,13 +833,16 @@ async function savePromptPattern(patternData) {
|
|
|
821
833
|
body: JSON.stringify(payload)
|
|
822
834
|
});
|
|
823
835
|
if (!res.ok) {
|
|
824
|
-
|
|
836
|
+
if (process.env.DEBUG)
|
|
837
|
+
console.warn(`[PatternSave] \u26A0\uFE0F HTTP ${res.status} al guardar patr\xF3n en ${apiUrl}: ${await res.text().catch(() => "")}`);
|
|
825
838
|
} else {
|
|
826
|
-
|
|
839
|
+
if (process.env.DEBUG)
|
|
840
|
+
console.log(`[PatternSave] \u2705 Patr\xF3n guardado exitosamente.`);
|
|
827
841
|
}
|
|
828
842
|
return res.ok;
|
|
829
843
|
} catch (err) {
|
|
830
|
-
|
|
844
|
+
if (process.env.DEBUG)
|
|
845
|
+
console.warn(`[PatternSave] \u26A0\uFE0F Error guardando patr\xF3n: ${err?.message || "unknown"}`);
|
|
831
846
|
return false;
|
|
832
847
|
}
|
|
833
848
|
}
|
|
@@ -987,7 +1002,8 @@ ${content}
|
|
|
987
1002
|
}
|
|
988
1003
|
}
|
|
989
1004
|
if (loadedCount > 0) {
|
|
990
|
-
|
|
1005
|
+
if (process.env.DEBUG)
|
|
1006
|
+
console.log(`>> \u{1F4D1} ${loadedCount} habilidades QA inyectadas al cerebro.`);
|
|
991
1007
|
return skillsContext;
|
|
992
1008
|
}
|
|
993
1009
|
} catch (e) {
|
|
@@ -2659,13 +2675,15 @@ var SecurityScanner = class {
|
|
|
2659
2675
|
* @returns A promise that resolves to an array of found vulnerabilities.
|
|
2660
2676
|
*/
|
|
2661
2677
|
async runAllScans() {
|
|
2662
|
-
|
|
2678
|
+
if (process.env.DEBUG)
|
|
2679
|
+
console.log("[QA-SEC] Starting comprehensive security scan...");
|
|
2663
2680
|
const headerVulnerabilities = await audit_http_headers(this.page);
|
|
2664
2681
|
this.report.push(...headerVulnerabilities);
|
|
2665
2682
|
const storageVulnerabilities = await scan_sensitive_data_exposure(this.page);
|
|
2666
2683
|
this.report.push(...storageVulnerabilities);
|
|
2667
2684
|
await this.testOpenRedirect();
|
|
2668
|
-
|
|
2685
|
+
if (process.env.DEBUG)
|
|
2686
|
+
console.log(`[QA-SEC] Comprehensive scan finished. Found ${this.report.length} potential vulnerabilities.`);
|
|
2669
2687
|
return this.getReport();
|
|
2670
2688
|
}
|
|
2671
2689
|
/**
|
|
@@ -2679,7 +2697,8 @@ var SecurityScanner = class {
|
|
|
2679
2697
|
const originalValue = url.searchParams.get(param);
|
|
2680
2698
|
const payload = "https://www.evil-redirect.com";
|
|
2681
2699
|
url.searchParams.set(param, payload);
|
|
2682
|
-
|
|
2700
|
+
if (process.env.DEBUG)
|
|
2701
|
+
console.log(`[QA-SEC] Testing Open Redirect on param: ${param}`);
|
|
2683
2702
|
try {
|
|
2684
2703
|
await this.page.goto(url.toString(), { waitUntil: "networkidle" });
|
|
2685
2704
|
if (this.page.url().startsWith(payload)) {
|
|
@@ -2710,7 +2729,8 @@ var SecurityScanner = class {
|
|
|
2710
2729
|
await this.page.goBack();
|
|
2711
2730
|
}
|
|
2712
2731
|
} catch (error) {
|
|
2713
|
-
|
|
2732
|
+
if (process.env.DEBUG)
|
|
2733
|
+
console.log(`[QA-SEC] Error during open redirect test, this might be expected if the navigation was blocked. Continuing scan.`);
|
|
2714
2734
|
await this.page.goBack();
|
|
2715
2735
|
}
|
|
2716
2736
|
}
|
|
@@ -2906,11 +2926,13 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2906
2926
|
const apiUrl = process.env.ARCALITY_API_URL || "";
|
|
2907
2927
|
const apiKey = process.env.ARCALITY_API_KEY || "";
|
|
2908
2928
|
if (!pid || !apiUrl || !apiKey) {
|
|
2909
|
-
|
|
2929
|
+
if (process.env.DEBUG)
|
|
2930
|
+
console.warn(`[CollectiveMemory] \u26A0\uFE0F Vars faltantes \u2014 project_id='${pid}' api_url='${apiUrl}' api_key='${apiKey ? "OK" : "MISSING"}' \u2192 NO persiste.`);
|
|
2910
2931
|
return;
|
|
2911
2932
|
}
|
|
2912
2933
|
if (collectiveMemoryPersisted && !isFailed) {
|
|
2913
|
-
|
|
2934
|
+
if (process.env.DEBUG)
|
|
2935
|
+
console.log(`[CollectiveMemory] \u2139\uFE0F Ya persistido como \xE9xito. Skip duplicado.`);
|
|
2914
2936
|
return;
|
|
2915
2937
|
}
|
|
2916
2938
|
if (!isFailed && stepsData.length === 0 && stepsDataBackup.length === 0) {
|
|
@@ -3907,7 +3929,8 @@ ${patternContext}` : prompt;
|
|
|
3907
3929
|
const stepsToSave = stepsData.length > 0 ? stepsData : stepsDataBackup;
|
|
3908
3930
|
const hasCompleteSteps = stepsToSave.length >= stepsDataBackup.length;
|
|
3909
3931
|
if (!isDuplicate && hasCompleteSteps) {
|
|
3910
|
-
|
|
3932
|
+
if (process.env.DEBUG)
|
|
3933
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F4BE} Guardando nuevo patr\xF3n de ejecuci\xF3n (${stepsToSave.length} pasos, pattern_saved: true)...`);
|
|
3911
3934
|
const sanitizedSteps = stepsToSave.map((step, idx) => {
|
|
3912
3935
|
const s = JSON.parse(JSON.stringify(step));
|
|
3913
3936
|
s.step_index = idx;
|
|
@@ -3940,7 +3963,8 @@ ${patternContext}` : prompt;
|
|
|
3940
3963
|
}
|
|
3941
3964
|
}
|
|
3942
3965
|
}
|
|
3943
|
-
|
|
3966
|
+
if (process.env.DEBUG)
|
|
3967
|
+
console.log("\u270D\uFE0F [HUMANIZANDO] Generando resumen de \xE9xito para el reporte...");
|
|
3944
3968
|
const summary = await agent.humanizeMissionResult(prompt, history);
|
|
3945
3969
|
if (summary) {
|
|
3946
3970
|
await testInfo.attach("success_summary", {
|
|
@@ -3948,7 +3972,8 @@ ${patternContext}` : prompt;
|
|
|
3948
3972
|
contentType: "text/plain"
|
|
3949
3973
|
});
|
|
3950
3974
|
}
|
|
3951
|
-
|
|
3975
|
+
if (process.env.DEBUG)
|
|
3976
|
+
console.log("\u{1F916} [SMART-YAML] Generating mission card...");
|
|
3952
3977
|
const smartYaml = await agent.summarizeMissionToYaml(prompt, history, target);
|
|
3953
3978
|
if (smartYaml) {
|
|
3954
3979
|
const yamlPath = path2.join(contextDir, "last-mission-smart.yaml");
|