@arcadialdev/arcality 2.6.7 → 3.0.1
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 +1045 -249
- package/scripts/init.mjs +221 -178
- package/src/arcalityClient.mjs +128 -1
- package/src/configLoader.mjs +6 -2
- package/src/configManager.mjs +1 -1
- package/tests/_helpers/ArcalityReporter.js +234 -251
- package/tests/_helpers/agentic-runner.bundle.spec.js +796 -109
|
@@ -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 fs4 = __toESM(require("fs"));
|
|
819
|
+
var path4 = __toESM(require("path"));
|
|
420
820
|
|
|
421
821
|
// src/KnowledgeService.ts
|
|
422
822
|
var crypto = __toESM(require("crypto"));
|
|
@@ -863,6 +1263,189 @@ async function getKnownFieldIdentifiers(pathUrl) {
|
|
|
863
1263
|
}
|
|
864
1264
|
}
|
|
865
1265
|
|
|
1266
|
+
// tests/_helpers/qa-context.ts
|
|
1267
|
+
var fs = __toESM(require("fs"));
|
|
1268
|
+
var path = __toESM(require("path"));
|
|
1269
|
+
var RECOGNIZED_SECTION_PATTERNS = [
|
|
1270
|
+
/reglas?.*(dominio|negocio)|dominio|negocio|general/i,
|
|
1271
|
+
/navegacion|ui|interfaz|flujo/i,
|
|
1272
|
+
/prohibid|anti-patron|anti patron|nunca/i,
|
|
1273
|
+
/datos|credenciales|usuarios?|cuentas?/i,
|
|
1274
|
+
/resultado|validacion|assert|exito|esperad/i
|
|
1275
|
+
];
|
|
1276
|
+
var PLACEHOLDER_PATTERNS = [
|
|
1277
|
+
/^ejemplo:/i,
|
|
1278
|
+
/^\(ejemplo/i,
|
|
1279
|
+
/^reemplaza /i,
|
|
1280
|
+
/^describe /i,
|
|
1281
|
+
/^agrega /i,
|
|
1282
|
+
/^completa /i,
|
|
1283
|
+
/^escribe /i,
|
|
1284
|
+
/^si no aplica/i,
|
|
1285
|
+
/^borra /i,
|
|
1286
|
+
/^pendiente$/i,
|
|
1287
|
+
/^\.\.\.$/
|
|
1288
|
+
];
|
|
1289
|
+
function stripHtmlComments(content) {
|
|
1290
|
+
return content.replace(/<!--[\s\S]*?-->/g, "");
|
|
1291
|
+
}
|
|
1292
|
+
function normalizeLine(line) {
|
|
1293
|
+
return line.replace(/\t/g, " ").trim();
|
|
1294
|
+
}
|
|
1295
|
+
function isBulletLine(line) {
|
|
1296
|
+
return /^[-*+]\s+/.test(line) || /^\d+\.\s+/.test(line);
|
|
1297
|
+
}
|
|
1298
|
+
function toBulletText(line) {
|
|
1299
|
+
return line.replace(/^[-*+]\s+/, "").replace(/^\d+\.\s+/, "").trim();
|
|
1300
|
+
}
|
|
1301
|
+
function isPlaceholderRule(rule) {
|
|
1302
|
+
const normalized = normalizeLine(rule);
|
|
1303
|
+
if (!normalized)
|
|
1304
|
+
return true;
|
|
1305
|
+
return PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
1306
|
+
}
|
|
1307
|
+
function isRecognizedSection(title) {
|
|
1308
|
+
return RECOGNIZED_SECTION_PATTERNS.some((pattern) => pattern.test(title));
|
|
1309
|
+
}
|
|
1310
|
+
function analyzeQaContext(projectRoot = process.cwd()) {
|
|
1311
|
+
const filePath = path.join(projectRoot, ".arcality", "qa-context.md");
|
|
1312
|
+
const emptyResult = {
|
|
1313
|
+
filePath,
|
|
1314
|
+
exists: false,
|
|
1315
|
+
rawContent: "",
|
|
1316
|
+
sanitizedContent: "",
|
|
1317
|
+
sections: [],
|
|
1318
|
+
ruleCount: 0,
|
|
1319
|
+
sectionCount: 0,
|
|
1320
|
+
recognizedSectionCount: 0,
|
|
1321
|
+
ignoredExampleCount: 0,
|
|
1322
|
+
warnings: [],
|
|
1323
|
+
status: "missing",
|
|
1324
|
+
isValid: false
|
|
1325
|
+
};
|
|
1326
|
+
if (!fs.existsSync(filePath)) {
|
|
1327
|
+
return emptyResult;
|
|
1328
|
+
}
|
|
1329
|
+
const rawContent = fs.readFileSync(filePath, "utf8");
|
|
1330
|
+
const cleanedContent = stripHtmlComments(rawContent);
|
|
1331
|
+
const lines = cleanedContent.split(/\r?\n/);
|
|
1332
|
+
const warnings = [];
|
|
1333
|
+
const sections = [];
|
|
1334
|
+
let currentSection = null;
|
|
1335
|
+
let ignoredExampleCount = 0;
|
|
1336
|
+
const openSection = (title) => {
|
|
1337
|
+
currentSection = {
|
|
1338
|
+
title: title.trim(),
|
|
1339
|
+
recognized: isRecognizedSection(title),
|
|
1340
|
+
rules: []
|
|
1341
|
+
};
|
|
1342
|
+
sections.push(currentSection);
|
|
1343
|
+
};
|
|
1344
|
+
for (const rawLine of lines) {
|
|
1345
|
+
const line = normalizeLine(rawLine);
|
|
1346
|
+
if (!line)
|
|
1347
|
+
continue;
|
|
1348
|
+
if (/^##\s+/.test(line)) {
|
|
1349
|
+
openSection(line.replace(/^##\s+/, "").trim());
|
|
1350
|
+
continue;
|
|
1351
|
+
}
|
|
1352
|
+
if (!isBulletLine(line))
|
|
1353
|
+
continue;
|
|
1354
|
+
if (!currentSection) {
|
|
1355
|
+
openSection("Reglas sin seccion");
|
|
1356
|
+
}
|
|
1357
|
+
const rule = toBulletText(line);
|
|
1358
|
+
if (isPlaceholderRule(rule)) {
|
|
1359
|
+
ignoredExampleCount += 1;
|
|
1360
|
+
continue;
|
|
1361
|
+
}
|
|
1362
|
+
if (currentSection) {
|
|
1363
|
+
currentSection.rules.push(rule);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
const nonEmptySections = sections.filter((section) => section.rules.length > 0);
|
|
1367
|
+
const ruleCount = nonEmptySections.reduce((total, section) => total + section.rules.length, 0);
|
|
1368
|
+
const recognizedSectionCount = nonEmptySections.filter((section) => section.recognized).length;
|
|
1369
|
+
if (!rawContent.trim()) {
|
|
1370
|
+
warnings.push("El archivo existe pero esta vacio.");
|
|
1371
|
+
}
|
|
1372
|
+
if (sections.length === 0 && rawContent.trim()) {
|
|
1373
|
+
warnings.push('No se detectaron secciones markdown con encabezados "##".');
|
|
1374
|
+
}
|
|
1375
|
+
if (ruleCount === 0) {
|
|
1376
|
+
if (ignoredExampleCount > 0) {
|
|
1377
|
+
warnings.push("Solo se detectaron ejemplos o placeholders. Reemplaza los bullets de ejemplo por reglas reales.");
|
|
1378
|
+
} else {
|
|
1379
|
+
warnings.push("No se detectaron reglas accionables. Agrega al menos un bullet real en alguna seccion.");
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
if (ruleCount > 0 && recognizedSectionCount === 0) {
|
|
1383
|
+
warnings.push("Hay reglas, pero ninguna cae en las secciones sugeridas. Revisa la estructura del archivo.");
|
|
1384
|
+
}
|
|
1385
|
+
const sanitizedContent = nonEmptySections.map((section) => {
|
|
1386
|
+
const rules = section.rules.map((rule) => `- ${rule}`).join("\n");
|
|
1387
|
+
return `## ${section.title}
|
|
1388
|
+
${rules}`;
|
|
1389
|
+
}).join("\n\n");
|
|
1390
|
+
let status = "ready";
|
|
1391
|
+
if (!rawContent.trim())
|
|
1392
|
+
status = "empty";
|
|
1393
|
+
else if (ruleCount === 0 && ignoredExampleCount > 0)
|
|
1394
|
+
status = "template-only";
|
|
1395
|
+
else if (warnings.length > 0)
|
|
1396
|
+
status = "warning";
|
|
1397
|
+
return {
|
|
1398
|
+
filePath,
|
|
1399
|
+
exists: true,
|
|
1400
|
+
rawContent,
|
|
1401
|
+
sanitizedContent,
|
|
1402
|
+
sections: nonEmptySections,
|
|
1403
|
+
ruleCount,
|
|
1404
|
+
sectionCount: nonEmptySections.length,
|
|
1405
|
+
recognizedSectionCount,
|
|
1406
|
+
ignoredExampleCount,
|
|
1407
|
+
warnings,
|
|
1408
|
+
status,
|
|
1409
|
+
isValid: ruleCount > 0
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
function loadQaContextForPrompt(projectRoot = process.cwd()) {
|
|
1413
|
+
const analysis = analyzeQaContext(projectRoot);
|
|
1414
|
+
return {
|
|
1415
|
+
analysis,
|
|
1416
|
+
promptBlock: analysis.isValid ? analysis.sanitizedContent : ""
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
function formatQaContextSummary(analysis) {
|
|
1420
|
+
const lines = [];
|
|
1421
|
+
lines.push("QA Context Summary");
|
|
1422
|
+
lines.push(`Status: ${analysis.status}`);
|
|
1423
|
+
lines.push(`Path: ${analysis.filePath}`);
|
|
1424
|
+
lines.push(`Rules loaded: ${analysis.ruleCount}`);
|
|
1425
|
+
lines.push(`Sections with rules: ${analysis.sectionCount}`);
|
|
1426
|
+
lines.push(`Recognized sections: ${analysis.recognizedSectionCount}`);
|
|
1427
|
+
lines.push(`Ignored examples/placeholders: ${analysis.ignoredExampleCount}`);
|
|
1428
|
+
if (analysis.sections.length > 0) {
|
|
1429
|
+
lines.push("");
|
|
1430
|
+
lines.push("Sections applied:");
|
|
1431
|
+
for (const section of analysis.sections) {
|
|
1432
|
+
lines.push(`- ${section.title}: ${section.rules.length} rule(s)`);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
if (analysis.warnings.length > 0) {
|
|
1436
|
+
lines.push("");
|
|
1437
|
+
lines.push("Warnings:");
|
|
1438
|
+
for (const warning of analysis.warnings) {
|
|
1439
|
+
lines.push(`- ${warning}`);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
if (!analysis.exists) {
|
|
1443
|
+
lines.push("");
|
|
1444
|
+
lines.push("No local QA Context file was found for this mission.");
|
|
1445
|
+
}
|
|
1446
|
+
return lines.join("\n");
|
|
1447
|
+
}
|
|
1448
|
+
|
|
866
1449
|
// tests/_helpers/ai-agent-helper.ts
|
|
867
1450
|
var qaSecurityTools = [
|
|
868
1451
|
{
|
|
@@ -911,6 +1494,9 @@ var AIAgentHelper = class {
|
|
|
911
1494
|
sessionStorage = /* @__PURE__ */ new Map();
|
|
912
1495
|
criticalNetworkError = null;
|
|
913
1496
|
criticalJsError = null;
|
|
1497
|
+
qaContextAnalysisCache = null;
|
|
1498
|
+
qaContextPromptCache = "";
|
|
1499
|
+
qaContextStatusLogged = false;
|
|
914
1500
|
constructor(page, contextDir = "out", testInfo, knowledgeService) {
|
|
915
1501
|
this.page = page;
|
|
916
1502
|
this.contextDir = contextDir;
|
|
@@ -925,16 +1511,20 @@ var AIAgentHelper = class {
|
|
|
925
1511
|
});
|
|
926
1512
|
this.page.on("requestfailed", (request) => {
|
|
927
1513
|
const failure = request.failure();
|
|
1514
|
+
const errorText = failure?.errorText || "Aborted";
|
|
1515
|
+
if (errorText.includes("net::ERR_ABORTED") || errorText === "Aborted") {
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
928
1518
|
const resourceType = request.resourceType();
|
|
929
1519
|
const url = request.url();
|
|
930
1520
|
if (["fetch", "xhr", "document"].includes(resourceType)) {
|
|
931
1521
|
if (!url.includes("google") && !url.includes("analytics") && !url.includes("telemetry")) {
|
|
932
1522
|
if (resourceType === "document" || request.method() !== "GET") {
|
|
933
|
-
this.criticalNetworkError = `Network Request Failed (${request.method()}): ${url} - ${
|
|
1523
|
+
this.criticalNetworkError = `Network Request Failed (${request.method()}): ${url} - ${errorText}`;
|
|
934
1524
|
}
|
|
935
1525
|
}
|
|
936
1526
|
}
|
|
937
|
-
this.logs.push(`[NETWORK_ERROR] ${request.method()} ${request.url()} - ${
|
|
1527
|
+
this.logs.push(`[NETWORK_ERROR] ${request.method()} ${request.url()} - ${errorText}`);
|
|
938
1528
|
if (this.logs.length > 30)
|
|
939
1529
|
this.logs.shift();
|
|
940
1530
|
});
|
|
@@ -968,29 +1558,29 @@ var AIAgentHelper = class {
|
|
|
968
1558
|
try {
|
|
969
1559
|
let skillsContext = "";
|
|
970
1560
|
let loadedCount = 0;
|
|
971
|
-
const toolsRoot = process.env.ARCALITY_ROOT ||
|
|
1561
|
+
const toolsRoot = process.env.ARCALITY_ROOT || path4.join(__dirname, "..", "..");
|
|
972
1562
|
const possibleDirs = [
|
|
973
|
-
|
|
974
|
-
|
|
1563
|
+
path4.join(toolsRoot, ".agent", "skills"),
|
|
1564
|
+
path4.join(toolsRoot, ".agents", "skills")
|
|
975
1565
|
];
|
|
976
1566
|
for (const rootDir of possibleDirs) {
|
|
977
|
-
if (!
|
|
1567
|
+
if (!fs4.existsSync(rootDir))
|
|
978
1568
|
continue;
|
|
979
1569
|
if (loadedCount === 0) {
|
|
980
1570
|
skillsContext += "\n\u{1F6E0}\uFE0F SKILLS INSTALADAS (Metodolog\xEDas activas):\n";
|
|
981
1571
|
}
|
|
982
|
-
const entries =
|
|
1572
|
+
const entries = fs4.readdirSync(rootDir, { withFileTypes: true });
|
|
983
1573
|
for (const entry of entries) {
|
|
984
1574
|
let content = "";
|
|
985
1575
|
let skillName = entry.name;
|
|
986
1576
|
if (entry.isDirectory()) {
|
|
987
|
-
const skillPath =
|
|
988
|
-
if (
|
|
989
|
-
content =
|
|
1577
|
+
const skillPath = path4.join(rootDir, entry.name, "SKILL.md");
|
|
1578
|
+
if (fs4.existsSync(skillPath)) {
|
|
1579
|
+
content = fs4.readFileSync(skillPath, "utf8");
|
|
990
1580
|
}
|
|
991
1581
|
} else if (entry.name.endsWith(".md")) {
|
|
992
|
-
const skillPath =
|
|
993
|
-
content =
|
|
1582
|
+
const skillPath = path4.join(rootDir, entry.name);
|
|
1583
|
+
content = fs4.readFileSync(skillPath, "utf8");
|
|
994
1584
|
}
|
|
995
1585
|
if (content) {
|
|
996
1586
|
skillsContext += `
|
|
@@ -1013,9 +1603,9 @@ ${content}
|
|
|
1013
1603
|
}
|
|
1014
1604
|
loadMemory(prompt) {
|
|
1015
1605
|
try {
|
|
1016
|
-
const memoryFile =
|
|
1017
|
-
if (
|
|
1018
|
-
const memories = JSON.parse(
|
|
1606
|
+
const memoryFile = path4.join(this.contextDir, "memoria-agente.json");
|
|
1607
|
+
if (fs4.existsSync(memoryFile)) {
|
|
1608
|
+
const memories = JSON.parse(fs4.readFileSync(memoryFile, "utf8"));
|
|
1019
1609
|
const keywords = prompt.toLowerCase().split(" ").filter((w) => w.length > 4);
|
|
1020
1610
|
const relevantSuccess = memories.filter((m) => {
|
|
1021
1611
|
if (!m.success)
|
|
@@ -1288,7 +1878,7 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
1288
1878
|
if (bodyTextLength2 < 80 && components2 < 10) {
|
|
1289
1879
|
console.error(`
|
|
1290
1880
|
\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
|
|
1881
|
+
throw new Error(`[BLANK_STATE_CRASH] El portal carg\xF3 en blanco. Esto es un fallo de renderizado del portal, no de Arcality.`);
|
|
1292
1882
|
} else {
|
|
1293
1883
|
console.log(`>>ARCALITY_STATUS>> \u2705 [BLANK_STATE] Falsa alarma. P\xE1gina recuper\xF3 contenido tras espera (${components2} comps, ${bodyTextLength2} chars).`);
|
|
1294
1884
|
}
|
|
@@ -1386,13 +1976,13 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
1386
1976
|
*/
|
|
1387
1977
|
async getActionFromGuia(prompt, historyLength) {
|
|
1388
1978
|
try {
|
|
1389
|
-
const
|
|
1390
|
-
const
|
|
1391
|
-
const memoryFile =
|
|
1392
|
-
if (!
|
|
1979
|
+
const fs6 = require("fs");
|
|
1980
|
+
const path6 = require("path");
|
|
1981
|
+
const memoryFile = path6.join(this.contextDir, "memoria-agente.json");
|
|
1982
|
+
if (!fs6.existsSync(memoryFile)) {
|
|
1393
1983
|
return null;
|
|
1394
1984
|
}
|
|
1395
|
-
const memories = JSON.parse(
|
|
1985
|
+
const memories = JSON.parse(fs6.readFileSync(memoryFile, "utf8"));
|
|
1396
1986
|
const state = await this.getPageState();
|
|
1397
1987
|
const currentUrl = this.page.url();
|
|
1398
1988
|
const extractCriticalKeywords = (text) => {
|
|
@@ -1678,11 +2268,11 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
1678
2268
|
}
|
|
1679
2269
|
let customUserContext = "";
|
|
1680
2270
|
try {
|
|
1681
|
-
const
|
|
1682
|
-
const
|
|
1683
|
-
const customContextPath =
|
|
1684
|
-
if (
|
|
1685
|
-
const content =
|
|
2271
|
+
const fs6 = require("fs");
|
|
2272
|
+
const path6 = require("path");
|
|
2273
|
+
const customContextPath = path6.join(process.cwd(), ".arcality", "qa-context.md");
|
|
2274
|
+
if (false) {
|
|
2275
|
+
const content = fs6.readFileSync(customContextPath, "utf8");
|
|
1686
2276
|
customUserContext = `
|
|
1687
2277
|
<CUSTOMER_BUSINESS_RULES>
|
|
1688
2278
|
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:
|
|
@@ -1692,6 +2282,38 @@ ${content}
|
|
|
1692
2282
|
}
|
|
1693
2283
|
} catch (e) {
|
|
1694
2284
|
}
|
|
2285
|
+
try {
|
|
2286
|
+
if (!this.qaContextAnalysisCache) {
|
|
2287
|
+
const qaContextState = loadQaContextForPrompt(process.cwd());
|
|
2288
|
+
this.qaContextAnalysisCache = qaContextState.analysis;
|
|
2289
|
+
this.qaContextPromptCache = qaContextState.promptBlock;
|
|
2290
|
+
}
|
|
2291
|
+
const qaContextAnalysis = this.qaContextAnalysisCache;
|
|
2292
|
+
if (!this.qaContextStatusLogged) {
|
|
2293
|
+
if (qaContextAnalysis.exists && this.qaContextPromptCache) {
|
|
2294
|
+
console.log(`>>ARCALITY_STATUS>> QA Context local inyectado (${qaContextAnalysis.ruleCount} reglas, ${qaContextAnalysis.sectionCount} secciones).`);
|
|
2295
|
+
} else if (qaContextAnalysis.exists) {
|
|
2296
|
+
console.log(`>>ARCALITY_STATUS>> QA Context local detectado, pero sin reglas utilizables.`);
|
|
2297
|
+
}
|
|
2298
|
+
if (qaContextAnalysis.warnings.length > 0) {
|
|
2299
|
+
for (const warning of qaContextAnalysis.warnings) {
|
|
2300
|
+
console.log(`>>ARCALITY_STATUS>> QA Context warning: ${warning}`);
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
this.qaContextStatusLogged = true;
|
|
2304
|
+
}
|
|
2305
|
+
if (this.qaContextPromptCache) {
|
|
2306
|
+
customUserContext = `
|
|
2307
|
+
<CUSTOMER_BUSINESS_RULES>
|
|
2308
|
+
El Ing. de QA o Cliente final ha provisto reglas locales para este negocio/dominio. Debes priorizar esta informacion sobre tus heuristicas base cuando exista conflicto.
|
|
2309
|
+
${this.qaContextPromptCache}
|
|
2310
|
+
</CUSTOMER_BUSINESS_RULES>
|
|
2311
|
+
`;
|
|
2312
|
+
} else {
|
|
2313
|
+
customUserContext = "";
|
|
2314
|
+
}
|
|
2315
|
+
} catch (e) {
|
|
2316
|
+
}
|
|
1695
2317
|
const systemPromptBlocks = [
|
|
1696
2318
|
{
|
|
1697
2319
|
type: "text",
|
|
@@ -1788,9 +2410,10 @@ These are NOT optional. You MUST use these tools in these situations:
|
|
|
1788
2410
|
- 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
2411
|
1. You MUST identify WHICH field caused the duplication (usually the main name/title field).
|
|
1790
2412
|
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.
|
|
2413
|
+
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.
|
|
2414
|
+
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.
|
|
2415
|
+
5. **NEVER attempt to save with the same value that was already rejected.** This is FORBIDDEN.
|
|
2416
|
+
6. If you cannot determine which field is the duplicate, use \`capture_console_errors\` to get hints from the application.
|
|
1794
2417
|
7. **DATA UNIQUENESS**: When generating test data, ALWAYS include a unique suffix (timestamp, counter). Never use "Test123" or "Prueba" alone.
|
|
1795
2418
|
8. **FINISH CORRECTLY**: Use \`finish: true\` ONLY when the mission objective is FULLY achieved.
|
|
1796
2419
|
- **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 +2424,7 @@ These are NOT optional. You MUST use these tools in these situations:
|
|
|
1801
2424
|
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
2425
|
12. **GUIDE CONFLICT**: If SUCCESS MEMORY disagrees with what you see on screen, ABANDON the memory and trust your eyes.
|
|
1803
2426
|
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.
|
|
2427
|
+
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
2428
|
|
|
1805
2429
|
# WIZARD & MULTI-STEP FORMS (CRITICAL)
|
|
1806
2430
|
When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 Step 3):
|
|
@@ -1841,7 +2465,7 @@ When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 St
|
|
|
1841
2465
|
|
|
1842
2466
|
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
2467
|
|
|
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)
|
|
2468
|
+
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
2469
|
}
|
|
1846
2470
|
];
|
|
1847
2471
|
if (this.testInfo) {
|
|
@@ -1901,7 +2525,7 @@ ${history.slice(-25).join("\n") || "None"}`
|
|
|
1901
2525
|
const isProxyMode = !!process.env.ARCALITY_API_URL;
|
|
1902
2526
|
const endpointUrl = isProxyMode ? `${process.env.ARCALITY_API_URL}/api/v1/ai/proxy` : "https://api.anthropic.com/v1/messages";
|
|
1903
2527
|
const displayTurn = stepCount !== void 0 ? stepCount : turn + 1;
|
|
1904
|
-
console.log(`>>ARCALITY_STATUS>> \u{1F4E1} Llamando a: ${endpointUrl} (turno IA ${displayTurn}${turn > 0 ? `,
|
|
2528
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F4E1} Llamando a: ${endpointUrl} (turno IA ${displayTurn}${turn > 0 ? `, ciclo interno ${turn + 1}` : ""})`);
|
|
1905
2529
|
const headers = {
|
|
1906
2530
|
"Content-Type": "application/json"
|
|
1907
2531
|
};
|
|
@@ -1973,7 +2597,7 @@ ${history.slice(-25).join("\n") || "None"}`
|
|
|
1973
2597
|
const textResponse = message.content.find((c) => c.type === "text")?.text || "";
|
|
1974
2598
|
if (toolCalls.length === 0) {
|
|
1975
2599
|
console.log(`(${((Date.now() - startTime) / 1e3).toFixed(1)}s)`);
|
|
1976
|
-
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F
|
|
2600
|
+
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Arcality devolvi\xF3 texto sin acci\xF3n. Reinyectando instrucci\xF3n mandatoria...`);
|
|
1977
2601
|
anthropicMessages.push({
|
|
1978
2602
|
role: "user",
|
|
1979
2603
|
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 +2653,43 @@ ${history.slice(-25).join("\n") || "None"}`
|
|
|
2029
2653
|
);
|
|
2030
2654
|
} catch (e) {
|
|
2031
2655
|
}
|
|
2656
|
+
try {
|
|
2657
|
+
const { createAdoTask: createAdoTask2 } = await Promise.resolve().then(() => (init_arcalityClient(), arcalityClient_exports));
|
|
2658
|
+
const shortDesc = toolInput.bug_description.length > 80 ? toolInput.bug_description.substring(0, 80).trimEnd() + "..." : toolInput.bug_description;
|
|
2659
|
+
const bugTitle = `[QA Bug] ${shortDesc}`;
|
|
2660
|
+
const bugDescription = [
|
|
2661
|
+
`**Bug detectado autom\xE1ticamente por Arcality QA**`,
|
|
2662
|
+
``,
|
|
2663
|
+
`**Descripci\xF3n:** ${toolInput.bug_description}`,
|
|
2664
|
+
`**Comportamiento esperado:** ${toolInput.expected_behavior}`,
|
|
2665
|
+
`**Comportamiento actual:** ${toolInput.actual_behavior}`,
|
|
2666
|
+
`**URL afectada:** ${this.page.url()}`,
|
|
2667
|
+
`**Fecha:** ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
2668
|
+
].join("\n");
|
|
2669
|
+
createAdoTask2(bugTitle, bugDescription).catch(() => {
|
|
2670
|
+
});
|
|
2671
|
+
} catch (e) {
|
|
2672
|
+
}
|
|
2032
2673
|
throw new Error(`APPLICATION BUG: ${toolInput.bug_description}`);
|
|
2033
2674
|
} else if (toolName === "report_inability_to_proceed") {
|
|
2034
2675
|
toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: "Confusion acknowledged." });
|
|
2676
|
+
try {
|
|
2677
|
+
const { createAdoTask: createAdoTask2 } = await Promise.resolve().then(() => (init_arcalityClient(), arcalityClient_exports));
|
|
2678
|
+
const shortDesc = toolInput.bug_description.length > 80 ? toolInput.bug_description.substring(0, 80).trimEnd() + "..." : toolInput.bug_description;
|
|
2679
|
+
const bugTitle = `[QA Bug] ${shortDesc}`;
|
|
2680
|
+
const bugDescription = [
|
|
2681
|
+
`**Bug detectado autom\xE1ticamente por Arcality QA**`,
|
|
2682
|
+
``,
|
|
2683
|
+
`**Descripci\xF3n:** ${toolInput.bug_description}`,
|
|
2684
|
+
`**Comportamiento esperado:** ${toolInput.expected_behavior}`,
|
|
2685
|
+
`**Comportamiento actual:** ${toolInput.actual_behavior}`,
|
|
2686
|
+
`**URL afectada:** ${this.page.url()}`,
|
|
2687
|
+
`**Fecha:** ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
2688
|
+
].join("\n");
|
|
2689
|
+
createAdoTask2(bugTitle, bugDescription).catch(() => {
|
|
2690
|
+
});
|
|
2691
|
+
} catch (e) {
|
|
2692
|
+
}
|
|
2035
2693
|
return {
|
|
2036
2694
|
thought: `DISCULPA: ${toolInput.confusion_reason}
|
|
2037
2695
|
|
|
@@ -2770,8 +3428,8 @@ var SecurityScanner = class {
|
|
|
2770
3428
|
|
|
2771
3429
|
// tests/_helpers/agentic-runner.spec.ts
|
|
2772
3430
|
var import_config = require("dotenv/config");
|
|
2773
|
-
var
|
|
2774
|
-
var
|
|
3431
|
+
var fs5 = __toESM(require("fs"));
|
|
3432
|
+
var path5 = __toESM(require("path"));
|
|
2775
3433
|
var crypto2 = __toESM(require("crypto"));
|
|
2776
3434
|
var _sessionRuleCache = /* @__PURE__ */ new Set();
|
|
2777
3435
|
function captureValidationRule(errorMessage, context) {
|
|
@@ -2802,15 +3460,30 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2802
3460
|
const history = [];
|
|
2803
3461
|
const urlHistory = [];
|
|
2804
3462
|
const stepsData = [];
|
|
2805
|
-
const memoryFile =
|
|
2806
|
-
if (
|
|
3463
|
+
const memoryFile = path5.join(contextDir, "memoria-arcality.json");
|
|
3464
|
+
if (fs5.existsSync(memoryFile)) {
|
|
2807
3465
|
try {
|
|
2808
|
-
|
|
3466
|
+
fs5.unlinkSync(memoryFile);
|
|
2809
3467
|
} catch {
|
|
2810
3468
|
}
|
|
2811
3469
|
}
|
|
2812
3470
|
const agent = new AIAgentHelper(page, contextDir, testInfo);
|
|
2813
3471
|
const prompt = process.env.SMART_PROMPT || "Navega al inicio y verifica que el sitio cargue";
|
|
3472
|
+
const qaContextAnalysis = analyzeQaContext(process.cwd());
|
|
3473
|
+
if (qaContextAnalysis.exists && qaContextAnalysis.isValid) {
|
|
3474
|
+
console.log(`>>ARCALITY_STATUS>> QA Context validado al arranque (${qaContextAnalysis.ruleCount} reglas, ${qaContextAnalysis.sectionCount} secciones).`);
|
|
3475
|
+
} else if (qaContextAnalysis.exists) {
|
|
3476
|
+
console.log(`>>ARCALITY_STATUS>> QA Context detectado con advertencias.`);
|
|
3477
|
+
for (const warning of qaContextAnalysis.warnings) {
|
|
3478
|
+
console.log(`>>ARCALITY_STATUS>> QA Context warning: ${warning}`);
|
|
3479
|
+
}
|
|
3480
|
+
} else {
|
|
3481
|
+
console.log(`>>ARCALITY_STATUS>> QA Context local no configurado para esta mision.`);
|
|
3482
|
+
}
|
|
3483
|
+
await testInfo.attach("qa_context_summary", {
|
|
3484
|
+
body: Buffer.from(formatQaContextSummary(qaContextAnalysis), "utf-8"),
|
|
3485
|
+
contentType: "text/plain"
|
|
3486
|
+
});
|
|
2814
3487
|
const successKeywords = /exitosamente|creado correctamente|guardado correctamente|misión cumplida|registered successfully|created successfully|saved successfully|operación exitosa|registro guardado|successfully|correctly/i;
|
|
2815
3488
|
const failureKeywords = /\berror\b|falló|failed|no se puede|cannot|unable|ya existe|already exists|existente|exist|inválido|invalid|incorrecto|incorrect|ligado|linked|depende|depends|duplicado|duplicate|repetido|repeated|conflict|reintentar|retry|missing|required|obligatorio|bad request|400|500/i;
|
|
2816
3489
|
const systemErrorKeywords = /error interno|internal error|intentarlo más tarde|try again later|inténtelo más tarde|servicio no disponible|service unavailable|something went wrong|algo salió mal|ha ocurrido un error inesperado|unexpected error|server error|no se pudo procesar|could not be processed|comuníquese con soporte|contact support/i;
|
|
@@ -2833,14 +3506,14 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2833
3506
|
if (resultsSaved)
|
|
2834
3507
|
return;
|
|
2835
3508
|
resultsSaved = true;
|
|
2836
|
-
if (!
|
|
2837
|
-
|
|
3509
|
+
if (!fs5.existsSync(contextDir))
|
|
3510
|
+
fs5.mkdirSync(contextDir, { recursive: true });
|
|
2838
3511
|
const finalSuccess = aiMarkedSuccess && !hasCriticalError;
|
|
2839
3512
|
try {
|
|
2840
|
-
const memoryFile2 =
|
|
3513
|
+
const memoryFile2 = path5.join(contextDir, "memoria-arcality.json");
|
|
2841
3514
|
let memories = [];
|
|
2842
|
-
if (
|
|
2843
|
-
const content =
|
|
3515
|
+
if (fs5.existsSync(memoryFile2)) {
|
|
3516
|
+
const content = fs5.readFileSync(memoryFile2, "utf8").trim();
|
|
2844
3517
|
if (content) {
|
|
2845
3518
|
try {
|
|
2846
3519
|
memories = JSON.parse(content);
|
|
@@ -2914,16 +3587,16 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2914
3587
|
timestamp: Date.now()
|
|
2915
3588
|
};
|
|
2916
3589
|
memories.push(missionData);
|
|
2917
|
-
|
|
3590
|
+
fs5.writeFileSync(memoryFile2, JSON.stringify(memories, null, 2));
|
|
2918
3591
|
console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Memoria guardada: ${cleanSteps.length} pasos limpios (${rawSteps.length - cleanSteps.length} corruptos filtrados)`);
|
|
2919
3592
|
} catch (err) {
|
|
2920
3593
|
console.error("\u274C Fall\xF3 al guardar memoria:", err);
|
|
2921
3594
|
}
|
|
2922
3595
|
try {
|
|
2923
|
-
const logPath =
|
|
2924
|
-
|
|
3596
|
+
const logPath = path5.join(contextDir, `agent-log-${Date.now()}.json`);
|
|
3597
|
+
fs5.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));
|
|
2925
3598
|
try {
|
|
2926
|
-
|
|
3599
|
+
fs5.appendFileSync(path5.join(contextDir, "arcality-history.log"), JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), mission: prompt.substring(0, 150), success: finalSuccess, steps: stepCount, usage: totalMissionUsage }) + "\n");
|
|
2927
3600
|
} catch (e) {
|
|
2928
3601
|
}
|
|
2929
3602
|
} catch (err) {
|
|
@@ -2958,8 +3631,8 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2958
3631
|
});
|
|
2959
3632
|
console.log(`
|
|
2960
3633
|
======================================================`);
|
|
2961
|
-
console.log(`\u{1F9E0} [
|
|
2962
|
-
console.log(`>>arcality>>
|
|
3634
|
+
console.log(`\u{1F9E0} [ARCHIVE SUBSYSTEM - KNOWLEDGE INGESTION]`);
|
|
3635
|
+
console.log(`>>arcality>> Saving telemetry (project: ${pid}, steps: ${effectiveSteps.length}, isFailed: ${isFailed})...`);
|
|
2963
3636
|
console.log(`======================================================
|
|
2964
3637
|
`);
|
|
2965
3638
|
try {
|
|
@@ -2980,7 +3653,7 @@ function captureValidationRule(errorMessage, context) {
|
|
|
2980
3653
|
const ruleId = await pushRule({
|
|
2981
3654
|
rule_type: "NAVIGATION",
|
|
2982
3655
|
title: `Flujo de navegaci\xF3n: ${prompt.substring(0, 80)}`,
|
|
2983
|
-
description: `
|
|
3656
|
+
description: `Arcality naveg\xF3 ${uniqueUrls.length} p\xE1ginas: ${uniqueUrls.join(" \u2192 ").substring(0, 400)}`,
|
|
2984
3657
|
severity: "suggestion"
|
|
2985
3658
|
});
|
|
2986
3659
|
if (ruleId)
|
|
@@ -3003,7 +3676,7 @@ function captureValidationRule(errorMessage, context) {
|
|
|
3003
3676
|
const failRuleId = await pushRule({
|
|
3004
3677
|
rule_type: "VALIDATION",
|
|
3005
3678
|
title: `Misi\xF3n fallida: ${prompt.substring(0, 80)}`,
|
|
3006
|
-
description: `
|
|
3679
|
+
description: `Arcality no pudo completar: "${prompt.substring(0, 200)}". \xDAltimos pasos: ${stepsNarrative.substring(0, 300)}`,
|
|
3007
3680
|
severity: "important"
|
|
3008
3681
|
});
|
|
3009
3682
|
if (failRuleId)
|
|
@@ -3016,7 +3689,7 @@ function captureValidationRule(errorMessage, context) {
|
|
|
3016
3689
|
}
|
|
3017
3690
|
};
|
|
3018
3691
|
console.log(`
|
|
3019
|
-
\u{1F916} [
|
|
3692
|
+
\u{1F916} [ARCALITY] Iniciando misi\xF3n: "${prompt}"`);
|
|
3020
3693
|
const base = process.env.BASE_URL || "";
|
|
3021
3694
|
const target = process.env.TARGET_PATH || "/";
|
|
3022
3695
|
if (process.env.LOGIN_USER && process.env.LOGIN_PASSWORD) {
|
|
@@ -3086,10 +3759,19 @@ function captureValidationRule(errorMessage, context) {
|
|
|
3086
3759
|
} catch {
|
|
3087
3760
|
patternJsonObj = {};
|
|
3088
3761
|
}
|
|
3089
|
-
const
|
|
3762
|
+
const patternKeys = Object.keys(patternJsonObj);
|
|
3763
|
+
const bestMatchKeys = Object.keys(bestMatch);
|
|
3764
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F52C} [DIAGN\xD3STICO] Claves en bestMatch: [${bestMatchKeys.join(", ")}]`);
|
|
3765
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F52C} [DIAGN\xD3STICO] Claves en patternJsonObj: [${patternKeys.join(", ")}]`);
|
|
3766
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F52C} [DIAGN\xD3STICO] patternJsonObj.steps (tipo): ${typeof patternJsonObj.steps} | valor: ${JSON.stringify(patternJsonObj.steps)?.substring(0, 120)}`);
|
|
3767
|
+
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"}`);
|
|
3768
|
+
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
|
|
3769
|
+
(Array.isArray(patternJsonObj.steps) && patternJsonObj.steps.length > 0 && typeof patternJsonObj.steps[0] === "object" ? patternJsonObj.steps : null);
|
|
3090
3770
|
const resolvedStepsData = Array.isArray(rawStepsTechnical) ? rawStepsTechnical : [];
|
|
3091
3771
|
if (resolvedStepsData.length === 0) {
|
|
3092
|
-
console.warn(`>>ARCALITY_STATUS>> \u26A0\uFE0F Patr\xF3n descargado sin steps_technical v\xE1lidos. Modo Gu\xEDa no ser\xE1 activado.`);
|
|
3772
|
+
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.`);
|
|
3773
|
+
} else {
|
|
3774
|
+
console.log(`>>ARCALITY_STATUS>> \u2705 [DIAGN\xD3STICO] steps_technical resueltos: ${resolvedStepsData.length} pasos. Muestra del paso[0]: ${JSON.stringify(resolvedStepsData[0]).substring(0, 200)}`);
|
|
3093
3775
|
}
|
|
3094
3776
|
const localMemoryData = {
|
|
3095
3777
|
prompt,
|
|
@@ -3100,28 +3782,33 @@ function captureValidationRule(errorMessage, context) {
|
|
|
3100
3782
|
timestamp: Date.now()
|
|
3101
3783
|
};
|
|
3102
3784
|
try {
|
|
3103
|
-
const memoryFile2 =
|
|
3785
|
+
const memoryFile2 = path5.join(contextDir, "memoria-agente.json");
|
|
3104
3786
|
let memories = [];
|
|
3105
|
-
if (
|
|
3787
|
+
if (fs5.existsSync(memoryFile2)) {
|
|
3106
3788
|
try {
|
|
3107
|
-
memories = JSON.parse(
|
|
3789
|
+
memories = JSON.parse(fs5.readFileSync(memoryFile2, "utf8"));
|
|
3108
3790
|
} catch {
|
|
3109
3791
|
memories = [];
|
|
3110
3792
|
}
|
|
3111
3793
|
}
|
|
3112
3794
|
const normalizeForDedup = (s) => s.toLowerCase().replace(/\s+/g, " ").trim();
|
|
3113
3795
|
const normalizedCurrentPrompt = normalizeForDedup(prompt);
|
|
3114
|
-
const
|
|
3115
|
-
|
|
3796
|
+
const existingIdx = memories.findIndex((m) => normalizeForDedup(m.prompt || "") === normalizedCurrentPrompt);
|
|
3797
|
+
const existingHasSteps = existingIdx >= 0 && (memories[existingIdx].steps_data || []).length > 0;
|
|
3798
|
+
if (existingIdx < 0) {
|
|
3116
3799
|
if (resolvedStepsData.length > 0) {
|
|
3117
3800
|
memories.push(localMemoryData);
|
|
3118
|
-
|
|
3119
|
-
console.log(`>>ARCALITY_STATUS>> \u2728 Memoria sincronizada
|
|
3801
|
+
fs5.writeFileSync(memoryFile2, JSON.stringify(memories, null, 2));
|
|
3802
|
+
console.log(`>>ARCALITY_STATUS>> \u2728 Memoria sincronizada en memoria-agente.json (${resolvedStepsData.length} pasos). Arcality entrar\xE1 en MODO GU\xCDA.`);
|
|
3120
3803
|
} else {
|
|
3121
|
-
console.warn(`>>ARCALITY_STATUS>> \u26A0\uFE0F Memoria NO sincronizada: el patr\xF3n del backend tiene 0 pasos t\xE9cnicos v\xE1lidos.`);
|
|
3804
|
+
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).`);
|
|
3122
3805
|
}
|
|
3806
|
+
} else if (!existingHasSteps && resolvedStepsData.length > 0) {
|
|
3807
|
+
memories[existingIdx] = localMemoryData;
|
|
3808
|
+
fs5.writeFileSync(memoryFile2, JSON.stringify(memories, null, 2));
|
|
3809
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F504} Patr\xF3n corrupto (0 pasos) sobreescrito en memoria-agente.json (${resolvedStepsData.length} pasos nuevos). MODO GU\xCDA activado.`);
|
|
3123
3810
|
} else {
|
|
3124
|
-
console.log(`>>ARCALITY_STATUS>> \u2139\uFE0F Patr\xF3n ya existe en memoria
|
|
3811
|
+
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.`);
|
|
3125
3812
|
}
|
|
3126
3813
|
} catch (err) {
|
|
3127
3814
|
console.warn(`Error al sincronizar memoria local: ${err}`);
|
|
@@ -3159,7 +3846,7 @@ ${readableHistory}`;
|
|
|
3159
3846
|
let containsError = false;
|
|
3160
3847
|
if (page.isClosed()) {
|
|
3161
3848
|
console.error(`
|
|
3162
|
-
\
|
|
3849
|
+
\u26A0 [LAZARUS SUBSYSTEM: Critical Crash] La pesta\xF1a del navegador fue cerrada inesperadamente (inicio de ciclo).`);
|
|
3163
3850
|
console.error(` >> Causa probable: El portal caus\xF3 una navegaci\xF3n forzada, crash de tab, o redirigi\xF3 fuera del contexto.`);
|
|
3164
3851
|
hasCriticalError = true;
|
|
3165
3852
|
failReason = "portal_page_crash";
|
|
@@ -3257,7 +3944,7 @@ ${readableHistory}`;
|
|
|
3257
3944
|
console.log(`>>ARCALITY_STATUS>> \u{1F6D1} Error interno del Portal/Red detectado: "${networkError}"`);
|
|
3258
3945
|
hasCriticalError = true;
|
|
3259
3946
|
failReason = "portal_network_error";
|
|
3260
|
-
failReasoning = `El portal web fall\xF3 al intentar comunicarse con su backend (ej. una petici\xF3n XHR o Fetch fall\xF3 o dio timeout).
|
|
3947
|
+
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.
|
|
3261
3948
|
Detalles t\xE9cnicos capturados en la red: ${networkError}`;
|
|
3262
3949
|
isFinished = true;
|
|
3263
3950
|
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.`);
|
|
@@ -3285,12 +3972,12 @@ Detalles de la excepci\xF3n en consola: ${jsError}`;
|
|
|
3285
3972
|
console.error(`
|
|
3286
3973
|
\u{1F6D1} [SYSTEM ERROR DETECTADO] El portal report\xF3 un error de sistema no recuperable en UI:`);
|
|
3287
3974
|
console.error(` Mensaje: "${sysErrTxt.trim().substring(0, 150)}"`);
|
|
3288
|
-
console.error(` >> Tipo: portal_internal_error |
|
|
3975
|
+
console.error(` >> Tipo: portal_internal_error | Arcality NO puede corregir errores del servidor.`);
|
|
3289
3976
|
hasCriticalError = true;
|
|
3290
3977
|
failReason = "portal_internal_error";
|
|
3291
|
-
failReasoning = `El servidor devolvi\xF3 un error sist\xE9mico irrecuperable que fue mostrado en la interfaz visual: "${sysErrTxt.trim()}".
|
|
3978
|
+
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.`;
|
|
3292
3979
|
isFinished = true;
|
|
3293
|
-
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
|
|
3980
|
+
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.`);
|
|
3294
3981
|
saveMissionResults();
|
|
3295
3982
|
break;
|
|
3296
3983
|
}
|
|
@@ -3302,7 +3989,7 @@ Detalles de la excepci\xF3n en consola: ${jsError}`;
|
|
|
3302
3989
|
break;
|
|
3303
3990
|
let hasVisibleError = false;
|
|
3304
3991
|
try {
|
|
3305
|
-
const errorEls = await page.locator('.toast, .alert, [role="alert"], [role="status"], .snackbar, .notification, .error-message').all();
|
|
3992
|
+
const errorEls = await page.locator('.toast, .alert, [role="alert"], [role="status"], .snackbar, .notification, .error-message, .mat-snack-bar-container, [class*="alert"], [class*="toast"]').all();
|
|
3306
3993
|
for (const el of errorEls) {
|
|
3307
3994
|
if (await el.isVisible().catch(() => false)) {
|
|
3308
3995
|
const errTxt = await el.innerText().catch(() => "");
|
|
@@ -3321,26 +4008,26 @@ Detalles de la excepci\xF3n en consola: ${jsError}`;
|
|
|
3321
4008
|
await page.waitForTimeout(1e3);
|
|
3322
4009
|
response = await agent.getActionFromGuia(prompt, guideIdx);
|
|
3323
4010
|
if (response) {
|
|
3324
|
-
console.log(`>>ARCALITY_STATUS>> \u{1F504}
|
|
4011
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F504} Guide recovered path after 1000ms delay.`);
|
|
3325
4012
|
}
|
|
3326
4013
|
}
|
|
3327
4014
|
} else {
|
|
3328
|
-
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F
|
|
4015
|
+
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Visible UI anomaly detected. Bypassing Guide.`);
|
|
3329
4016
|
guideIdx = 999999;
|
|
3330
4017
|
if (stepsData.length > 0) {
|
|
3331
4018
|
stepsDataBackup = [...stepsData];
|
|
3332
4019
|
stepsData.length = 0;
|
|
3333
4020
|
}
|
|
3334
4021
|
try {
|
|
3335
|
-
const errorEls = await page.locator('.toast, .alert, [role="alert"], [role="status"], .snackbar, .notification, .error-message, .mat-snack-bar-container').all();
|
|
4022
|
+
const errorEls = await page.locator('.toast, .alert, [role="alert"], [role="status"], .snackbar, .notification, .error-message, .mat-snack-bar-container, [class*="alert"], [class*="toast"]').all();
|
|
3336
4023
|
for (const el of errorEls) {
|
|
3337
4024
|
if (await el.isVisible().catch(() => false)) {
|
|
3338
4025
|
const errTxt = await el.innerText().catch(() => "");
|
|
3339
4026
|
if (errTxt && failureKeywords.test(errTxt.toLowerCase())) {
|
|
3340
4027
|
const isDuplicateError = /repetido|duplicado|ya existe|already exists/i.test(errTxt);
|
|
3341
|
-
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}
|
|
4028
|
+
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.`;
|
|
3342
4029
|
history.push(`Turno ${stepCount}: \u{1F6D1} ERROR VISIBLE EN PANTALLA: "${errTxt.trim().substring(0, 200)}". ${correctionHint}`);
|
|
3343
|
-
console.log(`>>ARCALITY_STATUS>> \u{1F6D1}
|
|
4030
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F6D1} Anomal\xEDa inyectada en historial: "${errTxt.substring(0, 80)}"`);
|
|
3344
4031
|
break;
|
|
3345
4032
|
}
|
|
3346
4033
|
}
|
|
@@ -3349,7 +4036,7 @@ Detalles de la excepci\xF3n en consola: ${jsError}`;
|
|
|
3349
4036
|
}
|
|
3350
4037
|
}
|
|
3351
4038
|
if (response) {
|
|
3352
|
-
const guideActionDesc = response.actions?.[0] ? `${response.actions[0].action}
|
|
4039
|
+
const guideActionDesc = response.actions?.[0] ? `${response.actions[0].action} on "${response.actions[0].description || ""}"`.toLowerCase() : "";
|
|
3353
4040
|
const recentHistory = history.slice(-4).map((h) => h.toLowerCase());
|
|
3354
4041
|
const isGuideRepeating = guideActionDesc && recentHistory.some((h) => h.includes(guideActionDesc.substring(0, 30)));
|
|
3355
4042
|
if (isGuideRepeating) {
|
|
@@ -3361,15 +4048,15 @@ Detalles de la excepci\xF3n en consola: ${jsError}`;
|
|
|
3361
4048
|
guideIdx++;
|
|
3362
4049
|
console.log(`
|
|
3363
4050
|
======================================================`);
|
|
3364
|
-
console.log(`\u{1F680} [
|
|
3365
|
-
console.log(`>>arcality>> Paso validado (Gu\xEDa #${guideStepCount}, guideIdx=${guideIdx},
|
|
4051
|
+
console.log(`\u{1F680} [MODO GU\xCDA ACTIVADO - EJECUTANDO RUTA R\xC1PIDA CONOCIDA]`);
|
|
4052
|
+
console.log(`>>arcality>> Paso validado (Gu\xEDa #${guideStepCount}, guideIdx=${guideIdx}, omite IA)`);
|
|
3366
4053
|
console.log(`======================================================
|
|
3367
4054
|
`);
|
|
3368
4055
|
}
|
|
3369
4056
|
}
|
|
3370
4057
|
if (!response) {
|
|
3371
4058
|
stepCount++;
|
|
3372
|
-
console.log(`>>ARCALITY_STATUS>> \
|
|
4059
|
+
console.log(`>>ARCALITY_STATUS>> \u25C9 [ARCALITY] Calculando siguiente acci\xF3n... (Turno ${stepCount}/${maxSteps}) (Saltos por Gu\xEDa: ${guideStepCount})...`);
|
|
3373
4060
|
const effectivePrompt = patternContext ? `${prompt}
|
|
3374
4061
|
|
|
3375
4062
|
${patternContext}` : prompt;
|
|
@@ -3421,7 +4108,7 @@ ${patternContext}` : prompt;
|
|
|
3421
4108
|
}
|
|
3422
4109
|
console.log(`
|
|
3423
4110
|
======================================================`);
|
|
3424
|
-
console.log(`\u{1F916} [RAZONAMIENTO
|
|
4111
|
+
console.log(`\u{1F916} [RAZONAMIENTO DE ARCALITY]`);
|
|
3425
4112
|
console.log(`${response ? response.thought : "N/A"}`);
|
|
3426
4113
|
console.log(`======================================================
|
|
3427
4114
|
`);
|
|
@@ -3444,7 +4131,7 @@ ${patternContext}` : prompt;
|
|
|
3444
4131
|
const loopType = isClickLoop ? "click" : "fill";
|
|
3445
4132
|
const uniqueTargets = isClickLoop ? uniqueClicks : uniqueFills;
|
|
3446
4133
|
console.warn(`
|
|
3447
|
-
\u26A0
|
|
4134
|
+
\u26A0 [OUROBOROS SUBSYSTEM: Repetitive ${loopType} loop anomaly detected]:`);
|
|
3448
4135
|
console.warn(` \xDAltimas acciones: ${lastActions.join(" \u2192 ")}`);
|
|
3449
4136
|
response.thought = `\u{1F6D1} ERROR: Bucle repetitivo en ${loopType} sobre: ${Array.from(uniqueTargets).join(", ")}.`;
|
|
3450
4137
|
response.finish = true;
|
|
@@ -3452,27 +4139,27 @@ ${patternContext}` : prompt;
|
|
|
3452
4139
|
aiMarkedSuccess = false;
|
|
3453
4140
|
hasCriticalError = true;
|
|
3454
4141
|
failReason = "loop_detected";
|
|
3455
|
-
failReasoning = `
|
|
4142
|
+
failReasoning = `Arcality QA entr\xF3 en un bucle l\xF3gico intentando ejecutar la misma acci\xF3n repetidamente sin observar cambios significativos en el DOM.
|
|
3456
4143
|
Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(uniqueTargets).join(", ")}.`;
|
|
3457
4144
|
} else if ((isClickLoop || isFillLoop) && agentRecovered) {
|
|
3458
|
-
console.log(`>>ARCALITY_STATUS>> \u{1F504} Loop candidato detectado pero
|
|
4145
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F504} Loop candidato detectado pero Arcality se recuper\xF3 en este turno. Continuando misi\xF3n.`);
|
|
3459
4146
|
}
|
|
3460
4147
|
}
|
|
3461
4148
|
if (!response.finish && history.length >= 3) {
|
|
3462
4149
|
const lastSixHistory = history.slice(-6);
|
|
3463
4150
|
const consecutiveWaits = lastSixHistory.filter((h) => h.includes('wait en "')).length;
|
|
3464
4151
|
if (consecutiveWaits >= 3) {
|
|
3465
|
-
const blankEvidencePath =
|
|
4152
|
+
const blankEvidencePath = path5.join(contextDir, `portal-blank-state-${Date.now()}.png`);
|
|
3466
4153
|
await page.screenshot({ path: blankEvidencePath, fullPage: true }).catch(() => {
|
|
3467
4154
|
});
|
|
3468
4155
|
const blankUrl = page.url();
|
|
3469
4156
|
console.error(`
|
|
3470
|
-
\
|
|
4157
|
+
\u26A0 [VOIDWATCH SUBSYSTEM: Rendering Anomaly] Arcality ejecut\xF3 ${consecutiveWaits} esperas consecutivas sin progreso.`);
|
|
3471
4158
|
console.error(` >> URL afectada: ${blankUrl}`);
|
|
3472
4159
|
console.error(` >> El portal carg\xF3 el shell (navegaci\xF3n, header) pero los WIDGETS/COMPONENTES quedaron en blanco.`);
|
|
3473
4160
|
console.error(` >> Evidencia guardada: ${blankEvidencePath}`);
|
|
3474
|
-
console.error(` >> Tipo: portal_rendering_failure | Esto es un bug del portal, NO
|
|
3475
|
-
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.
|
|
4161
|
+
console.error(` >> Tipo: portal_rendering_failure | Esto es un bug del portal, NO de Arcality.`);
|
|
4162
|
+
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.`;
|
|
3476
4163
|
response.finish = true;
|
|
3477
4164
|
response.actions = [];
|
|
3478
4165
|
aiMarkedSuccess = false;
|
|
@@ -3554,7 +4241,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
3554
4241
|
const bodyText = await page.innerText("body").catch(() => "");
|
|
3555
4242
|
if (bodyText.match(/ligado|depende|no se puede/i)) {
|
|
3556
4243
|
console.log(`
|
|
3557
|
-
\u2728 [ESTANCADO]
|
|
4244
|
+
\u2728 [ESTANCADO] Arcality repite click pero el error ya es visible. Finalizando.`);
|
|
3558
4245
|
isFinished = true;
|
|
3559
4246
|
aiMarkedSuccess = true;
|
|
3560
4247
|
break;
|
|
@@ -3635,8 +4322,8 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
3635
4322
|
lastSeenErrorToast = fbText.trim();
|
|
3636
4323
|
captureValidationRule(fbText.trim(), `Acci\xF3n: click en "${desc}" en ${page.url()}`);
|
|
3637
4324
|
const isDuplicateError = /repetido|duplicado|ya existe|already exists/i.test(fbText);
|
|
3638
|
-
const correctionHint = isDuplicateError ? `
|
|
3639
|
-
history.push(`Turno ${stepCount}: \u{1F6D1} ERROR POST-
|
|
4325
|
+
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.`;
|
|
4326
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} ERROR POST-ACCI\xD3N: "${fbText.trim().substring(0, 200)}". ${correctionHint}`);
|
|
3640
4327
|
}
|
|
3641
4328
|
}
|
|
3642
4329
|
}
|
|
@@ -3734,11 +4421,11 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
3734
4421
|
const panelCount = Math.min(panels.length, 6);
|
|
3735
4422
|
if (panelCount >= 2 && emptyPanelCount >= panelCount * 0.6) {
|
|
3736
4423
|
const blankUrl = page.url();
|
|
3737
|
-
const blankEvidencePath =
|
|
4424
|
+
const blankEvidencePath = path5.join(contextDir, `portal-blank-dashboard-${Date.now()}.png`);
|
|
3738
4425
|
await page.screenshot({ path: blankEvidencePath, fullPage: true }).catch(() => {
|
|
3739
4426
|
});
|
|
3740
4427
|
console.error(`
|
|
3741
|
-
\
|
|
4428
|
+
\u26A0 [VOIDWATCH SUBSYSTEM: Blank Dashboard Anomaly] El portal carg\xF3 el shell pero los widgets est\xE1n vac\xEDos.`);
|
|
3742
4429
|
console.error(` >> URL: ${blankUrl}`);
|
|
3743
4430
|
console.error(` >> ${emptyPanelCount} de ${panelCount} paneles est\xE1n en blanco.`);
|
|
3744
4431
|
console.error(` >> Tipo: portal_rendering_failure | Bug del portal confirmado.`);
|
|
@@ -3746,7 +4433,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
3746
4433
|
hasCriticalError = true;
|
|
3747
4434
|
failReason = "portal_rendering_failure";
|
|
3748
4435
|
isFinished = true;
|
|
3749
|
-
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.
|
|
4436
|
+
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}`);
|
|
3750
4437
|
saveMissionResults();
|
|
3751
4438
|
break;
|
|
3752
4439
|
}
|
|
@@ -3775,19 +4462,19 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
3775
4462
|
const errMsg = e.message || "";
|
|
3776
4463
|
if (errMsg.includes("[BLANK_STATE_CRASH]")) {
|
|
3777
4464
|
console.error(`
|
|
3778
|
-
\
|
|
3779
|
-
console.error(` >> Causa: Error de renderizado del portal (White Screen).
|
|
4465
|
+
\u26A0 [VOIDWATCH SUBSYSTEM: Blank State Crash] El portal carg\xF3 en blanco despu\xE9s de la acci\xF3n "${step.description || step.action}".`);
|
|
4466
|
+
console.error(` >> Causa: Error de renderizado del portal (White Screen). Arcality no puede continuar.`);
|
|
3780
4467
|
hasCriticalError = true;
|
|
3781
4468
|
failReason = "portal_render_failure";
|
|
3782
4469
|
isFinished = true;
|
|
3783
|
-
history.push(`Turno ${stepCount}: \u{1F6D1} [BLANK_STATE_CRASH] El portal qued\xF3 en blanco tras navegar. Esto es un bug del portal, no
|
|
4470
|
+
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.`);
|
|
3784
4471
|
saveMissionResults();
|
|
3785
4472
|
break;
|
|
3786
4473
|
}
|
|
3787
4474
|
const isPageClosed = errMsg.includes("Target page, context or browser has been closed") || errMsg.includes("page has been closed") || errMsg.includes("Session closed") || page.isClosed();
|
|
3788
4475
|
if (isPageClosed) {
|
|
3789
4476
|
console.error(`
|
|
3790
|
-
\
|
|
4477
|
+
\u26A0 [LAZARUS SUBSYSTEM: Critical Crash] La pesta\xF1a colaps\xF3 durante la acci\xF3n "${step.description || step.action}"`);
|
|
3791
4478
|
console.error(` >> Error: ${errMsg.substring(0, 150)}`);
|
|
3792
4479
|
console.error(` >> Tipo: portal_page_crash | El portal cerr\xF3 el contexto del navegador inesperadamente.`);
|
|
3793
4480
|
hasCriticalError = true;
|
|
@@ -3847,7 +4534,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
3847
4534
|
}
|
|
3848
4535
|
if (response.finish) {
|
|
3849
4536
|
if (!containsError) {
|
|
3850
|
-
console.log(">>ARCALITY_STATUS>> \u2705
|
|
4537
|
+
console.log(">>ARCALITY_STATUS>> \u2705 Arcality solicit\xF3 finalizar misi\xF3n.");
|
|
3851
4538
|
isFinished = true;
|
|
3852
4539
|
if (!response.actions || response.actions.length === 0) {
|
|
3853
4540
|
stepsData.push({
|
|
@@ -3890,13 +4577,13 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
3890
4577
|
isFinished = true;
|
|
3891
4578
|
saveMissionResults();
|
|
3892
4579
|
} else {
|
|
3893
|
-
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F
|
|
4580
|
+
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Arcality intent\xF3 finalizar pero se detect\xF3 un ERROR en alert/toast.`);
|
|
3894
4581
|
aiMarkedSuccess = false;
|
|
3895
4582
|
isFinished = false;
|
|
3896
|
-
history.push(`Turno ${stepCount}:
|
|
4583
|
+
history.push(`Turno ${stepCount}: Arcality intent\xF3 finalizar pero el sistema detect\xF3 un error en un toast/alerta visible.`);
|
|
3897
4584
|
}
|
|
3898
4585
|
} else {
|
|
3899
|
-
console.warn(">>ARCALITY_STATUS>> \u26A0\uFE0F
|
|
4586
|
+
console.warn(">>ARCALITY_STATUS>> \u26A0\uFE0F Arcality intent\xF3 finalizar pero hubo fallos en el turno. Continuando para investigaci\xF3n...");
|
|
3900
4587
|
isFinished = false;
|
|
3901
4588
|
}
|
|
3902
4589
|
}
|
|
@@ -3916,7 +4603,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
3916
4603
|
failReason = "portal_internal_error";
|
|
3917
4604
|
isFinished = true;
|
|
3918
4605
|
aiMarkedSuccess = false;
|
|
3919
|
-
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
|
|
4606
|
+
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.`);
|
|
3920
4607
|
saveMissionResults();
|
|
3921
4608
|
break;
|
|
3922
4609
|
} else if (failureKeywords.test(txt)) {
|
|
@@ -4015,8 +4702,8 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
4015
4702
|
console.log("\u{1F916} [SMART-YAML] Generating mission card...");
|
|
4016
4703
|
const smartYaml = await agent.summarizeMissionToYaml(prompt, history, target);
|
|
4017
4704
|
if (smartYaml) {
|
|
4018
|
-
const yamlPath =
|
|
4019
|
-
|
|
4705
|
+
const yamlPath = path5.join(contextDir, "last-mission-smart.yaml");
|
|
4706
|
+
fs5.writeFileSync(yamlPath, smartYaml);
|
|
4020
4707
|
console.log(`>>ARCALITY_STATUS>> \u{1F4DD} Smart Mission Card generated.`);
|
|
4021
4708
|
await testInfo.attach("smart_mission_card", {
|
|
4022
4709
|
body: Buffer.from(smartYaml, "utf-8"),
|
|
@@ -4078,7 +4765,7 @@ Evidence: ${JSON.stringify(v.evidence).substring(0, 400)}`,
|
|
|
4078
4765
|
finalErrorMsg += `\u{1F3AF} TIPO DE FALLO: ${failReason}
|
|
4079
4766
|
`;
|
|
4080
4767
|
if (failReasoning) {
|
|
4081
|
-
finalErrorMsg += `\u{1F9E0} AN\xC1LISIS DEL
|
|
4768
|
+
finalErrorMsg += `\u{1F9E0} AN\xC1LISIS DEL ARCALITY QA:
|
|
4082
4769
|
${failReasoning}
|
|
4083
4770
|
`;
|
|
4084
4771
|
}
|
|
@@ -4088,15 +4775,15 @@ ${failReasoning}
|
|
|
4088
4775
|
} else {
|
|
4089
4776
|
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}`);
|
|
4090
4777
|
}
|
|
4091
|
-
if (
|
|
4778
|
+
if (fs5.existsSync(contextDir)) {
|
|
4092
4779
|
try {
|
|
4093
|
-
const files =
|
|
4780
|
+
const files = fs5.readdirSync(contextDir);
|
|
4094
4781
|
for (const file of files) {
|
|
4095
4782
|
if (file.endsWith(".json") || file.endsWith(".png")) {
|
|
4096
|
-
|
|
4783
|
+
fs5.unlinkSync(path5.join(contextDir, file));
|
|
4097
4784
|
}
|
|
4098
4785
|
}
|
|
4099
|
-
|
|
4786
|
+
fs5.rmSync(contextDir, { recursive: true, force: true });
|
|
4100
4787
|
console.log(`>>ARCALITY_STATUS>> \u{1F9F9} Carpeta context eliminada (Memoria ef\xEDmera completada).`);
|
|
4101
4788
|
} catch (err) {
|
|
4102
4789
|
console.warn(`No se pudo limpiar la carpeta context: ${err}`);
|