@arcadialdev/arcality 2.6.6 → 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/src/services/securityScanner.ts +5 -0
- package/tests/_helpers/ArcalityReporter.js +234 -251
- package/tests/_helpers/agentic-runner.bundle.spec.js +600 -135
|
@@ -4,6 +4,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __getProtoOf = Object.getPrototypeOf;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __esm = (fn, res) => function __init() {
|
|
8
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
9
|
+
};
|
|
10
|
+
var __export = (target, all) => {
|
|
11
|
+
for (var name in all)
|
|
12
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
|
+
};
|
|
7
14
|
var __copyProps = (to, from, except, desc) => {
|
|
8
15
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
9
16
|
for (let key of __getOwnPropNames(from))
|
|
@@ -21,6 +28,399 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
28
|
mod
|
|
22
29
|
));
|
|
23
30
|
|
|
31
|
+
// src/configLoader.mjs
|
|
32
|
+
function getApiUrl() {
|
|
33
|
+
return "https://arcalityqadev.arcadial.lat";
|
|
34
|
+
}
|
|
35
|
+
function loadLocalArcalityConfig() {
|
|
36
|
+
try {
|
|
37
|
+
const configPath = import_node_path.default.join(process.cwd(), "arcality.config");
|
|
38
|
+
if (import_node_fs.default.existsSync(configPath)) {
|
|
39
|
+
let raw = import_node_fs.default.readFileSync(configPath, "utf8");
|
|
40
|
+
if (raw.charCodeAt(0) === 65279)
|
|
41
|
+
raw = raw.slice(1);
|
|
42
|
+
return JSON.parse(raw);
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
function getApiKey() {
|
|
49
|
+
const localConfig = loadLocalArcalityConfig();
|
|
50
|
+
if (localConfig?.apiKey) {
|
|
51
|
+
process.env.ARCALITY_API_KEY = localConfig.apiKey;
|
|
52
|
+
return { key: localConfig.apiKey, source: "config" };
|
|
53
|
+
}
|
|
54
|
+
if (process.env.ARCALITY_API_KEY) {
|
|
55
|
+
return { key: process.env.ARCALITY_API_KEY, source: "env" };
|
|
56
|
+
}
|
|
57
|
+
return { key: null, source: "none" };
|
|
58
|
+
}
|
|
59
|
+
var import_node_fs, import_node_path, import_node_os, import_dotenv, CONFIG_DIR, CONFIG_FILE;
|
|
60
|
+
var init_configLoader = __esm({
|
|
61
|
+
"src/configLoader.mjs"() {
|
|
62
|
+
import_node_fs = __toESM(require("node:fs"), 1);
|
|
63
|
+
import_node_path = __toESM(require("node:path"), 1);
|
|
64
|
+
import_node_os = __toESM(require("node:os"), 1);
|
|
65
|
+
import_dotenv = __toESM(require("dotenv"), 1);
|
|
66
|
+
import_dotenv.default.config();
|
|
67
|
+
CONFIG_DIR = import_node_path.default.join(import_node_os.default.homedir(), ".arcality");
|
|
68
|
+
CONFIG_FILE = import_node_path.default.join(CONFIG_DIR, "config.json");
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// src/arcalityClient.mjs
|
|
73
|
+
var arcalityClient_exports = {};
|
|
74
|
+
__export(arcalityClient_exports, {
|
|
75
|
+
ArcalityClient: () => ArcalityClient,
|
|
76
|
+
createAdoTask: () => createAdoTask,
|
|
77
|
+
endMission: () => endMission,
|
|
78
|
+
fetchMissions: () => fetchMissions,
|
|
79
|
+
getEvidenceSasToken: () => getEvidenceSasToken,
|
|
80
|
+
startMission: () => startMission,
|
|
81
|
+
validateApiKey: () => validateApiKey
|
|
82
|
+
});
|
|
83
|
+
function getTodayKey() {
|
|
84
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
85
|
+
}
|
|
86
|
+
function loadLocalUsage() {
|
|
87
|
+
try {
|
|
88
|
+
if (import_node_fs2.default.existsSync(USAGE_FILE)) {
|
|
89
|
+
return JSON.parse(import_node_fs2.default.readFileSync(USAGE_FILE, "utf8"));
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
function saveLocalUsage(usage) {
|
|
96
|
+
try {
|
|
97
|
+
if (!import_node_fs2.default.existsSync(CONFIG_DIR)) {
|
|
98
|
+
import_node_fs2.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
99
|
+
}
|
|
100
|
+
import_node_fs2.default.writeFileSync(USAGE_FILE, JSON.stringify(usage, null, 2), "utf8");
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function getLocalDailyUsage() {
|
|
105
|
+
const usage = loadLocalUsage();
|
|
106
|
+
const today = getTodayKey();
|
|
107
|
+
return usage[today] || 0;
|
|
108
|
+
}
|
|
109
|
+
function incrementLocalUsage() {
|
|
110
|
+
const usage = loadLocalUsage();
|
|
111
|
+
const today = getTodayKey();
|
|
112
|
+
usage[today] = (usage[today] || 0) + 1;
|
|
113
|
+
const keys = Object.keys(usage).sort().reverse();
|
|
114
|
+
const cleaned = {};
|
|
115
|
+
for (const k of keys.slice(0, 7)) {
|
|
116
|
+
cleaned[k] = usage[k];
|
|
117
|
+
}
|
|
118
|
+
saveLocalUsage(cleaned);
|
|
119
|
+
return cleaned[today];
|
|
120
|
+
}
|
|
121
|
+
function loadArcalityConfig() {
|
|
122
|
+
try {
|
|
123
|
+
const configPath = import_node_path2.default.join(process.cwd(), "arcality.config");
|
|
124
|
+
if (import_node_fs2.default.existsSync(configPath)) {
|
|
125
|
+
let raw = import_node_fs2.default.readFileSync(configPath, "utf8");
|
|
126
|
+
if (raw.charCodeAt(0) === 65279)
|
|
127
|
+
raw = raw.slice(1);
|
|
128
|
+
return JSON.parse(raw);
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
function getEffectiveApiBase() {
|
|
135
|
+
return getApiUrl();
|
|
136
|
+
}
|
|
137
|
+
function getEffectiveApiKey() {
|
|
138
|
+
const localConfig = loadArcalityConfig();
|
|
139
|
+
if (localConfig?.apiKey)
|
|
140
|
+
return localConfig.apiKey;
|
|
141
|
+
const { key } = getApiKey();
|
|
142
|
+
return key;
|
|
143
|
+
}
|
|
144
|
+
function loadProjectId() {
|
|
145
|
+
const localConfig = loadArcalityConfig();
|
|
146
|
+
if (localConfig?.projectId)
|
|
147
|
+
return localConfig.projectId;
|
|
148
|
+
return process.env.ARCALITY_PROJECT_ID || null;
|
|
149
|
+
}
|
|
150
|
+
async function validateApiKey() {
|
|
151
|
+
const key = getEffectiveApiKey();
|
|
152
|
+
if (!key) {
|
|
153
|
+
return { valid: false, error: "no_api_key", mode: "mock" };
|
|
154
|
+
}
|
|
155
|
+
if (!key.startsWith("arc_")) {
|
|
156
|
+
return { valid: false, error: "invalid_format", mode: "mock" };
|
|
157
|
+
}
|
|
158
|
+
const apiBase = getEffectiveApiBase();
|
|
159
|
+
if (apiBase) {
|
|
160
|
+
try {
|
|
161
|
+
const controller = new AbortController();
|
|
162
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
163
|
+
const res = await fetch(`${apiBase}/api/v1/auth/validate`, {
|
|
164
|
+
method: "POST",
|
|
165
|
+
headers: {
|
|
166
|
+
"Content-Type": "application/json",
|
|
167
|
+
"x-api-key": key
|
|
168
|
+
},
|
|
169
|
+
signal: controller.signal
|
|
170
|
+
});
|
|
171
|
+
clearTimeout(timeout);
|
|
172
|
+
if (res.status === 401)
|
|
173
|
+
return { valid: false, error: "invalid_api_key", mode: "live" };
|
|
174
|
+
if (res.status === 403)
|
|
175
|
+
return { valid: false, error: "plan_expired", mode: "live" };
|
|
176
|
+
if (!res.ok)
|
|
177
|
+
return { valid: false, error: "server_error", mode: "live" };
|
|
178
|
+
const data = await res.json();
|
|
179
|
+
if (process.env.DEBUG || process.argv.includes("--debug")) {
|
|
180
|
+
console.log(`[DEBUG] Auth validation response keys:`, Object.keys(data));
|
|
181
|
+
console.log(`[DEBUG] Auth validation response:`, JSON.stringify(data, null, 2));
|
|
182
|
+
}
|
|
183
|
+
return { ...data, valid: true, mode: "live" };
|
|
184
|
+
} catch (e) {
|
|
185
|
+
const reason = e?.name === "AbortError" ? "timeout (5s)" : e?.message || "unknown";
|
|
186
|
+
console.warn(`\u26A0\uFE0F Backend not available (${apiBase}): ${reason}. Using local mode.`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const dailyUsed = getLocalDailyUsage();
|
|
190
|
+
return {
|
|
191
|
+
valid: true,
|
|
192
|
+
mode: "mock",
|
|
193
|
+
plan: "internal",
|
|
194
|
+
daily_used: dailyUsed,
|
|
195
|
+
daily_limit: DAILY_MISSION_LIMIT,
|
|
196
|
+
remaining: Math.max(0, DAILY_MISSION_LIMIT - dailyUsed)
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
async function startMission(prompt, targetUrl) {
|
|
200
|
+
const key = getEffectiveApiKey();
|
|
201
|
+
if (!key)
|
|
202
|
+
return { allowed: false, error: "no_api_key" };
|
|
203
|
+
const apiBase = getEffectiveApiBase();
|
|
204
|
+
if (apiBase) {
|
|
205
|
+
try {
|
|
206
|
+
const projectId = process.env.ARCALITY_PROJECT_ID || loadArcalityConfig()?.projectId;
|
|
207
|
+
const controller = new AbortController();
|
|
208
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
209
|
+
const res = await fetch(`${apiBase}/api/v1/missions/start`, {
|
|
210
|
+
method: "POST",
|
|
211
|
+
headers: {
|
|
212
|
+
"Content-Type": "application/json",
|
|
213
|
+
"x-api-key": key
|
|
214
|
+
},
|
|
215
|
+
body: JSON.stringify({
|
|
216
|
+
prompt,
|
|
217
|
+
prompt_hash: simpleHash(prompt),
|
|
218
|
+
target_url: targetUrl,
|
|
219
|
+
project_id: projectId || void 0
|
|
220
|
+
}),
|
|
221
|
+
signal: controller.signal
|
|
222
|
+
});
|
|
223
|
+
clearTimeout(timeout);
|
|
224
|
+
if (res.status === 429) {
|
|
225
|
+
const data = await res.json();
|
|
226
|
+
return { allowed: false, error: "mission_limit_exceeded", ...data };
|
|
227
|
+
}
|
|
228
|
+
if (!res.ok)
|
|
229
|
+
return { allowed: false, error: "server_error" };
|
|
230
|
+
return { allowed: true, ...await res.json() };
|
|
231
|
+
} catch (e) {
|
|
232
|
+
const reason = e?.name === "AbortError" ? "timeout (5s)" : e?.message || "unknown";
|
|
233
|
+
console.warn(`\u26A0\uFE0F startMission failed (${apiBase}): ${reason}. Falling back to mock mode.`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const dailyUsed = getLocalDailyUsage();
|
|
237
|
+
if (dailyUsed >= DAILY_MISSION_LIMIT) {
|
|
238
|
+
const tomorrow = /* @__PURE__ */ new Date();
|
|
239
|
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
240
|
+
tomorrow.setHours(0, 0, 0, 0);
|
|
241
|
+
return {
|
|
242
|
+
allowed: false,
|
|
243
|
+
error: "mission_limit_exceeded",
|
|
244
|
+
daily_used: dailyUsed,
|
|
245
|
+
daily_limit: DAILY_MISSION_LIMIT,
|
|
246
|
+
remaining: 0,
|
|
247
|
+
resets_at: tomorrow.toISOString()
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const newCount = incrementLocalUsage();
|
|
251
|
+
return {
|
|
252
|
+
allowed: true,
|
|
253
|
+
mode: "mock",
|
|
254
|
+
mission_id: `mock_${Date.now().toString(36)}`,
|
|
255
|
+
daily_used: newCount,
|
|
256
|
+
daily_limit: DAILY_MISSION_LIMIT,
|
|
257
|
+
remaining: DAILY_MISSION_LIMIT - newCount
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
async function fetchMissions(projectId, tags) {
|
|
261
|
+
const key = getEffectiveApiKey();
|
|
262
|
+
if (!key)
|
|
263
|
+
return { success: false, error: "no_api_key", missions: [] };
|
|
264
|
+
const apiBase = getEffectiveApiBase();
|
|
265
|
+
if (!apiBase) {
|
|
266
|
+
return { success: true, mode: "mock", missions: [] };
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
const url = new URL(`${apiBase}/api/v1/projects/${projectId}/missions`);
|
|
270
|
+
if (tags) {
|
|
271
|
+
url.searchParams.append("tags", tags);
|
|
272
|
+
}
|
|
273
|
+
const controller = new AbortController();
|
|
274
|
+
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
275
|
+
const res = await fetch(url.toString(), {
|
|
276
|
+
method: "GET",
|
|
277
|
+
headers: {
|
|
278
|
+
"x-api-key": key
|
|
279
|
+
},
|
|
280
|
+
signal: controller.signal
|
|
281
|
+
});
|
|
282
|
+
clearTimeout(timeout);
|
|
283
|
+
if (!res.ok) {
|
|
284
|
+
const errText = await res.text();
|
|
285
|
+
console.warn(`[DEBUG] fetchMissions failed: ${res.status} - ${errText}`);
|
|
286
|
+
return { success: false, error: "server_error", missions: [] };
|
|
287
|
+
}
|
|
288
|
+
const data = await res.json();
|
|
289
|
+
return { success: true, missions: data.missions || [] };
|
|
290
|
+
} catch (e) {
|
|
291
|
+
const reason = e?.name === "AbortError" ? "timeout (10s)" : e?.message || "unknown";
|
|
292
|
+
console.warn(`\u26A0\uFE0F fetchMissions failed (${apiBase}): ${reason}.`);
|
|
293
|
+
return { success: false, error: reason, missions: [] };
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async function endMission(missionId, result, usage = null, failReason = null, reportUrl = null, failReasoning = null) {
|
|
297
|
+
const apiBase = getEffectiveApiBase();
|
|
298
|
+
if (!apiBase || !missionId)
|
|
299
|
+
return { ok: true };
|
|
300
|
+
const key = getEffectiveApiKey();
|
|
301
|
+
try {
|
|
302
|
+
await fetch(`${apiBase}/api/v1/missions/${missionId}/end`, {
|
|
303
|
+
method: "POST",
|
|
304
|
+
headers: {
|
|
305
|
+
"Content-Type": "application/json",
|
|
306
|
+
"x-api-key": key
|
|
307
|
+
},
|
|
308
|
+
body: JSON.stringify({ result, usage, failReason, failReasoning, reportUrl })
|
|
309
|
+
});
|
|
310
|
+
} catch {
|
|
311
|
+
}
|
|
312
|
+
return { ok: true };
|
|
313
|
+
}
|
|
314
|
+
function simpleHash(str) {
|
|
315
|
+
let hash = 0;
|
|
316
|
+
for (let i = 0; i < str.length; i++) {
|
|
317
|
+
const char = str.charCodeAt(i);
|
|
318
|
+
hash = (hash << 5) - hash + char;
|
|
319
|
+
hash |= 0;
|
|
320
|
+
}
|
|
321
|
+
return "ph_" + Math.abs(hash).toString(36);
|
|
322
|
+
}
|
|
323
|
+
async function createAdoTask(title, description) {
|
|
324
|
+
const apiBase = getEffectiveApiBase();
|
|
325
|
+
if (!apiBase) {
|
|
326
|
+
if (process.env.DEBUG)
|
|
327
|
+
console.log(`[ADO] No hay ARCALITY_API_URL configurado. Omitiendo creaci\xF3n de Task en Azure DevOps.`);
|
|
328
|
+
return { success: false, error: "no_api_url" };
|
|
329
|
+
}
|
|
330
|
+
const projectId = loadProjectId();
|
|
331
|
+
if (!projectId) {
|
|
332
|
+
if (process.env.DEBUG)
|
|
333
|
+
console.log(`[ADO] No hay Project ID configurado. Omitiendo creaci\xF3n de Task en Azure DevOps.`);
|
|
334
|
+
return { success: false, error: "no_project_id" };
|
|
335
|
+
}
|
|
336
|
+
const key = getEffectiveApiKey();
|
|
337
|
+
if (!key) {
|
|
338
|
+
if (process.env.DEBUG)
|
|
339
|
+
console.log(`[ADO] No hay API key configurada. Omitiendo creaci\xF3n de Task en Azure DevOps.`);
|
|
340
|
+
return { success: false, error: "no_api_key" };
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
const controller = new AbortController();
|
|
344
|
+
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
345
|
+
const res = await fetch(`${apiBase}/api/v1/integrations/azuredevops/task`, {
|
|
346
|
+
method: "POST",
|
|
347
|
+
headers: {
|
|
348
|
+
"Content-Type": "application/json",
|
|
349
|
+
"x-api-key": key
|
|
350
|
+
},
|
|
351
|
+
body: JSON.stringify({ project_id: projectId, title, description }),
|
|
352
|
+
signal: controller.signal
|
|
353
|
+
});
|
|
354
|
+
clearTimeout(timeout);
|
|
355
|
+
if (res.status === 400) {
|
|
356
|
+
const data2 = await res.json().catch(() => ({}));
|
|
357
|
+
if (data2?.error === "create_task_failed") {
|
|
358
|
+
if (process.env.DEBUG)
|
|
359
|
+
console.log(`[ADO] Integraci\xF3n no configurada para esta organizaci\xF3n: ${data2.message}`);
|
|
360
|
+
} else {
|
|
361
|
+
console.warn(`\u26A0\uFE0F [ADO] Error al crear Task en Azure DevOps: ${data2?.message || "Bad Request"}`);
|
|
362
|
+
}
|
|
363
|
+
return { success: false, error: data2?.message || "create_task_failed" };
|
|
364
|
+
}
|
|
365
|
+
if (!res.ok) {
|
|
366
|
+
console.warn(`\u26A0\uFE0F [ADO] Error HTTP ${res.status} al crear Task en Azure DevOps.`);
|
|
367
|
+
return { success: false, error: `http_${res.status}` };
|
|
368
|
+
}
|
|
369
|
+
const data = await res.json();
|
|
370
|
+
console.log(`\u2705 [ADO] Task creada en Azure DevOps: ${data.task_url || "(sin URL)"}`);
|
|
371
|
+
return { success: true, taskUrl: data.task_url };
|
|
372
|
+
} catch (e) {
|
|
373
|
+
const reason = e?.name === "AbortError" ? "timeout (15s)" : e?.message || "unknown";
|
|
374
|
+
console.warn(`\u26A0\uFE0F [ADO] No se pudo crear la Task en Azure DevOps: ${reason}`);
|
|
375
|
+
return { success: false, error: reason };
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
async function getEvidenceSasToken(missionId, projectId) {
|
|
379
|
+
const apiBase = getEffectiveApiBase();
|
|
380
|
+
if (!apiBase || !missionId)
|
|
381
|
+
return null;
|
|
382
|
+
const key = getEffectiveApiKey();
|
|
383
|
+
if (!key)
|
|
384
|
+
return null;
|
|
385
|
+
try {
|
|
386
|
+
const organization_id = process.env.ARCALITY_ORG_ID || "";
|
|
387
|
+
const res = await fetch(`${apiBase}/api/v1/missions/${missionId}/evidence/sas`, {
|
|
388
|
+
method: "POST",
|
|
389
|
+
headers: {
|
|
390
|
+
"Content-Type": "application/json",
|
|
391
|
+
"x-api-key": key
|
|
392
|
+
},
|
|
393
|
+
body: JSON.stringify({ organization_id, project_id: projectId })
|
|
394
|
+
});
|
|
395
|
+
if (!res.ok) {
|
|
396
|
+
const txt = await res.text();
|
|
397
|
+
console.log(`[DEBUG] SAS Token API Failed: ${res.status} - ${txt}`);
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
const data = await res.json();
|
|
401
|
+
return data;
|
|
402
|
+
} catch (e) {
|
|
403
|
+
console.log(`[DEBUG] SAS Token API Error: ${e.message}`);
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
var import_node_fs2, import_node_path2, DAILY_MISSION_LIMIT, USAGE_FILE, ArcalityClient;
|
|
408
|
+
var init_arcalityClient = __esm({
|
|
409
|
+
"src/arcalityClient.mjs"() {
|
|
410
|
+
import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
411
|
+
import_node_path2 = __toESM(require("node:path"), 1);
|
|
412
|
+
init_configLoader();
|
|
413
|
+
DAILY_MISSION_LIMIT = 35;
|
|
414
|
+
USAGE_FILE = import_node_path2.default.join(CONFIG_DIR, "usage.json");
|
|
415
|
+
ArcalityClient = class {
|
|
416
|
+
constructor(apiKey) {
|
|
417
|
+
this.apiUrl = getApiUrl();
|
|
418
|
+
this.apiKey = apiKey;
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
|
|
24
424
|
// tests/_helpers/agentic-runner.spec.ts
|
|
25
425
|
var import_test = require("@playwright/test");
|
|
26
426
|
|
|
@@ -415,8 +815,8 @@ async function scan_sensitive_data_exposure(page) {
|
|
|
415
815
|
}
|
|
416
816
|
|
|
417
817
|
// tests/_helpers/ai-agent-helper.ts
|
|
418
|
-
var
|
|
419
|
-
var
|
|
818
|
+
var fs3 = __toESM(require("fs"));
|
|
819
|
+
var path3 = __toESM(require("path"));
|
|
420
820
|
|
|
421
821
|
// src/KnowledgeService.ts
|
|
422
822
|
var crypto = __toESM(require("crypto"));
|
|
@@ -925,16 +1325,20 @@ var AIAgentHelper = class {
|
|
|
925
1325
|
});
|
|
926
1326
|
this.page.on("requestfailed", (request) => {
|
|
927
1327
|
const failure = request.failure();
|
|
1328
|
+
const errorText = failure?.errorText || "Aborted";
|
|
1329
|
+
if (errorText.includes("net::ERR_ABORTED") || errorText === "Aborted") {
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
928
1332
|
const resourceType = request.resourceType();
|
|
929
1333
|
const url = request.url();
|
|
930
1334
|
if (["fetch", "xhr", "document"].includes(resourceType)) {
|
|
931
1335
|
if (!url.includes("google") && !url.includes("analytics") && !url.includes("telemetry")) {
|
|
932
1336
|
if (resourceType === "document" || request.method() !== "GET") {
|
|
933
|
-
this.criticalNetworkError = `Network Request Failed (${request.method()}): ${url} - ${
|
|
1337
|
+
this.criticalNetworkError = `Network Request Failed (${request.method()}): ${url} - ${errorText}`;
|
|
934
1338
|
}
|
|
935
1339
|
}
|
|
936
1340
|
}
|
|
937
|
-
this.logs.push(`[NETWORK_ERROR] ${request.method()} ${request.url()} - ${
|
|
1341
|
+
this.logs.push(`[NETWORK_ERROR] ${request.method()} ${request.url()} - ${errorText}`);
|
|
938
1342
|
if (this.logs.length > 30)
|
|
939
1343
|
this.logs.shift();
|
|
940
1344
|
});
|
|
@@ -968,29 +1372,29 @@ var AIAgentHelper = class {
|
|
|
968
1372
|
try {
|
|
969
1373
|
let skillsContext = "";
|
|
970
1374
|
let loadedCount = 0;
|
|
971
|
-
const toolsRoot = process.env.ARCALITY_ROOT ||
|
|
1375
|
+
const toolsRoot = process.env.ARCALITY_ROOT || path3.join(__dirname, "..", "..");
|
|
972
1376
|
const possibleDirs = [
|
|
973
|
-
|
|
974
|
-
|
|
1377
|
+
path3.join(toolsRoot, ".agent", "skills"),
|
|
1378
|
+
path3.join(toolsRoot, ".agents", "skills")
|
|
975
1379
|
];
|
|
976
1380
|
for (const rootDir of possibleDirs) {
|
|
977
|
-
if (!
|
|
1381
|
+
if (!fs3.existsSync(rootDir))
|
|
978
1382
|
continue;
|
|
979
1383
|
if (loadedCount === 0) {
|
|
980
1384
|
skillsContext += "\n\u{1F6E0}\uFE0F SKILLS INSTALADAS (Metodolog\xEDas activas):\n";
|
|
981
1385
|
}
|
|
982
|
-
const entries =
|
|
1386
|
+
const entries = fs3.readdirSync(rootDir, { withFileTypes: true });
|
|
983
1387
|
for (const entry of entries) {
|
|
984
1388
|
let content = "";
|
|
985
1389
|
let skillName = entry.name;
|
|
986
1390
|
if (entry.isDirectory()) {
|
|
987
|
-
const skillPath =
|
|
988
|
-
if (
|
|
989
|
-
content =
|
|
1391
|
+
const skillPath = path3.join(rootDir, entry.name, "SKILL.md");
|
|
1392
|
+
if (fs3.existsSync(skillPath)) {
|
|
1393
|
+
content = fs3.readFileSync(skillPath, "utf8");
|
|
990
1394
|
}
|
|
991
1395
|
} else if (entry.name.endsWith(".md")) {
|
|
992
|
-
const skillPath =
|
|
993
|
-
content =
|
|
1396
|
+
const skillPath = path3.join(rootDir, entry.name);
|
|
1397
|
+
content = fs3.readFileSync(skillPath, "utf8");
|
|
994
1398
|
}
|
|
995
1399
|
if (content) {
|
|
996
1400
|
skillsContext += `
|
|
@@ -1013,9 +1417,9 @@ ${content}
|
|
|
1013
1417
|
}
|
|
1014
1418
|
loadMemory(prompt) {
|
|
1015
1419
|
try {
|
|
1016
|
-
const memoryFile =
|
|
1017
|
-
if (
|
|
1018
|
-
const memories = JSON.parse(
|
|
1420
|
+
const memoryFile = path3.join(this.contextDir, "memoria-agente.json");
|
|
1421
|
+
if (fs3.existsSync(memoryFile)) {
|
|
1422
|
+
const memories = JSON.parse(fs3.readFileSync(memoryFile, "utf8"));
|
|
1019
1423
|
const keywords = prompt.toLowerCase().split(" ").filter((w) => w.length > 4);
|
|
1020
1424
|
const relevantSuccess = memories.filter((m) => {
|
|
1021
1425
|
if (!m.success)
|
|
@@ -1288,7 +1692,7 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
1288
1692
|
if (bodyTextLength2 < 80 && components2 < 10) {
|
|
1289
1693
|
console.error(`
|
|
1290
1694
|
\u{1F6D1} [BLANK_STATE_CRASH] Vista en blanco confirmada tras espera (Comps: ${components2}, Texto: ${bodyTextLength2} chars). URL: ${urlForBlankCheck}`);
|
|
1291
|
-
throw new Error(`[BLANK_STATE_CRASH] El portal carg\xF3 en blanco. Esto es un fallo de renderizado del portal, no
|
|
1695
|
+
throw new Error(`[BLANK_STATE_CRASH] El portal carg\xF3 en blanco. Esto es un fallo de renderizado del portal, no de Arcality.`);
|
|
1292
1696
|
} else {
|
|
1293
1697
|
console.log(`>>ARCALITY_STATUS>> \u2705 [BLANK_STATE] Falsa alarma. P\xE1gina recuper\xF3 contenido tras espera (${components2} comps, ${bodyTextLength2} chars).`);
|
|
1294
1698
|
}
|
|
@@ -1386,13 +1790,13 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
1386
1790
|
*/
|
|
1387
1791
|
async getActionFromGuia(prompt, historyLength) {
|
|
1388
1792
|
try {
|
|
1389
|
-
const
|
|
1390
|
-
const
|
|
1391
|
-
const memoryFile =
|
|
1392
|
-
if (!
|
|
1793
|
+
const fs5 = require("fs");
|
|
1794
|
+
const path5 = require("path");
|
|
1795
|
+
const memoryFile = path5.join(this.contextDir, "memoria-agente.json");
|
|
1796
|
+
if (!fs5.existsSync(memoryFile)) {
|
|
1393
1797
|
return null;
|
|
1394
1798
|
}
|
|
1395
|
-
const memories = JSON.parse(
|
|
1799
|
+
const memories = JSON.parse(fs5.readFileSync(memoryFile, "utf8"));
|
|
1396
1800
|
const state = await this.getPageState();
|
|
1397
1801
|
const currentUrl = this.page.url();
|
|
1398
1802
|
const extractCriticalKeywords = (text) => {
|
|
@@ -1678,11 +2082,11 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
1678
2082
|
}
|
|
1679
2083
|
let customUserContext = "";
|
|
1680
2084
|
try {
|
|
1681
|
-
const
|
|
1682
|
-
const
|
|
1683
|
-
const customContextPath =
|
|
1684
|
-
if (
|
|
1685
|
-
const content =
|
|
2085
|
+
const fs5 = require("fs");
|
|
2086
|
+
const path5 = require("path");
|
|
2087
|
+
const customContextPath = path5.join(process.cwd(), ".arcality", "qa-context.md");
|
|
2088
|
+
if (fs5.existsSync(customContextPath)) {
|
|
2089
|
+
const content = fs5.readFileSync(customContextPath, "utf8");
|
|
1686
2090
|
customUserContext = `
|
|
1687
2091
|
<CUSTOMER_BUSINESS_RULES>
|
|
1688
2092
|
El Ing. de QA o Cliente final ha provisto las siguientes reglas estrictas para este negocio/dominio. DEBES priorizar esta informaci\xF3n sobre todas tus skills base:
|
|
@@ -1788,9 +2192,10 @@ These are NOT optional. You MUST use these tools in these situations:
|
|
|
1788
2192
|
- If HISTORY contains a line with "ERROR VISIBLE EN PANTALLA" or "ERROR POST-GUARDADO" with words like "repetido", "duplicado", "ya existe", or "already exists":
|
|
1789
2193
|
1. You MUST identify WHICH field caused the duplication (usually the main name/title field).
|
|
1790
2194
|
2. You MUST use \`fill\` to REPLACE that field's value with a NEW unique value. Add a numeric suffix like \`_${(/* @__PURE__ */ new Date()).getMinutes()}${(/* @__PURE__ */ new Date()).getSeconds()}\` or a short random number.
|
|
1791
|
-
3.
|
|
1792
|
-
4. **
|
|
1793
|
-
5.
|
|
2195
|
+
3. **MANDATORY SINGLE-TURN GROUPING**: You MUST perform the correction (using \`fill\`) AND click the submit/save button (using \`click\` on Siguiente/Guardar/Save) in the EXACT SAME TURN. Do NOT split this into separate turns.
|
|
2196
|
+
4. **DO NOT CLOSE ERROR TOASTS/ALERTS**: Closing toasts/alerts is strictly FORBIDDEN. It wastes a turn and hides the error context from the runner. Playwright can interact with elements underneath perfectly.
|
|
2197
|
+
5. **NEVER attempt to save with the same value that was already rejected.** This is FORBIDDEN.
|
|
2198
|
+
6. If you cannot determine which field is the duplicate, use \`capture_console_errors\` to get hints from the application.
|
|
1794
2199
|
7. **DATA UNIQUENESS**: When generating test data, ALWAYS include a unique suffix (timestamp, counter). Never use "Test123" or "Prueba" alone.
|
|
1795
2200
|
8. **FINISH CORRECTLY**: Use \`finish: true\` ONLY when the mission objective is FULLY achieved.
|
|
1796
2201
|
- **MANDATORY FORMS RULE**: If your mission involves creating, editing, or configuring something, YOU MUST CLICK THE FINAL SUBMIT/SAVE BUTTON ("Guardar", "Siguiente", "Finalizar") AND VERIFY THE SUCCESS MESSAGE BEFORE emitting \`finish: true\`. Typing the last field is NOT finishing the mission! Do NOT finish prematurely.
|
|
@@ -1801,6 +2206,7 @@ These are NOT optional. You MUST use these tools in these situations:
|
|
|
1801
2206
|
11. **NEGATIVE TESTING**: If mission expects an error (e.g., "verifica que no se pueda guardar sin campos"), and you see that error, the mission is SUCCESS. Use \`finish: true\`.
|
|
1802
2207
|
12. **GUIDE CONFLICT**: If SUCCESS MEMORY disagrees with what you see on screen, ABANDON the memory and trust your eyes.
|
|
1803
2208
|
13. **NATIVE CONTROLS**: For \`<input type="time">\`, \`<input type="date">\`, or similar native pickers, NEVER use \`fill\` repeatedly. Use \`interact_native_control\` or \`send_keyboard_event\` instead. If \`fill\` fails once, switch strategy immediately.
|
|
2209
|
+
14. **USER-DEFINED BUGS (MANDATORY & IMMEDIATE)**: If the prompt explicitly defines a condition as a bug (e.g., "Si no te deja eliminarla, es un bug de la aplicaci\xF3n", or "If X fails it's a bug"), and you encounter that condition, you MUST call \`report_application_bug\` IMMEDIATELY in that exact same turn. DO NOT perform any intermediate UI actions (such as clicking "Cancelar", closing modals, or navigating away) before calling the tool. Any intermediate UI action will cause you to lose your reasoning context in the next turn and enter an infinite loop. The call to \`report_application_bug\` must take absolute priority over any UI cleanup or close actions. DO NOT dismiss it as "expected business logic" or "referential integrity". The user's definition of a bug OVERRIDES all general reasoning.
|
|
1804
2210
|
|
|
1805
2211
|
# WIZARD & MULTI-STEP FORMS (CRITICAL)
|
|
1806
2212
|
When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 Step 3):
|
|
@@ -1841,7 +2247,7 @@ When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 St
|
|
|
1841
2247
|
|
|
1842
2248
|
11. **COMBOBOX KEYBOARD FALLBACK**: If you click a combobox/select field and the dropdown options (\`[li]\`, \`[OPTION]\`) are NOT visible after 1 turn, use this keyboard sequence: a) Click the combobox field ONCE. b) Use \`send_keyboard_event\` with key "ArrowDown" to open the options and highlight the first one. c) Use \`send_keyboard_event\` with key "Enter" to select it. This avoids the toggle problem and works with all dropdown implementations.
|
|
1843
2249
|
|
|
1844
|
-
12. **ERROR STOP PROTOCOL (CRITICAL)**: If you see a message starting with \`\u{1F6D1} [ERROR_CR\xCDTICO]\` or \`\u{1F6D1} ERROR POST-GUARDADO\`, do NOT assume it is a transient error. You MUST analyze the text. If it says "ya existe" or "duplicado", you MUST change the offending field value (e.g., add a random number)
|
|
2250
|
+
12. **ERROR STOP PROTOCOL (CRITICAL)**: If you see a message starting with \`\u{1F6D1} [ERROR_CR\xCDTICO]\` or \`\u{1F6D1} ERROR POST-GUARDADO\`, do NOT assume it is a transient error. You MUST analyze the text. If it says "ya existe" or "duplicado", you MUST change the offending field value (e.g., add a random number) AND click the save button in the SAME turn. Do NOT waste a turn closing the error toast. If you retry without changing any data and the error persists, you MUST use \`report_inability_to_proceed\` immediately. Looping on the same error toast is FORBIDDEN.`
|
|
1845
2251
|
}
|
|
1846
2252
|
];
|
|
1847
2253
|
if (this.testInfo) {
|
|
@@ -1901,7 +2307,7 @@ ${history.slice(-25).join("\n") || "None"}`
|
|
|
1901
2307
|
const isProxyMode = !!process.env.ARCALITY_API_URL;
|
|
1902
2308
|
const endpointUrl = isProxyMode ? `${process.env.ARCALITY_API_URL}/api/v1/ai/proxy` : "https://api.anthropic.com/v1/messages";
|
|
1903
2309
|
const displayTurn = stepCount !== void 0 ? stepCount : turn + 1;
|
|
1904
|
-
console.log(`>>ARCALITY_STATUS>> \u{1F4E1} Llamando a: ${endpointUrl} (turno IA ${displayTurn}${turn > 0 ? `,
|
|
2310
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F4E1} Llamando a: ${endpointUrl} (turno IA ${displayTurn}${turn > 0 ? `, ciclo interno ${turn + 1}` : ""})`);
|
|
1905
2311
|
const headers = {
|
|
1906
2312
|
"Content-Type": "application/json"
|
|
1907
2313
|
};
|
|
@@ -1973,7 +2379,7 @@ ${history.slice(-25).join("\n") || "None"}`
|
|
|
1973
2379
|
const textResponse = message.content.find((c) => c.type === "text")?.text || "";
|
|
1974
2380
|
if (toolCalls.length === 0) {
|
|
1975
2381
|
console.log(`(${((Date.now() - startTime) / 1e3).toFixed(1)}s)`);
|
|
1976
|
-
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F
|
|
2382
|
+
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Arcality devolvi\xF3 texto sin acci\xF3n. Reinyectando instrucci\xF3n mandatoria...`);
|
|
1977
2383
|
anthropicMessages.push({
|
|
1978
2384
|
role: "user",
|
|
1979
2385
|
content: [{ type: "text", text: `MANDATORY: You MUST call one of the available tools right now. You are NOT allowed to return plain text only. If you are unsure, call 'perform_ui_actions' with the most logical next action based on the mission and history. If you are truly stuck, call 'report_inability_to_proceed'. DO NOT return text without a tool call.` }]
|
|
@@ -2029,9 +2435,43 @@ ${history.slice(-25).join("\n") || "None"}`
|
|
|
2029
2435
|
);
|
|
2030
2436
|
} catch (e) {
|
|
2031
2437
|
}
|
|
2438
|
+
try {
|
|
2439
|
+
const { createAdoTask: createAdoTask2 } = await Promise.resolve().then(() => (init_arcalityClient(), arcalityClient_exports));
|
|
2440
|
+
const shortDesc = toolInput.bug_description.length > 80 ? toolInput.bug_description.substring(0, 80).trimEnd() + "..." : toolInput.bug_description;
|
|
2441
|
+
const bugTitle = `[QA Bug] ${shortDesc}`;
|
|
2442
|
+
const bugDescription = [
|
|
2443
|
+
`**Bug detectado autom\xE1ticamente por Arcality QA**`,
|
|
2444
|
+
``,
|
|
2445
|
+
`**Descripci\xF3n:** ${toolInput.bug_description}`,
|
|
2446
|
+
`**Comportamiento esperado:** ${toolInput.expected_behavior}`,
|
|
2447
|
+
`**Comportamiento actual:** ${toolInput.actual_behavior}`,
|
|
2448
|
+
`**URL afectada:** ${this.page.url()}`,
|
|
2449
|
+
`**Fecha:** ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
2450
|
+
].join("\n");
|
|
2451
|
+
createAdoTask2(bugTitle, bugDescription).catch(() => {
|
|
2452
|
+
});
|
|
2453
|
+
} catch (e) {
|
|
2454
|
+
}
|
|
2032
2455
|
throw new Error(`APPLICATION BUG: ${toolInput.bug_description}`);
|
|
2033
2456
|
} else if (toolName === "report_inability_to_proceed") {
|
|
2034
2457
|
toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: "Confusion acknowledged." });
|
|
2458
|
+
try {
|
|
2459
|
+
const { createAdoTask: createAdoTask2 } = await Promise.resolve().then(() => (init_arcalityClient(), arcalityClient_exports));
|
|
2460
|
+
const shortDesc = toolInput.bug_description.length > 80 ? toolInput.bug_description.substring(0, 80).trimEnd() + "..." : toolInput.bug_description;
|
|
2461
|
+
const bugTitle = `[QA Bug] ${shortDesc}`;
|
|
2462
|
+
const bugDescription = [
|
|
2463
|
+
`**Bug detectado autom\xE1ticamente por Arcality QA**`,
|
|
2464
|
+
``,
|
|
2465
|
+
`**Descripci\xF3n:** ${toolInput.bug_description}`,
|
|
2466
|
+
`**Comportamiento esperado:** ${toolInput.expected_behavior}`,
|
|
2467
|
+
`**Comportamiento actual:** ${toolInput.actual_behavior}`,
|
|
2468
|
+
`**URL afectada:** ${this.page.url()}`,
|
|
2469
|
+
`**Fecha:** ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
2470
|
+
].join("\n");
|
|
2471
|
+
createAdoTask2(bugTitle, bugDescription).catch(() => {
|
|
2472
|
+
});
|
|
2473
|
+
} catch (e) {
|
|
2474
|
+
}
|
|
2035
2475
|
return {
|
|
2036
2476
|
thought: `DISCULPA: ${toolInput.confusion_reason}
|
|
2037
2477
|
|
|
@@ -2675,6 +3115,11 @@ var SecurityScanner = class {
|
|
|
2675
3115
|
* @returns A promise that resolves to an array of found vulnerabilities.
|
|
2676
3116
|
*/
|
|
2677
3117
|
async runAllScans() {
|
|
3118
|
+
if (this.page.isClosed()) {
|
|
3119
|
+
if (process.env.DEBUG)
|
|
3120
|
+
console.log("[QA-SEC] Security scan skipped because page is closed.");
|
|
3121
|
+
return [];
|
|
3122
|
+
}
|
|
2678
3123
|
if (process.env.DEBUG)
|
|
2679
3124
|
console.log("[QA-SEC] Starting comprehensive security scan...");
|
|
2680
3125
|
const headerVulnerabilities = await audit_http_headers(this.page);
|
|
@@ -2690,6 +3135,8 @@ var SecurityScanner = class {
|
|
|
2690
3135
|
* A basic test for open redirect vulnerabilities on the current URL.
|
|
2691
3136
|
*/
|
|
2692
3137
|
async testOpenRedirect() {
|
|
3138
|
+
if (this.page.isClosed())
|
|
3139
|
+
return;
|
|
2693
3140
|
const url = new URL(this.page.url());
|
|
2694
3141
|
const redirectParams = ["redirect", "next", "url", "returnTo", "dest"];
|
|
2695
3142
|
for (const param of redirectParams) {
|
|
@@ -2763,8 +3210,8 @@ var SecurityScanner = class {
|
|
|
2763
3210
|
|
|
2764
3211
|
// tests/_helpers/agentic-runner.spec.ts
|
|
2765
3212
|
var import_config = require("dotenv/config");
|
|
2766
|
-
var
|
|
2767
|
-
var
|
|
3213
|
+
var fs4 = __toESM(require("fs"));
|
|
3214
|
+
var path4 = __toESM(require("path"));
|
|
2768
3215
|
var crypto2 = __toESM(require("crypto"));
|
|
2769
3216
|
var _sessionRuleCache = /* @__PURE__ */ new Set();
|
|
2770
3217
|
function captureValidationRule(errorMessage, context) {
|
|
@@ -2795,10 +3242,10 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2795
3242
|
const history = [];
|
|
2796
3243
|
const urlHistory = [];
|
|
2797
3244
|
const stepsData = [];
|
|
2798
|
-
const memoryFile =
|
|
2799
|
-
if (
|
|
3245
|
+
const memoryFile = path4.join(contextDir, "memoria-arcality.json");
|
|
3246
|
+
if (fs4.existsSync(memoryFile)) {
|
|
2800
3247
|
try {
|
|
2801
|
-
|
|
3248
|
+
fs4.unlinkSync(memoryFile);
|
|
2802
3249
|
} catch {
|
|
2803
3250
|
}
|
|
2804
3251
|
}
|
|
@@ -2826,14 +3273,14 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2826
3273
|
if (resultsSaved)
|
|
2827
3274
|
return;
|
|
2828
3275
|
resultsSaved = true;
|
|
2829
|
-
if (!
|
|
2830
|
-
|
|
3276
|
+
if (!fs4.existsSync(contextDir))
|
|
3277
|
+
fs4.mkdirSync(contextDir, { recursive: true });
|
|
2831
3278
|
const finalSuccess = aiMarkedSuccess && !hasCriticalError;
|
|
2832
3279
|
try {
|
|
2833
|
-
const memoryFile2 =
|
|
3280
|
+
const memoryFile2 = path4.join(contextDir, "memoria-arcality.json");
|
|
2834
3281
|
let memories = [];
|
|
2835
|
-
if (
|
|
2836
|
-
const content =
|
|
3282
|
+
if (fs4.existsSync(memoryFile2)) {
|
|
3283
|
+
const content = fs4.readFileSync(memoryFile2, "utf8").trim();
|
|
2837
3284
|
if (content) {
|
|
2838
3285
|
try {
|
|
2839
3286
|
memories = JSON.parse(content);
|
|
@@ -2907,16 +3354,16 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2907
3354
|
timestamp: Date.now()
|
|
2908
3355
|
};
|
|
2909
3356
|
memories.push(missionData);
|
|
2910
|
-
|
|
3357
|
+
fs4.writeFileSync(memoryFile2, JSON.stringify(memories, null, 2));
|
|
2911
3358
|
console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Memoria guardada: ${cleanSteps.length} pasos limpios (${rawSteps.length - cleanSteps.length} corruptos filtrados)`);
|
|
2912
3359
|
} catch (err) {
|
|
2913
3360
|
console.error("\u274C Fall\xF3 al guardar memoria:", err);
|
|
2914
3361
|
}
|
|
2915
3362
|
try {
|
|
2916
|
-
const logPath =
|
|
2917
|
-
|
|
3363
|
+
const logPath = path4.join(contextDir, `agent-log-${Date.now()}.json`);
|
|
3364
|
+
fs4.writeFileSync(logPath, JSON.stringify({ prompt, history, steps: stepCount, success: finalSuccess, error: hasCriticalError, fail_reason: failReason, fail_reasoning: failReasoning, usage: totalMissionUsage, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
2918
3365
|
try {
|
|
2919
|
-
|
|
3366
|
+
fs4.appendFileSync(path4.join(contextDir, "arcality-history.log"), JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), mission: prompt.substring(0, 150), success: finalSuccess, steps: stepCount, usage: totalMissionUsage }) + "\n");
|
|
2920
3367
|
} catch (e) {
|
|
2921
3368
|
}
|
|
2922
3369
|
} catch (err) {
|
|
@@ -2951,8 +3398,8 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2951
3398
|
});
|
|
2952
3399
|
console.log(`
|
|
2953
3400
|
======================================================`);
|
|
2954
|
-
console.log(`\u{1F9E0} [
|
|
2955
|
-
console.log(`>>arcality>>
|
|
3401
|
+
console.log(`\u{1F9E0} [ARCHIVE SUBSYSTEM - KNOWLEDGE INGESTION]`);
|
|
3402
|
+
console.log(`>>arcality>> Saving telemetry (project: ${pid}, steps: ${effectiveSteps.length}, isFailed: ${isFailed})...`);
|
|
2956
3403
|
console.log(`======================================================
|
|
2957
3404
|
`);
|
|
2958
3405
|
try {
|
|
@@ -2973,7 +3420,7 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2973
3420
|
const ruleId = await pushRule({
|
|
2974
3421
|
rule_type: "NAVIGATION",
|
|
2975
3422
|
title: `Flujo de navegaci\xF3n: ${prompt.substring(0, 80)}`,
|
|
2976
|
-
description: `
|
|
3423
|
+
description: `Arcality naveg\xF3 ${uniqueUrls.length} p\xE1ginas: ${uniqueUrls.join(" \u2192 ").substring(0, 400)}`,
|
|
2977
3424
|
severity: "suggestion"
|
|
2978
3425
|
});
|
|
2979
3426
|
if (ruleId)
|
|
@@ -2996,7 +3443,7 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2996
3443
|
const failRuleId = await pushRule({
|
|
2997
3444
|
rule_type: "VALIDATION",
|
|
2998
3445
|
title: `Misi\xF3n fallida: ${prompt.substring(0, 80)}`,
|
|
2999
|
-
description: `
|
|
3446
|
+
description: `Arcality no pudo completar: "${prompt.substring(0, 200)}". \xDAltimos pasos: ${stepsNarrative.substring(0, 300)}`,
|
|
3000
3447
|
severity: "important"
|
|
3001
3448
|
});
|
|
3002
3449
|
if (failRuleId)
|
|
@@ -3009,7 +3456,7 @@ function captureValidationRule(errorMessage, context) {
|
|
|
3009
3456
|
}
|
|
3010
3457
|
};
|
|
3011
3458
|
console.log(`
|
|
3012
|
-
\u{1F916} [
|
|
3459
|
+
\u{1F916} [ARCALITY] Iniciando misi\xF3n: "${prompt}"`);
|
|
3013
3460
|
const base = process.env.BASE_URL || "";
|
|
3014
3461
|
const target = process.env.TARGET_PATH || "/";
|
|
3015
3462
|
if (process.env.LOGIN_USER && process.env.LOGIN_PASSWORD) {
|
|
@@ -3079,10 +3526,19 @@ function captureValidationRule(errorMessage, context) {
|
|
|
3079
3526
|
} catch {
|
|
3080
3527
|
patternJsonObj = {};
|
|
3081
3528
|
}
|
|
3082
|
-
const
|
|
3529
|
+
const patternKeys = Object.keys(patternJsonObj);
|
|
3530
|
+
const bestMatchKeys = Object.keys(bestMatch);
|
|
3531
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F52C} [DIAGN\xD3STICO] Claves en bestMatch: [${bestMatchKeys.join(", ")}]`);
|
|
3532
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F52C} [DIAGN\xD3STICO] Claves en patternJsonObj: [${patternKeys.join(", ")}]`);
|
|
3533
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F52C} [DIAGN\xD3STICO] patternJsonObj.steps (tipo): ${typeof patternJsonObj.steps} | valor: ${JSON.stringify(patternJsonObj.steps)?.substring(0, 120)}`);
|
|
3534
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F52C} [DIAGN\xD3STICO] patternJsonObj.steps_technical (tipo): ${typeof patternJsonObj.steps_technical} | es array: ${Array.isArray(patternJsonObj.steps_technical)} | length: ${Array.isArray(patternJsonObj.steps_technical) ? patternJsonObj.steps_technical.length : "N/A"}`);
|
|
3535
|
+
const rawStepsTechnical = patternJsonObj.steps_technical || patternJsonObj.stepsTechnical || patternJsonObj.StepsTechnical || patternJsonObj.technical_steps || patternJsonObj.technicalSteps || // Fallback: si 'steps' es un array de objetos (no un número), usarlo directamente
|
|
3536
|
+
(Array.isArray(patternJsonObj.steps) && patternJsonObj.steps.length > 0 && typeof patternJsonObj.steps[0] === "object" ? patternJsonObj.steps : null);
|
|
3083
3537
|
const resolvedStepsData = Array.isArray(rawStepsTechnical) ? rawStepsTechnical : [];
|
|
3084
3538
|
if (resolvedStepsData.length === 0) {
|
|
3085
|
-
console.warn(`>>ARCALITY_STATUS>> \u26A0\uFE0F Patr\xF3n descargado sin steps_technical v\xE1lidos. Modo Gu\xEDa no ser\xE1 activado.`);
|
|
3539
|
+
console.warn(`>>ARCALITY_STATUS>> \u26A0\uFE0F Patr\xF3n descargado sin steps_technical v\xE1lidos (0 pasos encontrados en todas las variantes de nombre). Modo Gu\xEDa no ser\xE1 activado.`);
|
|
3540
|
+
} else {
|
|
3541
|
+
console.log(`>>ARCALITY_STATUS>> \u2705 [DIAGN\xD3STICO] steps_technical resueltos: ${resolvedStepsData.length} pasos. Muestra del paso[0]: ${JSON.stringify(resolvedStepsData[0]).substring(0, 200)}`);
|
|
3086
3542
|
}
|
|
3087
3543
|
const localMemoryData = {
|
|
3088
3544
|
prompt,
|
|
@@ -3093,28 +3549,33 @@ function captureValidationRule(errorMessage, context) {
|
|
|
3093
3549
|
timestamp: Date.now()
|
|
3094
3550
|
};
|
|
3095
3551
|
try {
|
|
3096
|
-
const memoryFile2 =
|
|
3552
|
+
const memoryFile2 = path4.join(contextDir, "memoria-agente.json");
|
|
3097
3553
|
let memories = [];
|
|
3098
|
-
if (
|
|
3554
|
+
if (fs4.existsSync(memoryFile2)) {
|
|
3099
3555
|
try {
|
|
3100
|
-
memories = JSON.parse(
|
|
3556
|
+
memories = JSON.parse(fs4.readFileSync(memoryFile2, "utf8"));
|
|
3101
3557
|
} catch {
|
|
3102
3558
|
memories = [];
|
|
3103
3559
|
}
|
|
3104
3560
|
}
|
|
3105
3561
|
const normalizeForDedup = (s) => s.toLowerCase().replace(/\s+/g, " ").trim();
|
|
3106
3562
|
const normalizedCurrentPrompt = normalizeForDedup(prompt);
|
|
3107
|
-
const
|
|
3108
|
-
|
|
3563
|
+
const existingIdx = memories.findIndex((m) => normalizeForDedup(m.prompt || "") === normalizedCurrentPrompt);
|
|
3564
|
+
const existingHasSteps = existingIdx >= 0 && (memories[existingIdx].steps_data || []).length > 0;
|
|
3565
|
+
if (existingIdx < 0) {
|
|
3109
3566
|
if (resolvedStepsData.length > 0) {
|
|
3110
3567
|
memories.push(localMemoryData);
|
|
3111
|
-
|
|
3112
|
-
console.log(`>>ARCALITY_STATUS>> \u2728 Memoria sincronizada
|
|
3568
|
+
fs4.writeFileSync(memoryFile2, JSON.stringify(memories, null, 2));
|
|
3569
|
+
console.log(`>>ARCALITY_STATUS>> \u2728 Memoria sincronizada en memoria-agente.json (${resolvedStepsData.length} pasos). Arcality entrar\xE1 en MODO GU\xCDA.`);
|
|
3113
3570
|
} else {
|
|
3114
|
-
console.warn(`>>ARCALITY_STATUS>> \u26A0\uFE0F Memoria NO sincronizada: el patr\xF3n del backend tiene 0 pasos t\xE9cnicos v\xE1lidos.`);
|
|
3571
|
+
console.warn(`>>ARCALITY_STATUS>> \u26A0\uFE0F Memoria NO sincronizada: el patr\xF3n del backend tiene 0 pasos t\xE9cnicos v\xE1lidos (steps_technical vac\xEDo).`);
|
|
3115
3572
|
}
|
|
3573
|
+
} else if (!existingHasSteps && resolvedStepsData.length > 0) {
|
|
3574
|
+
memories[existingIdx] = localMemoryData;
|
|
3575
|
+
fs4.writeFileSync(memoryFile2, JSON.stringify(memories, null, 2));
|
|
3576
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F504} Patr\xF3n corrupto (0 pasos) sobreescrito en memoria-agente.json (${resolvedStepsData.length} pasos nuevos). MODO GU\xCDA activado.`);
|
|
3116
3577
|
} else {
|
|
3117
|
-
console.log(`>>ARCALITY_STATUS>> \u2139\uFE0F Patr\xF3n ya existe en memoria
|
|
3578
|
+
console.log(`>>ARCALITY_STATUS>> \u2139\uFE0F Patr\xF3n ya existe en memoria-agente.json con ${(memories[existingIdx]?.steps_data || []).length} pasos. MODO GU\xCDA activado sin re-escritura.`);
|
|
3118
3579
|
}
|
|
3119
3580
|
} catch (err) {
|
|
3120
3581
|
console.warn(`Error al sincronizar memoria local: ${err}`);
|
|
@@ -3152,7 +3613,7 @@ ${readableHistory}`;
|
|
|
3152
3613
|
let containsError = false;
|
|
3153
3614
|
if (page.isClosed()) {
|
|
3154
3615
|
console.error(`
|
|
3155
|
-
\
|
|
3616
|
+
\u26A0 [LAZARUS SUBSYSTEM: Critical Crash] La pesta\xF1a del navegador fue cerrada inesperadamente (inicio de ciclo).`);
|
|
3156
3617
|
console.error(` >> Causa probable: El portal caus\xF3 una navegaci\xF3n forzada, crash de tab, o redirigi\xF3 fuera del contexto.`);
|
|
3157
3618
|
hasCriticalError = true;
|
|
3158
3619
|
failReason = "portal_page_crash";
|
|
@@ -3250,7 +3711,7 @@ ${readableHistory}`;
|
|
|
3250
3711
|
console.log(`>>ARCALITY_STATUS>> \u{1F6D1} Error interno del Portal/Red detectado: "${networkError}"`);
|
|
3251
3712
|
hasCriticalError = true;
|
|
3252
3713
|
failReason = "portal_network_error";
|
|
3253
|
-
failReasoning = `El portal web fall\xF3 al intentar comunicarse con su backend (ej. una petici\xF3n XHR o Fetch fall\xF3 o dio timeout).
|
|
3714
|
+
failReasoning = `El portal web fall\xF3 al intentar comunicarse con su backend (ej. una petici\xF3n XHR o Fetch fall\xF3 o dio timeout). Arcality QA no tiene control sobre las ca\xEDdas del servidor.
|
|
3254
3715
|
Detalles t\xE9cnicos capturados en la red: ${networkError}`;
|
|
3255
3716
|
isFinished = true;
|
|
3256
3717
|
history.push(`Turno ${stepCount}: \u{1F6D1} ERROR CR\xCDTICO DE SISTEMA/RED: ${networkError}. El portal fall\xF3 internamente al ejecutar una petici\xF3n XHR/Fetch.`);
|
|
@@ -3278,12 +3739,12 @@ Detalles de la excepci\xF3n en consola: ${jsError}`;
|
|
|
3278
3739
|
console.error(`
|
|
3279
3740
|
\u{1F6D1} [SYSTEM ERROR DETECTADO] El portal report\xF3 un error de sistema no recuperable en UI:`);
|
|
3280
3741
|
console.error(` Mensaje: "${sysErrTxt.trim().substring(0, 150)}"`);
|
|
3281
|
-
console.error(` >> Tipo: portal_internal_error |
|
|
3742
|
+
console.error(` >> Tipo: portal_internal_error | Arcality NO puede corregir errores del servidor.`);
|
|
3282
3743
|
hasCriticalError = true;
|
|
3283
3744
|
failReason = "portal_internal_error";
|
|
3284
|
-
failReasoning = `El servidor devolvi\xF3 un error sist\xE9mico irrecuperable que fue mostrado en la interfaz visual: "${sysErrTxt.trim()}".
|
|
3745
|
+
failReasoning = `El servidor devolvi\xF3 un error sist\xE9mico irrecuperable que fue mostrado en la interfaz visual: "${sysErrTxt.trim()}". Arcality QA no puede reparar bugs l\xF3gicos o de infraestructura del servidor, por lo que la misi\xF3n fue detenida preventivamente.`;
|
|
3285
3746
|
isFinished = true;
|
|
3286
|
-
history.push(`Turno ${stepCount}: \u{1F6D1} ERROR SIST\xC9MICO DEL PORTAL: "${sysErrTxt.trim().substring(0, 200)}". Este es un fallo del servidor, no un error de datos
|
|
3747
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} ERROR SIST\xC9MICO DEL PORTAL: "${sysErrTxt.trim().substring(0, 200)}". Este es un fallo del servidor, no un error de datos de Arcality. El portal devolvi\xF3 HTTP 200 pero report\xF3 un error interno en la UI.`);
|
|
3287
3748
|
saveMissionResults();
|
|
3288
3749
|
break;
|
|
3289
3750
|
}
|
|
@@ -3295,7 +3756,7 @@ Detalles de la excepci\xF3n en consola: ${jsError}`;
|
|
|
3295
3756
|
break;
|
|
3296
3757
|
let hasVisibleError = false;
|
|
3297
3758
|
try {
|
|
3298
|
-
const errorEls = await page.locator('.toast, .alert, [role="alert"], [role="status"], .snackbar, .notification, .error-message').all();
|
|
3759
|
+
const errorEls = await page.locator('.toast, .alert, [role="alert"], [role="status"], .snackbar, .notification, .error-message, .mat-snack-bar-container, [class*="alert"], [class*="toast"]').all();
|
|
3299
3760
|
for (const el of errorEls) {
|
|
3300
3761
|
if (await el.isVisible().catch(() => false)) {
|
|
3301
3762
|
const errTxt = await el.innerText().catch(() => "");
|
|
@@ -3314,26 +3775,26 @@ Detalles de la excepci\xF3n en consola: ${jsError}`;
|
|
|
3314
3775
|
await page.waitForTimeout(1e3);
|
|
3315
3776
|
response = await agent.getActionFromGuia(prompt, guideIdx);
|
|
3316
3777
|
if (response) {
|
|
3317
|
-
console.log(`>>ARCALITY_STATUS>> \u{1F504}
|
|
3778
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F504} Guide recovered path after 1000ms delay.`);
|
|
3318
3779
|
}
|
|
3319
3780
|
}
|
|
3320
3781
|
} else {
|
|
3321
|
-
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F
|
|
3782
|
+
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Visible UI anomaly detected. Bypassing Guide.`);
|
|
3322
3783
|
guideIdx = 999999;
|
|
3323
3784
|
if (stepsData.length > 0) {
|
|
3324
3785
|
stepsDataBackup = [...stepsData];
|
|
3325
3786
|
stepsData.length = 0;
|
|
3326
3787
|
}
|
|
3327
3788
|
try {
|
|
3328
|
-
const errorEls = await page.locator('.toast, .alert, [role="alert"], [role="status"], .snackbar, .notification, .error-message, .mat-snack-bar-container').all();
|
|
3789
|
+
const errorEls = await page.locator('.toast, .alert, [role="alert"], [role="status"], .snackbar, .notification, .error-message, .mat-snack-bar-container, [class*="alert"], [class*="toast"]').all();
|
|
3329
3790
|
for (const el of errorEls) {
|
|
3330
3791
|
if (await el.isVisible().catch(() => false)) {
|
|
3331
3792
|
const errTxt = await el.innerText().catch(() => "");
|
|
3332
3793
|
if (errTxt && failureKeywords.test(errTxt.toLowerCase())) {
|
|
3333
3794
|
const isDuplicateError = /repetido|duplicado|ya existe|already exists/i.test(errTxt);
|
|
3334
|
-
const correctionHint = isDuplicateError ? `\u{1F6D1} ACCI\xD3N REQUERIDA: El valor que ingresaste ya existe en el sistema. DEBES regresar al campo que caus\xF3 el error y cambiar su valor por uno \xDANICO (agrega un sufijo como _${Date.now().toString().slice(-4)} o un n\xFAmero aleatorio). NO intentes guardar sin cambiar el valor primero.` : `\u{1F6D1}
|
|
3795
|
+
const correctionHint = isDuplicateError ? `\u{1F6D1} ACCI\xD3N REQUERIDA: El valor que ingresaste ya existe en el sistema. DEBES regresar al campo que caus\xF3 el error y cambiar su valor por uno \xDANICO (agrega un sufijo como _${Date.now().toString().slice(-4)} o un n\xFAmero aleatorio). NO intentes guardar sin cambiar el valor primero.` : `\u{1F6D1} ANOMAL\xCDA UI DETECTADA: El sistema mostr\xF3 un error de validaci\xF3n. DIRECTIVA PARA ARCALITY: Si este error significa que la misi\xF3n no puede completarse (ej. un bug del sistema o un estado bloqueado), DEBES usar 'finish: true' inmediatamente y explicar el problema. Si es un simple error de formato de datos, corrige los datos y reintenta.`;
|
|
3335
3796
|
history.push(`Turno ${stepCount}: \u{1F6D1} ERROR VISIBLE EN PANTALLA: "${errTxt.trim().substring(0, 200)}". ${correctionHint}`);
|
|
3336
|
-
console.log(`>>ARCALITY_STATUS>> \u{1F6D1}
|
|
3797
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F6D1} Anomal\xEDa inyectada en historial: "${errTxt.substring(0, 80)}"`);
|
|
3337
3798
|
break;
|
|
3338
3799
|
}
|
|
3339
3800
|
}
|
|
@@ -3342,7 +3803,7 @@ Detalles de la excepci\xF3n en consola: ${jsError}`;
|
|
|
3342
3803
|
}
|
|
3343
3804
|
}
|
|
3344
3805
|
if (response) {
|
|
3345
|
-
const guideActionDesc = response.actions?.[0] ? `${response.actions[0].action}
|
|
3806
|
+
const guideActionDesc = response.actions?.[0] ? `${response.actions[0].action} on "${response.actions[0].description || ""}"`.toLowerCase() : "";
|
|
3346
3807
|
const recentHistory = history.slice(-4).map((h) => h.toLowerCase());
|
|
3347
3808
|
const isGuideRepeating = guideActionDesc && recentHistory.some((h) => h.includes(guideActionDesc.substring(0, 30)));
|
|
3348
3809
|
if (isGuideRepeating) {
|
|
@@ -3354,15 +3815,15 @@ Detalles de la excepci\xF3n en consola: ${jsError}`;
|
|
|
3354
3815
|
guideIdx++;
|
|
3355
3816
|
console.log(`
|
|
3356
3817
|
======================================================`);
|
|
3357
|
-
console.log(`\u{1F680} [
|
|
3358
|
-
console.log(`>>arcality>> Paso validado (Gu\xEDa #${guideStepCount}, guideIdx=${guideIdx},
|
|
3818
|
+
console.log(`\u{1F680} [MODO GU\xCDA ACTIVADO - EJECUTANDO RUTA R\xC1PIDA CONOCIDA]`);
|
|
3819
|
+
console.log(`>>arcality>> Paso validado (Gu\xEDa #${guideStepCount}, guideIdx=${guideIdx}, omite IA)`);
|
|
3359
3820
|
console.log(`======================================================
|
|
3360
3821
|
`);
|
|
3361
3822
|
}
|
|
3362
3823
|
}
|
|
3363
3824
|
if (!response) {
|
|
3364
3825
|
stepCount++;
|
|
3365
|
-
console.log(`>>ARCALITY_STATUS>> \
|
|
3826
|
+
console.log(`>>ARCALITY_STATUS>> \u25C9 [ARCALITY] Calculando siguiente acci\xF3n... (Turno ${stepCount}/${maxSteps}) (Saltos por Gu\xEDa: ${guideStepCount})...`);
|
|
3366
3827
|
const effectivePrompt = patternContext ? `${prompt}
|
|
3367
3828
|
|
|
3368
3829
|
${patternContext}` : prompt;
|
|
@@ -3414,7 +3875,7 @@ ${patternContext}` : prompt;
|
|
|
3414
3875
|
}
|
|
3415
3876
|
console.log(`
|
|
3416
3877
|
======================================================`);
|
|
3417
|
-
console.log(`\u{1F916} [RAZONAMIENTO
|
|
3878
|
+
console.log(`\u{1F916} [RAZONAMIENTO DE ARCALITY]`);
|
|
3418
3879
|
console.log(`${response ? response.thought : "N/A"}`);
|
|
3419
3880
|
console.log(`======================================================
|
|
3420
3881
|
`);
|
|
@@ -3437,7 +3898,7 @@ ${patternContext}` : prompt;
|
|
|
3437
3898
|
const loopType = isClickLoop ? "click" : "fill";
|
|
3438
3899
|
const uniqueTargets = isClickLoop ? uniqueClicks : uniqueFills;
|
|
3439
3900
|
console.warn(`
|
|
3440
|
-
\u26A0
|
|
3901
|
+
\u26A0 [OUROBOROS SUBSYSTEM: Repetitive ${loopType} loop anomaly detected]:`);
|
|
3441
3902
|
console.warn(` \xDAltimas acciones: ${lastActions.join(" \u2192 ")}`);
|
|
3442
3903
|
response.thought = `\u{1F6D1} ERROR: Bucle repetitivo en ${loopType} sobre: ${Array.from(uniqueTargets).join(", ")}.`;
|
|
3443
3904
|
response.finish = true;
|
|
@@ -3445,27 +3906,27 @@ ${patternContext}` : prompt;
|
|
|
3445
3906
|
aiMarkedSuccess = false;
|
|
3446
3907
|
hasCriticalError = true;
|
|
3447
3908
|
failReason = "loop_detected";
|
|
3448
|
-
failReasoning = `
|
|
3909
|
+
failReasoning = `Arcality QA entr\xF3 en un bucle l\xF3gico intentando ejecutar la misma acci\xF3n repetidamente sin observar cambios significativos en el DOM.
|
|
3449
3910
|
Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(uniqueTargets).join(", ")}.`;
|
|
3450
3911
|
} else if ((isClickLoop || isFillLoop) && agentRecovered) {
|
|
3451
|
-
console.log(`>>ARCALITY_STATUS>> \u{1F504} Loop candidato detectado pero
|
|
3912
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F504} Loop candidato detectado pero Arcality se recuper\xF3 en este turno. Continuando misi\xF3n.`);
|
|
3452
3913
|
}
|
|
3453
3914
|
}
|
|
3454
3915
|
if (!response.finish && history.length >= 3) {
|
|
3455
3916
|
const lastSixHistory = history.slice(-6);
|
|
3456
3917
|
const consecutiveWaits = lastSixHistory.filter((h) => h.includes('wait en "')).length;
|
|
3457
3918
|
if (consecutiveWaits >= 3) {
|
|
3458
|
-
const blankEvidencePath =
|
|
3919
|
+
const blankEvidencePath = path4.join(contextDir, `portal-blank-state-${Date.now()}.png`);
|
|
3459
3920
|
await page.screenshot({ path: blankEvidencePath, fullPage: true }).catch(() => {
|
|
3460
3921
|
});
|
|
3461
3922
|
const blankUrl = page.url();
|
|
3462
3923
|
console.error(`
|
|
3463
|
-
\
|
|
3924
|
+
\u26A0 [VOIDWATCH SUBSYSTEM: Rendering Anomaly] Arcality ejecut\xF3 ${consecutiveWaits} esperas consecutivas sin progreso.`);
|
|
3464
3925
|
console.error(` >> URL afectada: ${blankUrl}`);
|
|
3465
3926
|
console.error(` >> El portal carg\xF3 el shell (navegaci\xF3n, header) pero los WIDGETS/COMPONENTES quedaron en blanco.`);
|
|
3466
3927
|
console.error(` >> Evidencia guardada: ${blankEvidencePath}`);
|
|
3467
|
-
console.error(` >> Tipo: portal_rendering_failure | Esto es un bug del portal, NO
|
|
3468
|
-
response.thought = `\u{1F6D1} [BUG DEL PORTAL DETECTADO] La p\xE1gina ${blankUrl} carg\xF3 su estructura pero los componentes principales (widgets, tablas, listas) NO se renderizaron.
|
|
3928
|
+
console.error(` >> Tipo: portal_rendering_failure | Esto es un bug del portal, NO de Arcality.`);
|
|
3929
|
+
response.thought = `\u{1F6D1} [BUG DEL PORTAL DETECTADO] La p\xE1gina ${blankUrl} carg\xF3 su estructura pero los componentes principales (widgets, tablas, listas) NO se renderizaron. Arcality estuvo esperando ${consecutiveWaits} turnos sin mejora. Esto es un fallo de renderizado del portal.`;
|
|
3469
3930
|
response.finish = true;
|
|
3470
3931
|
response.actions = [];
|
|
3471
3932
|
aiMarkedSuccess = false;
|
|
@@ -3547,7 +4008,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
3547
4008
|
const bodyText = await page.innerText("body").catch(() => "");
|
|
3548
4009
|
if (bodyText.match(/ligado|depende|no se puede/i)) {
|
|
3549
4010
|
console.log(`
|
|
3550
|
-
\u2728 [ESTANCADO]
|
|
4011
|
+
\u2728 [ESTANCADO] Arcality repite click pero el error ya es visible. Finalizando.`);
|
|
3551
4012
|
isFinished = true;
|
|
3552
4013
|
aiMarkedSuccess = true;
|
|
3553
4014
|
break;
|
|
@@ -3628,8 +4089,8 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
3628
4089
|
lastSeenErrorToast = fbText.trim();
|
|
3629
4090
|
captureValidationRule(fbText.trim(), `Acci\xF3n: click en "${desc}" en ${page.url()}`);
|
|
3630
4091
|
const isDuplicateError = /repetido|duplicado|ya existe|already exists/i.test(fbText);
|
|
3631
|
-
const correctionHint = isDuplicateError ? `
|
|
3632
|
-
history.push(`Turno ${stepCount}: \u{1F6D1} ERROR POST-
|
|
4092
|
+
const correctionHint = isDuplicateError ? `ACCI\xD3N REQUERIDA: Cambia el valor duplicado por uno \xFAnico (agrega _${Date.now().toString().slice(-4)}).` : `ANOMAL\xCDA UI DETECTADA: Si este error previene que la misi\xF3n se complete, usa 'finish: true' y reporta el bug. De lo contrario, corrige el dato.`;
|
|
4093
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} ERROR POST-ACCI\xD3N: "${fbText.trim().substring(0, 200)}". ${correctionHint}`);
|
|
3633
4094
|
}
|
|
3634
4095
|
}
|
|
3635
4096
|
}
|
|
@@ -3727,11 +4188,11 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
3727
4188
|
const panelCount = Math.min(panels.length, 6);
|
|
3728
4189
|
if (panelCount >= 2 && emptyPanelCount >= panelCount * 0.6) {
|
|
3729
4190
|
const blankUrl = page.url();
|
|
3730
|
-
const blankEvidencePath =
|
|
4191
|
+
const blankEvidencePath = path4.join(contextDir, `portal-blank-dashboard-${Date.now()}.png`);
|
|
3731
4192
|
await page.screenshot({ path: blankEvidencePath, fullPage: true }).catch(() => {
|
|
3732
4193
|
});
|
|
3733
4194
|
console.error(`
|
|
3734
|
-
\
|
|
4195
|
+
\u26A0 [VOIDWATCH SUBSYSTEM: Blank Dashboard Anomaly] El portal carg\xF3 el shell pero los widgets est\xE1n vac\xEDos.`);
|
|
3735
4196
|
console.error(` >> URL: ${blankUrl}`);
|
|
3736
4197
|
console.error(` >> ${emptyPanelCount} de ${panelCount} paneles est\xE1n en blanco.`);
|
|
3737
4198
|
console.error(` >> Tipo: portal_rendering_failure | Bug del portal confirmado.`);
|
|
@@ -3739,7 +4200,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
3739
4200
|
hasCriticalError = true;
|
|
3740
4201
|
failReason = "portal_rendering_failure";
|
|
3741
4202
|
isFinished = true;
|
|
3742
|
-
history.push(`Turno ${stepCount}: \u{1F6D1} [BLANK_DASHBOARD] El portal carg\xF3 la p\xE1gina ${blankUrl} pero los widgets/tablas NO se renderizaron (${emptyPanelCount}/${panelCount} paneles vac\xEDos). Esto es un BUG DEL PORTAL detectado autom\xE1ticamente por Arcality QA.
|
|
4203
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} [BLANK_DASHBOARD] El portal carg\xF3 la p\xE1gina ${blankUrl} pero los widgets/tablas NO se renderizaron (${emptyPanelCount}/${panelCount} paneles vac\xEDos). Esto es un BUG DEL PORTAL detectado autom\xE1ticamente por Arcality QA. Arcality NO puede continuar. Evidencia guardada: ${blankEvidencePath}`);
|
|
3743
4204
|
saveMissionResults();
|
|
3744
4205
|
break;
|
|
3745
4206
|
}
|
|
@@ -3768,19 +4229,19 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
3768
4229
|
const errMsg = e.message || "";
|
|
3769
4230
|
if (errMsg.includes("[BLANK_STATE_CRASH]")) {
|
|
3770
4231
|
console.error(`
|
|
3771
|
-
\
|
|
3772
|
-
console.error(` >> Causa: Error de renderizado del portal (White Screen).
|
|
4232
|
+
\u26A0 [VOIDWATCH SUBSYSTEM: Blank State Crash] El portal carg\xF3 en blanco despu\xE9s de la acci\xF3n "${step.description || step.action}".`);
|
|
4233
|
+
console.error(` >> Causa: Error de renderizado del portal (White Screen). Arcality no puede continuar.`);
|
|
3773
4234
|
hasCriticalError = true;
|
|
3774
4235
|
failReason = "portal_render_failure";
|
|
3775
4236
|
isFinished = true;
|
|
3776
|
-
history.push(`Turno ${stepCount}: \u{1F6D1} [BLANK_STATE_CRASH] El portal qued\xF3 en blanco tras navegar. Esto es un bug del portal, no
|
|
4237
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} [BLANK_STATE_CRASH] El portal qued\xF3 en blanco tras navegar. Esto es un bug del portal, no de Arcality.`);
|
|
3777
4238
|
saveMissionResults();
|
|
3778
4239
|
break;
|
|
3779
4240
|
}
|
|
3780
4241
|
const isPageClosed = errMsg.includes("Target page, context or browser has been closed") || errMsg.includes("page has been closed") || errMsg.includes("Session closed") || page.isClosed();
|
|
3781
4242
|
if (isPageClosed) {
|
|
3782
4243
|
console.error(`
|
|
3783
|
-
\
|
|
4244
|
+
\u26A0 [LAZARUS SUBSYSTEM: Critical Crash] La pesta\xF1a colaps\xF3 durante la acci\xF3n "${step.description || step.action}"`);
|
|
3784
4245
|
console.error(` >> Error: ${errMsg.substring(0, 150)}`);
|
|
3785
4246
|
console.error(` >> Tipo: portal_page_crash | El portal cerr\xF3 el contexto del navegador inesperadamente.`);
|
|
3786
4247
|
hasCriticalError = true;
|
|
@@ -3840,7 +4301,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
3840
4301
|
}
|
|
3841
4302
|
if (response.finish) {
|
|
3842
4303
|
if (!containsError) {
|
|
3843
|
-
console.log(">>ARCALITY_STATUS>> \u2705
|
|
4304
|
+
console.log(">>ARCALITY_STATUS>> \u2705 Arcality solicit\xF3 finalizar misi\xF3n.");
|
|
3844
4305
|
isFinished = true;
|
|
3845
4306
|
if (!response.actions || response.actions.length === 0) {
|
|
3846
4307
|
stepsData.push({
|
|
@@ -3883,13 +4344,13 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
3883
4344
|
isFinished = true;
|
|
3884
4345
|
saveMissionResults();
|
|
3885
4346
|
} else {
|
|
3886
|
-
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F
|
|
4347
|
+
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Arcality intent\xF3 finalizar pero se detect\xF3 un ERROR en alert/toast.`);
|
|
3887
4348
|
aiMarkedSuccess = false;
|
|
3888
4349
|
isFinished = false;
|
|
3889
|
-
history.push(`Turno ${stepCount}:
|
|
4350
|
+
history.push(`Turno ${stepCount}: Arcality intent\xF3 finalizar pero el sistema detect\xF3 un error en un toast/alerta visible.`);
|
|
3890
4351
|
}
|
|
3891
4352
|
} else {
|
|
3892
|
-
console.warn(">>ARCALITY_STATUS>> \u26A0\uFE0F
|
|
4353
|
+
console.warn(">>ARCALITY_STATUS>> \u26A0\uFE0F Arcality intent\xF3 finalizar pero hubo fallos en el turno. Continuando para investigaci\xF3n...");
|
|
3893
4354
|
isFinished = false;
|
|
3894
4355
|
}
|
|
3895
4356
|
}
|
|
@@ -3909,7 +4370,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
3909
4370
|
failReason = "portal_internal_error";
|
|
3910
4371
|
isFinished = true;
|
|
3911
4372
|
aiMarkedSuccess = false;
|
|
3912
|
-
history.push(`Turno ${stepCount}: \u{1F6D1} [ERROR_SIST\xC9MICO] El portal devolvi\xF3 un error de servidor en la UI: "${rawTxt.trim().substring(0, 200)}". Este error no puede ser corregido por
|
|
4373
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} [ERROR_SIST\xC9MICO] El portal devolvi\xF3 un error de servidor en la UI: "${rawTxt.trim().substring(0, 200)}". Este error no puede ser corregido por Arcality. Requiere intervenci\xF3n en el backend.`);
|
|
3913
4374
|
saveMissionResults();
|
|
3914
4375
|
break;
|
|
3915
4376
|
} else if (failureKeywords.test(txt)) {
|
|
@@ -4008,8 +4469,8 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
4008
4469
|
console.log("\u{1F916} [SMART-YAML] Generating mission card...");
|
|
4009
4470
|
const smartYaml = await agent.summarizeMissionToYaml(prompt, history, target);
|
|
4010
4471
|
if (smartYaml) {
|
|
4011
|
-
const yamlPath =
|
|
4012
|
-
|
|
4472
|
+
const yamlPath = path4.join(contextDir, "last-mission-smart.yaml");
|
|
4473
|
+
fs4.writeFileSync(yamlPath, smartYaml);
|
|
4013
4474
|
console.log(`>>ARCALITY_STATUS>> \u{1F4DD} Smart Mission Card generated.`);
|
|
4014
4475
|
await testInfo.attach("smart_mission_card", {
|
|
4015
4476
|
body: Buffer.from(smartYaml, "utf-8"),
|
|
@@ -4017,36 +4478,40 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
4017
4478
|
});
|
|
4018
4479
|
}
|
|
4019
4480
|
}
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
const
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4481
|
+
if (!page.isClosed()) {
|
|
4482
|
+
try {
|
|
4483
|
+
const scanner = new SecurityScanner(page);
|
|
4484
|
+
const securityReport = await scanner.runAllScans();
|
|
4485
|
+
if (securityReport && securityReport.length > 0) {
|
|
4486
|
+
await testInfo.attach("security_report", {
|
|
4487
|
+
body: JSON.stringify(securityReport, null, 2),
|
|
4488
|
+
contentType: "application/json"
|
|
4489
|
+
});
|
|
4490
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F6E1}\uFE0F Escaneo de seguridad completado. ${securityReport.length} problemas encontrados.`);
|
|
4491
|
+
try {
|
|
4492
|
+
for (const v of securityReport) {
|
|
4493
|
+
const sev = v.severity || "Info";
|
|
4494
|
+
const mappedSeverity = sev === "Critical" ? "critical" : sev === "High" ? "important" : "suggestion";
|
|
4495
|
+
await pushRule({
|
|
4496
|
+
rule_type: "SECURITY",
|
|
4497
|
+
title: `Security: ${v.category} (${v.severity})`,
|
|
4498
|
+
description: `${v.description}
|
|
4037
4499
|
Evidence: ${JSON.stringify(v.evidence).substring(0, 400)}`,
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4500
|
+
severity: mappedSeverity
|
|
4501
|
+
}).catch(() => {
|
|
4502
|
+
});
|
|
4503
|
+
}
|
|
4504
|
+
} catch (e) {
|
|
4505
|
+
console.warn(">>ARCALITY_STATUS>> \u26A0\uFE0F Fall\xF3 persistir hallazgos de seguridad en Memoria Colectiva.");
|
|
4041
4506
|
}
|
|
4042
|
-
}
|
|
4043
|
-
console.
|
|
4507
|
+
} else {
|
|
4508
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F6E1}\uFE0F Escaneo de seguridad completado. No se encontraron problemas.`);
|
|
4044
4509
|
}
|
|
4045
|
-
}
|
|
4046
|
-
console.
|
|
4510
|
+
} catch (e) {
|
|
4511
|
+
console.error("Error in Security Scan:", e.message);
|
|
4047
4512
|
}
|
|
4048
|
-
}
|
|
4049
|
-
console.
|
|
4513
|
+
} else {
|
|
4514
|
+
console.warn(">>ARCALITY_STATUS>> \u{1F6E1}\uFE0F Escaneo de seguridad omitido porque el navegador o la pesta\xF1a colaps\xF3/cerr\xF3.");
|
|
4050
4515
|
}
|
|
4051
4516
|
if (!aiMarkedSuccess && !failReason) {
|
|
4052
4517
|
if (stepCount >= maxSteps)
|
|
@@ -4067,7 +4532,7 @@ Evidence: ${JSON.stringify(v.evidence).substring(0, 400)}`,
|
|
|
4067
4532
|
finalErrorMsg += `\u{1F3AF} TIPO DE FALLO: ${failReason}
|
|
4068
4533
|
`;
|
|
4069
4534
|
if (failReasoning) {
|
|
4070
|
-
finalErrorMsg += `\u{1F9E0} AN\xC1LISIS DEL
|
|
4535
|
+
finalErrorMsg += `\u{1F9E0} AN\xC1LISIS DEL ARCALITY QA:
|
|
4071
4536
|
${failReasoning}
|
|
4072
4537
|
`;
|
|
4073
4538
|
}
|
|
@@ -4077,15 +4542,15 @@ ${failReasoning}
|
|
|
4077
4542
|
} else {
|
|
4078
4543
|
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}`);
|
|
4079
4544
|
}
|
|
4080
|
-
if (
|
|
4545
|
+
if (fs4.existsSync(contextDir)) {
|
|
4081
4546
|
try {
|
|
4082
|
-
const files =
|
|
4547
|
+
const files = fs4.readdirSync(contextDir);
|
|
4083
4548
|
for (const file of files) {
|
|
4084
4549
|
if (file.endsWith(".json") || file.endsWith(".png")) {
|
|
4085
|
-
|
|
4550
|
+
fs4.unlinkSync(path4.join(contextDir, file));
|
|
4086
4551
|
}
|
|
4087
4552
|
}
|
|
4088
|
-
|
|
4553
|
+
fs4.rmSync(contextDir, { recursive: true, force: true });
|
|
4089
4554
|
console.log(`>>ARCALITY_STATUS>> \u{1F9F9} Carpeta context eliminada (Memoria ef\xEDmera completada).`);
|
|
4090
4555
|
} catch (err) {
|
|
4091
4556
|
console.warn(`No se pudo limpiar la carpeta context: ${err}`);
|