@arcadialdev/arcality 3.0.2 → 3.0.4

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.
@@ -27,6 +27,393 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
28
28
  mod
29
29
  ));
30
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
31
+
32
+ // tests/_helpers/asset-resolver.ts
33
+ var asset_resolver_exports = {};
34
+ __export(asset_resolver_exports, {
35
+ createPlaceholderUpload: () => createPlaceholderUpload,
36
+ formatUploadAssetContext: () => formatUploadAssetContext,
37
+ listUploadAssets: () => listUploadAssets,
38
+ resolveUploadAsset: () => resolveUploadAsset,
39
+ syncAssetsFromBackend: () => syncAssetsFromBackend
40
+ });
41
+ function readJsonFile(filePath) {
42
+ const raw = fs2.readFileSync(filePath, "utf8").trim();
43
+ if (!raw)
44
+ return null;
45
+ return JSON.parse(raw);
46
+ }
47
+ function sanitizeIdPart(value) {
48
+ return String(value || "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 80);
49
+ }
50
+ function normalizeMimeFromPath(filePath) {
51
+ const ext = path2.extname(filePath || "").toLowerCase();
52
+ if (ext === ".jpg" || ext === ".jpeg")
53
+ return "image/jpeg";
54
+ if (ext === ".webp")
55
+ return "image/webp";
56
+ if (ext === ".png")
57
+ return "image/png";
58
+ return "application/octet-stream";
59
+ }
60
+ function inferAssetType(...values) {
61
+ const text = values.join(" ").toLowerCase();
62
+ if (text.includes("banner") || text.includes("hero") || text.includes("cover"))
63
+ return "banner";
64
+ if (text.includes("logo"))
65
+ return "logo";
66
+ if (text.includes("avatar") || text.includes("profile") || text.includes("perfil"))
67
+ return "profile";
68
+ if (text.includes("product") || text.includes("producto"))
69
+ return "product";
70
+ return "image";
71
+ }
72
+ function normalizeAsset(raw, assetsDir = null) {
73
+ const assetId = String(raw.asset_id || raw.assetId || raw.id || "").trim();
74
+ const rawPath = String(raw.path || raw.file_path || raw.filePath || raw.storage_key || raw.storageKey || "").trim();
75
+ const filename = String(raw.filename || raw.fileName || (rawPath ? path2.basename(rawPath) : "") || "").trim();
76
+ const derivedId = filename ? `asset_${sanitizeIdPart(path2.basename(filename, path2.extname(filename)))}` : "";
77
+ const finalId = assetId || derivedId;
78
+ if (!finalId)
79
+ return null;
80
+ let filePath = rawPath || void 0;
81
+ if (filePath && !path2.isAbsolute(filePath)) {
82
+ const cwdCandidate = path2.resolve(process.cwd(), filePath);
83
+ const assetDirCandidate = assetsDir ? path2.resolve(assetsDir, filePath) : "";
84
+ filePath = fs2.existsSync(cwdCandidate) ? cwdCandidate : assetDirCandidate || cwdCandidate;
85
+ }
86
+ const label = String(raw.label || raw.user_label || raw.userLabel || filename || finalId).trim();
87
+ const type = String(raw.type || inferAssetType(label, filename)).trim().toLowerCase();
88
+ const mime = String(raw.mime || raw.mimeType || normalizeMimeFromPath(filePath || filename)).trim();
89
+ return {
90
+ assetId: finalId,
91
+ label,
92
+ type,
93
+ mime,
94
+ width: Number.isFinite(Number(raw.width)) ? Number(raw.width) : void 0,
95
+ height: Number.isFinite(Number(raw.height)) ? Number(raw.height) : void 0,
96
+ filePath,
97
+ filename,
98
+ isDefault: Boolean(raw.is_default || raw.isDefault)
99
+ };
100
+ }
101
+ function getDefaultAssetsDir() {
102
+ return process.env.ARCALITY_ASSETS_DIR || path2.join(process.cwd(), ".arcality", "assets");
103
+ }
104
+ function loadManifestAssets(assetsDir) {
105
+ const explicitFile = process.env.ARCALITY_ASSETS_FILE;
106
+ const manifestPath = explicitFile || path2.join(process.cwd(), ".arcality", "assets.json");
107
+ const inlineJson = process.env.ARCALITY_ASSETS_JSON;
108
+ try {
109
+ const parsed = inlineJson ? JSON.parse(inlineJson) : fs2.existsSync(manifestPath) ? readJsonFile(manifestPath) : null;
110
+ const list = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.assets) ? parsed.assets : [];
111
+ return list.map((entry) => normalizeAsset(entry, assetsDir)).filter(Boolean);
112
+ } catch (e) {
113
+ console.warn(`>>ARCALITY_STATUS>> Warning: no se pudo leer el inventario de assets (${e.message}).`);
114
+ return [];
115
+ }
116
+ }
117
+ function scanAssetsDir(assetsDir) {
118
+ try {
119
+ if (!fs2.existsSync(assetsDir))
120
+ return [];
121
+ return fs2.readdirSync(assetsDir, { withFileTypes: true }).filter((entry) => entry.isFile() && IMAGE_EXTENSIONS.has(path2.extname(entry.name).toLowerCase())).map((entry) => {
122
+ const filePath = path2.join(assetsDir, entry.name);
123
+ return normalizeAsset({
124
+ id: `asset_${sanitizeIdPart(path2.basename(entry.name, path2.extname(entry.name)))}`,
125
+ label: path2.basename(entry.name, path2.extname(entry.name)).replace(/[_-]+/g, " "),
126
+ path: filePath,
127
+ mime: normalizeMimeFromPath(filePath),
128
+ type: inferAssetType(entry.name)
129
+ }, assetsDir);
130
+ }).filter(Boolean);
131
+ } catch {
132
+ return [];
133
+ }
134
+ }
135
+ function extractBackendAssets(payload) {
136
+ if (Array.isArray(payload))
137
+ return payload;
138
+ if (Array.isArray(payload?.assets))
139
+ return payload.assets;
140
+ if (Array.isArray(payload?.data?.assets))
141
+ return payload.data.assets;
142
+ if (Array.isArray(payload?.items))
143
+ return payload.items;
144
+ return [];
145
+ }
146
+ function listUploadAssets() {
147
+ const assetsDir = getDefaultAssetsDir();
148
+ const merged = [...loadManifestAssets(assetsDir), ...scanAssetsDir(assetsDir)];
149
+ const byId = /* @__PURE__ */ new Map();
150
+ for (const asset of merged) {
151
+ if (!byId.has(asset.assetId))
152
+ byId.set(asset.assetId, asset);
153
+ }
154
+ return Array.from(byId.values());
155
+ }
156
+ function formatUploadAssetContext() {
157
+ const assets = listUploadAssets();
158
+ const lines = assets.slice(0, 12).map((asset) => {
159
+ const size = asset.width && asset.height ? `${asset.width}x${asset.height}` : "unknown-size";
160
+ const defaultFlag = asset.isDefault ? " | default" : "";
161
+ return `- ${asset.label} | asset_id:${asset.assetId} | type:${asset.type} | mime:${asset.mime} | size:${size}${defaultFlag} | use:"arcasset://${asset.assetId}"`;
162
+ });
163
+ return `
164
+ # AVAILABLE UPLOAD ASSETS
165
+ ${lines.length ? lines.join("\n") : "- No user assets registered for this project."}
166
+ - Fallback allowed: use "placeholder:image/banner" for required image uploads when no suitable asset exists.
167
+ `;
168
+ }
169
+ function findAsset(reference) {
170
+ const id = reference.replace(/^arcasset:\/\//i, "").trim();
171
+ const assets = listUploadAssets();
172
+ const lower = id.toLowerCase();
173
+ return assets.find(
174
+ (asset) => asset.assetId.toLowerCase() === lower || asset.label.toLowerCase() === lower || asset.filename?.toLowerCase() === lower
175
+ ) || null;
176
+ }
177
+ function inferPlaceholderSpec(label, reference = "") {
178
+ const text = `${label} ${reference}`.toLowerCase();
179
+ if (text.includes("logo") || text.includes("avatar") || text.includes("perfil")) {
180
+ return { width: 1024, height: 1024, name: "arcality-square-placeholder-1024x1024.png" };
181
+ }
182
+ if (text.includes("product") || text.includes("producto")) {
183
+ return { width: 1200, height: 1200, name: "arcality-product-placeholder-1200x1200.png" };
184
+ }
185
+ return { width: 1920, height: 1080, name: "arcality-banner-placeholder-1920x1080.png" };
186
+ }
187
+ function crc32(buffer) {
188
+ let crc = 4294967295;
189
+ for (let i = 0; i < buffer.length; i++) {
190
+ crc ^= buffer[i];
191
+ for (let j = 0; j < 8; j++) {
192
+ crc = crc >>> 1 ^ 3988292384 & -(crc & 1);
193
+ }
194
+ }
195
+ return (crc ^ 4294967295) >>> 0;
196
+ }
197
+ function pngChunk(type, data) {
198
+ const typeBuffer = Buffer.from(type, "ascii");
199
+ const length = Buffer.alloc(4);
200
+ length.writeUInt32BE(data.length, 0);
201
+ const crc = Buffer.alloc(4);
202
+ crc.writeUInt32BE(crc32(Buffer.concat([typeBuffer, data])), 0);
203
+ return Buffer.concat([length, typeBuffer, data, crc]);
204
+ }
205
+ function createPngBuffer(width, height) {
206
+ const bytesPerPixel = 3;
207
+ const stride = width * bytesPerPixel + 1;
208
+ const raw = Buffer.alloc(stride * height);
209
+ for (let y = 0; y < height; y++) {
210
+ const rowStart = y * stride;
211
+ raw[rowStart] = 0;
212
+ for (let x = 0; x < width; x++) {
213
+ const offset = rowStart + 1 + x * bytesPerPixel;
214
+ const shade = (x + y) % 64;
215
+ raw[offset] = 30 + shade;
216
+ raw[offset + 1] = 108 + Math.floor(shade / 2);
217
+ raw[offset + 2] = 147;
218
+ }
219
+ }
220
+ const header = Buffer.alloc(13);
221
+ header.writeUInt32BE(width, 0);
222
+ header.writeUInt32BE(height, 4);
223
+ header[8] = 8;
224
+ header[9] = 2;
225
+ header[10] = 0;
226
+ header[11] = 0;
227
+ header[12] = 0;
228
+ return Buffer.concat([
229
+ Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
230
+ pngChunk("IHDR", header),
231
+ pngChunk("IDAT", zlib.deflateSync(raw)),
232
+ pngChunk("IEND", Buffer.alloc(0))
233
+ ]);
234
+ }
235
+ function createPlaceholderUpload(label = "file upload", reference = "") {
236
+ const spec = inferPlaceholderSpec(label, reference);
237
+ return {
238
+ name: spec.name,
239
+ mimeType: "image/png",
240
+ buffer: createPngBuffer(spec.width, spec.height)
241
+ };
242
+ }
243
+ function resolveUploadAsset(value, label = "file upload") {
244
+ const raw = String(value || "").replace(/^"|"$/g, "").trim();
245
+ if (!raw || /^placeholder:/i.test(raw) || /^dummy$/i.test(raw)) {
246
+ const assets = listUploadAssets();
247
+ const subtype = raw.startsWith("placeholder:image/") ? raw.substring("placeholder:image/".length).toLowerCase() : "";
248
+ const targetType = subtype || inferAssetType(label, raw);
249
+ const matchingAsset = assets.find((a) => a.type === targetType || a.label.toLowerCase().includes(targetType)) || assets.find((a) => !a.isDefault);
250
+ if (matchingAsset?.filePath && fs2.existsSync(matchingAsset.filePath)) {
251
+ console.log(`>>ARCALITY_STATUS>> Usando asset real configurado para el placeholder "${raw}": ${matchingAsset.filePath}`);
252
+ return matchingAsset.filePath;
253
+ }
254
+ return createPlaceholderUpload(label, raw);
255
+ }
256
+ if (/^arcasset:\/\//i.test(raw) || /^asset_[a-z0-9_-]+$/i.test(raw)) {
257
+ const asset = findAsset(raw);
258
+ if (asset?.filePath && fs2.existsSync(asset.filePath)) {
259
+ return asset.filePath;
260
+ }
261
+ console.warn(`>>ARCALITY_STATUS>> Asset "${raw}" no tiene archivo local disponible. Usando placeholder.`);
262
+ return createPlaceholderUpload(label, raw);
263
+ }
264
+ const filePath = path2.isAbsolute(raw) ? raw : path2.resolve(process.cwd(), raw);
265
+ if (fs2.existsSync(filePath)) {
266
+ return filePath;
267
+ }
268
+ console.warn(`>>ARCALITY_STATUS>> Archivo de upload no encontrado: "${raw}". Usando placeholder.`);
269
+ return createPlaceholderUpload(label, raw);
270
+ }
271
+ async function syncAssetsFromBackend() {
272
+ const apiUrl = process.env.ARCALITY_API_URL;
273
+ const apiKey = process.env.ARCALITY_API_KEY;
274
+ const projectId = process.env.ARCALITY_PROJECT_ID;
275
+ if (!apiUrl || !apiKey || !projectId) {
276
+ console.warn(`>>ARCALITY_STATUS>> \u26A0\uFE0F Asset sync omitido por configuraci\xF3n incompleta: API_URL=${apiUrl ? "OK" : "MISSING"}, API_KEY=${apiKey ? "OK" : "MISSING"}, PROJECT_ID=${projectId ? "OK" : "MISSING"}.`);
277
+ return;
278
+ }
279
+ try {
280
+ const assetsDir = getDefaultAssetsDir();
281
+ if (!fs2.existsSync(assetsDir)) {
282
+ fs2.mkdirSync(assetsDir, { recursive: true });
283
+ }
284
+ const url = `${apiUrl}/api/v1/projects/${projectId}/assets`;
285
+ console.log(`>>ARCALITY_STATUS>> \u{1F4E6} Consultando assets configurados del proyecto...`);
286
+ if (process.env.DEBUG) {
287
+ console.log(`[AssetSync] Fetching assets from: ${url}`);
288
+ }
289
+ const res = await globalThis.fetch(url, {
290
+ headers: {
291
+ "x-api-key": apiKey
292
+ }
293
+ });
294
+ if (!res.ok) {
295
+ console.warn(`>>ARCALITY_STATUS>> \u26A0\uFE0F Error al obtener assets del portal (${res.status}).`);
296
+ return;
297
+ }
298
+ const payload = await res.json();
299
+ const backendAssets = extractBackendAssets(payload);
300
+ if (!backendAssets.length) {
301
+ const payloadKeys = payload && typeof payload === "object" ? Object.keys(payload).slice(0, 8).join(", ") : typeof payload;
302
+ console.log(`>>ARCALITY_STATUS>> \u2139\uFE0F El proyecto no devolvi\xF3 assets utilizables. Se usar\xE1 placeholder cuando un upload lo requiera.`);
303
+ if (process.env.DEBUG) {
304
+ console.log(`[AssetSync] Payload shape without assets array: ${payloadKeys || "empty"}`);
305
+ }
306
+ return;
307
+ }
308
+ console.log(`>>ARCALITY_STATUS>> \u{1F4E6} Assets detectados en portal: ${backendAssets.length}`);
309
+ const localAssets = [];
310
+ for (const asset of backendAssets) {
311
+ const assetId = asset.asset_id || asset.assetId || asset.id;
312
+ if (!assetId) {
313
+ console.warn(`>>ARCALITY_STATUS>> \u26A0\uFE0F Asset ignorado porque no trae id utilizable: ${JSON.stringify(asset).slice(0, 200)}`);
314
+ continue;
315
+ }
316
+ const prefix = `${assetId}_`;
317
+ const files = fs2.existsSync(assetsDir) ? fs2.readdirSync(assetsDir) : [];
318
+ const existingFile = files.find((f) => f.startsWith(prefix));
319
+ let filename = asset.filename || asset.fileName;
320
+ let localFilePath = "";
321
+ let downloadNeeded = true;
322
+ if (existingFile) {
323
+ const candidatePath = path2.join(assetsDir, existingFile);
324
+ if (fs2.existsSync(candidatePath) && fs2.statSync(candidatePath).size > 0) {
325
+ filename = filename || existingFile.substring(prefix.length);
326
+ localFilePath = candidatePath;
327
+ downloadNeeded = false;
328
+ }
329
+ }
330
+ if (downloadNeeded) {
331
+ console.log(`>>ARCALITY_STATUS>> \u{1F4E5} Materializando asset del portal: "${asset.label}"...`);
332
+ const matRes = await globalThis.fetch(`${apiUrl}/api/v1/assets/${assetId}/materialize`, {
333
+ method: "POST",
334
+ headers: {
335
+ "x-api-key": apiKey,
336
+ "Content-Type": "application/json"
337
+ }
338
+ });
339
+ if (matRes.ok) {
340
+ const matData = await matRes.json();
341
+ const downloadUrl = matData.download_url || matData.downloadUrl;
342
+ const matFilename = matData.filename || matData.fileName || filename || `${assetId}.png`;
343
+ filename = matFilename;
344
+ const localFilename = `${assetId}_${matFilename}`;
345
+ localFilePath = path2.join(assetsDir, localFilename);
346
+ if (downloadUrl) {
347
+ console.log(`>>ARCALITY_STATUS>> \u{1F4E5} Descargando archivo de asset: "${asset.label}"...`);
348
+ const fileRes = await globalThis.fetch(downloadUrl);
349
+ if (fileRes.ok) {
350
+ const buffer = Buffer.from(await fileRes.arrayBuffer());
351
+ fs2.writeFileSync(localFilePath, buffer);
352
+ console.log(`>>ARCALITY_STATUS>> \u2705 Asset guardado localmente: ${localFilePath}`);
353
+ } else {
354
+ console.warn(`>>ARCALITY_STATUS>> \u26A0\uFE0F Error al descargar archivo de asset: ${fileRes.status}`);
355
+ }
356
+ }
357
+ } else {
358
+ console.warn(`>>ARCALITY_STATUS>> \u26A0\uFE0F Error al materializar asset: ${matRes.status}`);
359
+ filename = filename || `${assetId}.png`;
360
+ localFilePath = path2.join(assetsDir, `${assetId}_${filename}`);
361
+ }
362
+ }
363
+ localAssets.push({
364
+ asset_id: assetId,
365
+ label: asset.label || filename,
366
+ type: asset.type || "image",
367
+ mime: asset.mime || normalizeMimeFromPath(localFilePath || filename),
368
+ width: asset.width,
369
+ height: asset.height,
370
+ filePath: localFilePath,
371
+ path: localFilePath,
372
+ filename,
373
+ is_default: asset.is_default || asset.isDefault || false
374
+ });
375
+ }
376
+ const manifestPath = process.env.ARCALITY_ASSETS_FILE || path2.join(process.cwd(), ".arcality", "assets.json");
377
+ const manifestDir = path2.dirname(manifestPath);
378
+ if (!fs2.existsSync(manifestDir)) {
379
+ fs2.mkdirSync(manifestDir, { recursive: true });
380
+ }
381
+ fs2.writeFileSync(manifestPath, JSON.stringify(localAssets, null, 2), "utf8");
382
+ console.log(`>>ARCALITY_STATUS>> \u2705 Inventario local de assets actualizado (${localAssets.length}).`);
383
+ if (process.env.DEBUG) {
384
+ console.log(`[AssetSync] Local manifest updated at ${manifestPath}`);
385
+ }
386
+ } catch (e) {
387
+ console.warn(`>>ARCALITY_STATUS>> \u26A0\uFE0F Error durante la sincronizaci\xF3n de assets: ${e.message}`);
388
+ }
389
+ }
390
+ var fs2, path2, zlib, IMAGE_EXTENSIONS;
391
+ var init_asset_resolver = __esm({
392
+ "tests/_helpers/asset-resolver.ts"() {
393
+ fs2 = __toESM(require("fs"));
394
+ path2 = __toESM(require("path"));
395
+ zlib = __toESM(require("zlib"));
396
+ IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp"]);
397
+ }
398
+ });
399
+
400
+ // src/configManager.mjs
401
+ function isRealProjectId2(projectId) {
402
+ const id = String(projectId || "").trim();
403
+ if (!id)
404
+ return false;
405
+ if (id === "undefined" || id === "null")
406
+ return false;
407
+ if (/^(local|mock)_/i.test(id))
408
+ return false;
409
+ if (/^0{8}-0{4}-0{4}-0{4}-0{12}$/i.test(id))
410
+ return false;
411
+ return true;
412
+ }
413
+ var init_configManager = __esm({
414
+ "src/configManager.mjs"() {
415
+ }
416
+ });
30
417
 
31
418
  // src/configLoader.mjs
32
419
  function getApiUrl() {
@@ -63,6 +450,7 @@ var init_configLoader = __esm({
63
450
  import_node_path = __toESM(require("node:path"), 1);
64
451
  import_node_os = __toESM(require("node:os"), 1);
65
452
  import_dotenv = __toESM(require("dotenv"), 1);
453
+ init_configManager();
66
454
  import_dotenv.default.config();
67
455
  CONFIG_DIR = import_node_path.default.join(import_node_os.default.homedir(), ".arcality");
68
456
  CONFIG_FILE = import_node_path.default.join(CONFIG_DIR, "config.json");
@@ -80,44 +468,6 @@ __export(arcalityClient_exports, {
80
468
  startMission: () => startMission,
81
469
  validateApiKey: () => validateApiKey
82
470
  });
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
471
  function loadArcalityConfig() {
122
472
  try {
123
473
  const configPath = import_node_path2.default.join(process.cwd(), "arcality.config");
@@ -143,17 +493,16 @@ function getEffectiveApiKey() {
143
493
  }
144
494
  function loadProjectId() {
145
495
  const localConfig = loadArcalityConfig();
146
- if (localConfig?.projectId)
147
- return localConfig.projectId;
148
- return process.env.ARCALITY_PROJECT_ID || null;
496
+ const projectId = localConfig?.projectId || process.env.ARCALITY_PROJECT_ID || null;
497
+ return isRealProjectId2(projectId) ? projectId : null;
149
498
  }
150
499
  async function validateApiKey() {
151
500
  const key = getEffectiveApiKey();
152
501
  if (!key) {
153
- return { valid: false, error: "no_api_key", mode: "mock" };
502
+ return { valid: false, error: "no_api_key", mode: "live" };
154
503
  }
155
504
  if (!key.startsWith("arc_")) {
156
- return { valid: false, error: "invalid_format", mode: "mock" };
505
+ return { valid: false, error: "invalid_format", mode: "live" };
157
506
  }
158
507
  const apiBase = getEffectiveApiBase();
159
508
  if (apiBase) {
@@ -183,18 +532,11 @@ async function validateApiKey() {
183
532
  return { ...data, valid: true, mode: "live" };
184
533
  } catch (e) {
185
534
  const reason = e?.name === "AbortError" ? "timeout (5s)" : e?.message || "unknown";
186
- console.warn(`\u26A0\uFE0F Backend not available (${apiBase}): ${reason}. Using local mode.`);
535
+ console.warn(`Backend not available (${apiBase}): ${reason}.`);
536
+ return { valid: false, error: "backend_unavailable", mode: "live", reason };
187
537
  }
188
538
  }
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
- };
539
+ return { valid: false, error: "backend_unavailable", mode: "live" };
198
540
  }
199
541
  async function startMission(prompt, targetUrl) {
200
542
  const key = getEffectiveApiKey();
@@ -204,6 +546,9 @@ async function startMission(prompt, targetUrl) {
204
546
  if (apiBase) {
205
547
  try {
206
548
  const projectId = process.env.ARCALITY_PROJECT_ID || loadArcalityConfig()?.projectId;
549
+ if (!isRealProjectId2(projectId)) {
550
+ return { allowed: false, error: "no_project_id" };
551
+ }
207
552
  const controller = new AbortController();
208
553
  const timeout = setTimeout(() => controller.abort(), 5e3);
209
554
  const res = await fetch(`${apiBase}/api/v1/missions/start`, {
@@ -230,32 +575,11 @@ async function startMission(prompt, targetUrl) {
230
575
  return { allowed: true, ...await res.json() };
231
576
  } catch (e) {
232
577
  const reason = e?.name === "AbortError" ? "timeout (5s)" : e?.message || "unknown";
233
- console.warn(`\u26A0\uFE0F startMission failed (${apiBase}): ${reason}. Falling back to mock mode.`);
578
+ console.warn(`startMission failed (${apiBase}): ${reason}.`);
579
+ return { allowed: false, error: "backend_unavailable", reason };
234
580
  }
235
581
  }
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
- };
582
+ return { allowed: false, error: "backend_unavailable" };
259
583
  }
260
584
  async function fetchMissions(projectId, tags) {
261
585
  const key = getEffectiveApiKey();
@@ -263,7 +587,7 @@ async function fetchMissions(projectId, tags) {
263
587
  return { success: false, error: "no_api_key", missions: [] };
264
588
  const apiBase = getEffectiveApiBase();
265
589
  if (!apiBase) {
266
- return { success: true, mode: "mock", missions: [] };
590
+ return { success: false, error: "backend_unavailable", missions: [] };
267
591
  }
268
592
  try {
269
593
  const url = new URL(`${apiBase}/api/v1/projects/${projectId}/missions`);
@@ -295,8 +619,10 @@ async function fetchMissions(projectId, tags) {
295
619
  }
296
620
  async function endMission(missionId, result, usage = null, failReason = null, reportUrl = null, failReasoning = null) {
297
621
  const apiBase = getEffectiveApiBase();
298
- if (!apiBase || !missionId)
299
- return { ok: true };
622
+ if (!apiBase)
623
+ return { ok: false, error: "backend_unavailable" };
624
+ if (!missionId)
625
+ return { ok: false, error: "no_mission_id" };
300
626
  const key = getEffectiveApiKey();
301
627
  try {
302
628
  await fetch(`${apiBase}/api/v1/missions/${missionId}/end`, {
@@ -320,7 +646,12 @@ function simpleHash(str) {
320
646
  }
321
647
  return "ph_" + Math.abs(hash).toString(36);
322
648
  }
323
- async function createAdoTask(title, description) {
649
+ function normalizeBugClassification(value) {
650
+ const text = String(value || "").trim();
651
+ const allowed = ["[Backend Bug]", "[Frontend Bug]", "[UI Regression]", "[Flaky/Stale Selector]"];
652
+ return allowed.find((category) => text.includes(category)) || "";
653
+ }
654
+ async function createAdoTask(title, description, metadata = {}) {
324
655
  const apiBase = getEffectiveApiBase();
325
656
  if (!apiBase) {
326
657
  if (process.env.DEBUG)
@@ -328,7 +659,7 @@ async function createAdoTask(title, description) {
328
659
  return { success: false, error: "no_api_url" };
329
660
  }
330
661
  const projectId = loadProjectId();
331
- if (!projectId) {
662
+ if (!isRealProjectId2(projectId)) {
332
663
  if (process.env.DEBUG)
333
664
  console.log(`[ADO] No hay Project ID configurado. Omitiendo creaci\xF3n de Task en Azure DevOps.`);
334
665
  return { success: false, error: "no_project_id" };
@@ -342,13 +673,18 @@ async function createAdoTask(title, description) {
342
673
  try {
343
674
  const controller = new AbortController();
344
675
  const timeout = setTimeout(() => controller.abort(), 15e3);
676
+ const bugClassification = normalizeBugClassification(metadata?.classification);
677
+ const taskTitle = bugClassification && !String(title).includes(bugClassification) ? `${bugClassification} ${title}` : title;
678
+ const taskDescription = bugClassification && !String(description).includes("Clasificacion") ? `**Clasificacion:** ${bugClassification}
679
+
680
+ ${description}` : description;
345
681
  const res = await fetch(`${apiBase}/api/v1/integrations/azuredevops/task`, {
346
682
  method: "POST",
347
683
  headers: {
348
684
  "Content-Type": "application/json",
349
685
  "x-api-key": key
350
686
  },
351
- body: JSON.stringify({ project_id: projectId, title, description }),
687
+ body: JSON.stringify({ project_id: projectId, title: taskTitle, description: taskDescription }),
352
688
  signal: controller.signal
353
689
  });
354
690
  clearTimeout(timeout);
@@ -377,7 +713,7 @@ async function createAdoTask(title, description) {
377
713
  }
378
714
  async function getEvidenceSasToken(missionId, projectId) {
379
715
  const apiBase = getEffectiveApiBase();
380
- if (!apiBase || !missionId)
716
+ if (!apiBase || !missionId || !isRealProjectId2(projectId))
381
717
  return null;
382
718
  const key = getEffectiveApiKey();
383
719
  if (!key)
@@ -404,13 +740,13 @@ async function getEvidenceSasToken(missionId, projectId) {
404
740
  return null;
405
741
  }
406
742
  }
407
- var import_node_fs2, import_node_path2, DAILY_MISSION_LIMIT, USAGE_FILE, ArcalityClient;
743
+ var import_node_fs2, import_node_path2, USAGE_FILE, ArcalityClient;
408
744
  var init_arcalityClient = __esm({
409
745
  "src/arcalityClient.mjs"() {
410
746
  import_node_fs2 = __toESM(require("node:fs"), 1);
411
747
  import_node_path2 = __toESM(require("node:path"), 1);
412
748
  init_configLoader();
413
- DAILY_MISSION_LIMIT = 35;
749
+ init_configManager();
414
750
  USAGE_FILE = import_node_path2.default.join(CONFIG_DIR, "usage.json");
415
751
  ArcalityClient = class {
416
752
  constructor(apiKey) {
@@ -815,12 +1151,24 @@ async function scan_sensitive_data_exposure(page) {
815
1151
  }
816
1152
 
817
1153
  // tests/_helpers/ai-agent-helper.ts
818
- var fs4 = __toESM(require("fs"));
819
- var path4 = __toESM(require("path"));
1154
+ var fs5 = __toESM(require("fs"));
1155
+ var path5 = __toESM(require("path"));
820
1156
 
821
1157
  // src/KnowledgeService.ts
822
1158
  var crypto = __toESM(require("crypto"));
823
1159
  var import_chalk = __toESM(require("chalk"));
1160
+ function isRealProjectId(projectId) {
1161
+ const id = String(projectId || "").trim();
1162
+ if (!id)
1163
+ return false;
1164
+ if (id === "undefined" || id === "null")
1165
+ return false;
1166
+ if (/^(local|mock)_/i.test(id))
1167
+ return false;
1168
+ if (/^0{8}-0{4}-0{4}-0{4}-0{12}$/i.test(id))
1169
+ return false;
1170
+ return true;
1171
+ }
824
1172
  var KnowledgeService = class _KnowledgeService {
825
1173
  static instance;
826
1174
  apiBase;
@@ -859,10 +1207,10 @@ var KnowledgeService = class _KnowledgeService {
859
1207
  this.projectId = id;
860
1208
  }
861
1209
  getProjectId() {
862
- if (!this.projectId || this.projectId === "undefined" || this.projectId === "null") {
1210
+ if (!isRealProjectId(this.projectId)) {
863
1211
  this.projectId = process.env.ARCALITY_PROJECT_ID || null;
864
1212
  }
865
- return this.projectId;
1213
+ return isRealProjectId(this.projectId) ? this.projectId : null;
866
1214
  }
867
1215
  /**
868
1216
  * 1. Gestión de Proyectos
@@ -877,7 +1225,8 @@ var KnowledgeService = class _KnowledgeService {
877
1225
  const data = await res.json();
878
1226
  return data.projects || [];
879
1227
  } catch (e) {
880
- console.warn(import_chalk.default.yellow(`[KnowledgeService] Error al obtener proyectos: ${e.message}`));
1228
+ if (process.env.DEBUG)
1229
+ console.warn(import_chalk.default.yellow(`[KnowledgeService] Error al obtener proyectos: ${e.message}`));
881
1230
  return [];
882
1231
  }
883
1232
  }
@@ -895,7 +1244,8 @@ var KnowledgeService = class _KnowledgeService {
895
1244
  return null;
896
1245
  return await res.json();
897
1246
  } catch (e) {
898
- console.error(import_chalk.default.red(`[KnowledgeService] Fall\xF3 creaci\xF3n de proyecto: ${e.message}`));
1247
+ if (process.env.DEBUG)
1248
+ console.error(import_chalk.default.red(`[KnowledgeService] Fall\xF3 creaci\xF3n de proyecto: ${e.message}`));
899
1249
  return null;
900
1250
  }
901
1251
  }
@@ -903,8 +1253,9 @@ var KnowledgeService = class _KnowledgeService {
903
1253
  * 2. Ingesta (Post-Percept)
904
1254
  */
905
1255
  async ingest(payload) {
906
- if (!payload.project_id || payload.project_id === "undefined") {
907
- console.warn(import_chalk.default.yellow(`[Knowledge] Ingesta cancelada: project_id es inv\xE1lido.`));
1256
+ if (!isRealProjectId(payload.project_id)) {
1257
+ if (process.env.DEBUG)
1258
+ console.warn(import_chalk.default.yellow(`[Knowledge] Ingesta cancelada: project_id es inv\xE1lido.`));
908
1259
  return;
909
1260
  }
910
1261
  try {
@@ -925,7 +1276,8 @@ var KnowledgeService = class _KnowledgeService {
925
1276
  }
926
1277
  }
927
1278
  } catch (e) {
928
- console.warn(import_chalk.default.yellow(`[Knowledge] Error en ingesta: ${e.message}`));
1279
+ if (process.env.DEBUG)
1280
+ console.warn(import_chalk.default.yellow(`[Knowledge] Error en ingesta: ${e.message}`));
929
1281
  }
930
1282
  }
931
1283
  /**
@@ -948,7 +1300,8 @@ var KnowledgeService = class _KnowledgeService {
948
1300
  return null;
949
1301
  return await res.json();
950
1302
  } catch (e) {
951
- console.warn(import_chalk.default.yellow(`[Knowledge] Error al obtener contexto: ${e.message}`));
1303
+ if (process.env.DEBUG)
1304
+ console.warn(import_chalk.default.yellow(`[Knowledge] Error al obtener contexto: ${e.message}`));
952
1305
  return null;
953
1306
  }
954
1307
  }
@@ -977,7 +1330,8 @@ var KnowledgeService = class _KnowledgeService {
977
1330
  severity
978
1331
  })
979
1332
  });
980
- console.log(import_chalk.default.green(`[Knowledge] Nueva regla registrada: ${title}`));
1333
+ if (process.env.DEBUG)
1334
+ console.log(import_chalk.default.green(`[Knowledge] Nueva regla registrada: ${title}`));
981
1335
  } catch (e) {
982
1336
  }
983
1337
  }
@@ -1004,7 +1358,8 @@ var KnowledgeService = class _KnowledgeService {
1004
1358
  source: "agent_auto"
1005
1359
  })
1006
1360
  });
1007
- console.log(import_chalk.default.green(`[Knowledge] Nuevo campo catalogado: ${identifier}`));
1361
+ if (process.env.DEBUG)
1362
+ console.log(import_chalk.default.green(`[Knowledge] Nuevo campo catalogado: ${identifier}`));
1008
1363
  } catch (e) {
1009
1364
  }
1010
1365
  }
@@ -1031,7 +1386,8 @@ var KnowledgeService = class _KnowledgeService {
1031
1386
  source: "agent_auto"
1032
1387
  })
1033
1388
  });
1034
- console.log(import_chalk.default.green(`[Knowledge] Nuevo conocimiento guardado: ${title}`));
1389
+ if (process.env.DEBUG)
1390
+ console.log(import_chalk.default.green(`[Knowledge] Nuevo conocimiento guardado: ${title}`));
1035
1391
  } catch (e) {
1036
1392
  }
1037
1393
  }
@@ -1052,23 +1408,33 @@ var getOrgId = () => process.env.ARCALITY_ORG_ID;
1052
1408
  var getMissionId = () => process.env.ARCALITY_MISSION_ID || EMPTY_GUID;
1053
1409
  var EMPTY_GUID = "00000000-0000-0000-0000-000000000000";
1054
1410
  var configWarningLogged = false;
1411
+ function hasRealProjectId(pid) {
1412
+ if (!pid || pid === EMPTY_GUID)
1413
+ return false;
1414
+ if (/^(local|mock)_/i.test(pid))
1415
+ return false;
1416
+ if (pid === "undefined" || pid === "null")
1417
+ return false;
1418
+ return true;
1419
+ }
1055
1420
  function isConfigured() {
1056
1421
  const base = getBase();
1057
1422
  const key = getKey();
1058
1423
  const pid = getPid();
1059
1424
  const orgId = getOrgId();
1060
- if (!base || !key || !pid || pid === EMPTY_GUID || !orgId) {
1425
+ if (!base || !key || !hasRealProjectId(pid) || !orgId) {
1061
1426
  if (!configWarningLogged) {
1062
1427
  const missing = [];
1063
1428
  if (!base)
1064
1429
  missing.push("ARCALITY_API_URL");
1065
1430
  if (!key)
1066
1431
  missing.push("ARCALITY_API_KEY");
1067
- if (!pid || pid === EMPTY_GUID)
1432
+ if (!hasRealProjectId(pid))
1068
1433
  missing.push("ARCALITY_PROJECT_ID");
1069
1434
  if (!orgId)
1070
1435
  missing.push("ARCALITY_ORG_ID");
1071
- console.warn(`[CollectiveMemory] Service disabled. Missing: ${missing.join(", ")}`);
1436
+ if (process.env.DEBUG)
1437
+ console.warn(`[CollectiveMemory] Service disabled. Missing: ${missing.join(", ")}`);
1072
1438
  configWarningLogged = true;
1073
1439
  }
1074
1440
  return false;
@@ -1188,8 +1554,10 @@ async function searchPromptPattern(prompt, limit = 5) {
1188
1554
  prompt,
1189
1555
  limit
1190
1556
  };
1191
- console.log(`[PatternSearch] \u{1F50D} Buscando patrones en: ${apiUrl}`);
1192
- console.log(`[PatternSearch] \u{1F4E6} Payload: ${JSON.stringify(payload, null, 2)}`);
1557
+ const debugPatternSearch = process.env.DEBUG === "true" || process.env.ARCALITY_DEBUG === "true";
1558
+ if (debugPatternSearch) {
1559
+ console.log(`[PatternSearch] POST ${apiUrl} | prompt_chars=${prompt.length} | limit=${limit}`);
1560
+ }
1193
1561
  const res = await fetch(apiUrl, {
1194
1562
  method: "POST",
1195
1563
  headers: {
@@ -1199,15 +1567,19 @@ async function searchPromptPattern(prompt, limit = 5) {
1199
1567
  body: JSON.stringify(payload)
1200
1568
  });
1201
1569
  if (!res.ok) {
1202
- console.warn(`[PatternSearch] HTTP ${res.status} from ${apiUrl}`);
1570
+ if (debugPatternSearch)
1571
+ console.warn(`[PatternSearch] HTTP ${res.status} from ${apiUrl}`);
1203
1572
  return [];
1204
1573
  }
1205
1574
  const data = await res.json();
1206
1575
  const results = Array.isArray(data) ? data : data.matches || data.results || [];
1207
- console.log(`[PatternSearch] \u2705 ${results.length} patrones encontrados.`);
1576
+ if (debugPatternSearch)
1577
+ console.log(`[PatternSearch] ${results.length} patrones encontrados.`);
1208
1578
  return results;
1209
1579
  } catch (err) {
1210
- console.warn(`[PatternSearch] \u26A0\uFE0F Error buscando patrones: ${err?.message || "unknown"}`);
1580
+ if (process.env.DEBUG === "true" || process.env.ARCALITY_DEBUG === "true") {
1581
+ console.warn(`[PatternSearch] Error buscando patrones: ${err?.message || "unknown"}`);
1582
+ }
1211
1583
  return [];
1212
1584
  }
1213
1585
  }
@@ -1450,25 +1822,29 @@ function formatQaContextSummary(analysis) {
1450
1822
  var qaSecurityTools = [
1451
1823
  {
1452
1824
  name: "test_xss_injection",
1453
- description: "Injects a standard XSS payload into an input field to test for reflected XSS vulnerabilities. Reports a vulnerability if an alert is triggered.",
1825
+ description: "QA Security Skill: Inject a standard XSS payload into a visible input field identified by IDX. Use this to test reflected XSS on a component from CURRENT COMPONENTS.",
1454
1826
  parameters: {
1455
1827
  type: "object",
1456
1828
  properties: {
1457
- selector: { type: "string", description: "The CSS selector of the input field to test." }
1829
+ idx: { type: "number", description: "The IDX of the input field from CURRENT COMPONENTS." },
1830
+ payload_type: {
1831
+ enum: ["script", "image", "svg"],
1832
+ description: "Payload variant to inject. Defaults to script.",
1833
+ default: "script"
1834
+ }
1458
1835
  },
1459
- required: ["selector"]
1836
+ required: ["idx"]
1460
1837
  }
1461
1838
  },
1462
1839
  {
1463
1840
  name: "test_auth_bypass",
1464
- description: "Attempts to directly navigate to a URL that should be protected to check for authentication bypass vulnerabilities.",
1841
+ description: "QA Security Skill: Attempts to directly navigate to a protected route to check for authentication bypass vulnerabilities.",
1465
1842
  parameters: {
1466
1843
  type: "object",
1467
1844
  properties: {
1468
- url: { type: "string", description: "The protected URL to test." },
1469
- redirectUrl: { type: "string", description: "The expected URL to be redirected to if unauthorized (e.g., '/login')." }
1845
+ target_url: { type: "string", description: "The protected URL or route to test. Relative paths are resolved against the current origin." }
1470
1846
  },
1471
- required: ["url", "redirectUrl"]
1847
+ required: ["target_url"]
1472
1848
  }
1473
1849
  },
1474
1850
  {
@@ -1558,29 +1934,29 @@ var AIAgentHelper = class {
1558
1934
  try {
1559
1935
  let skillsContext = "";
1560
1936
  let loadedCount = 0;
1561
- const toolsRoot = process.env.ARCALITY_ROOT || path4.join(__dirname, "..", "..");
1937
+ const toolsRoot = process.env.ARCALITY_ROOT || path5.join(__dirname, "..", "..");
1562
1938
  const possibleDirs = [
1563
- path4.join(toolsRoot, ".agent", "skills"),
1564
- path4.join(toolsRoot, ".agents", "skills")
1939
+ path5.join(toolsRoot, ".agent", "skills"),
1940
+ path5.join(toolsRoot, ".agents", "skills")
1565
1941
  ];
1566
1942
  for (const rootDir of possibleDirs) {
1567
- if (!fs4.existsSync(rootDir))
1943
+ if (!fs5.existsSync(rootDir))
1568
1944
  continue;
1569
1945
  if (loadedCount === 0) {
1570
1946
  skillsContext += "\n\u{1F6E0}\uFE0F SKILLS INSTALADAS (Metodolog\xEDas activas):\n";
1571
1947
  }
1572
- const entries = fs4.readdirSync(rootDir, { withFileTypes: true });
1948
+ const entries = fs5.readdirSync(rootDir, { withFileTypes: true });
1573
1949
  for (const entry of entries) {
1574
1950
  let content = "";
1575
1951
  let skillName = entry.name;
1576
1952
  if (entry.isDirectory()) {
1577
- const skillPath = path4.join(rootDir, entry.name, "SKILL.md");
1578
- if (fs4.existsSync(skillPath)) {
1579
- content = fs4.readFileSync(skillPath, "utf8");
1953
+ const skillPath = path5.join(rootDir, entry.name, "SKILL.md");
1954
+ if (fs5.existsSync(skillPath)) {
1955
+ content = fs5.readFileSync(skillPath, "utf8");
1580
1956
  }
1581
1957
  } else if (entry.name.endsWith(".md")) {
1582
- const skillPath = path4.join(rootDir, entry.name);
1583
- content = fs4.readFileSync(skillPath, "utf8");
1958
+ const skillPath = path5.join(rootDir, entry.name);
1959
+ content = fs5.readFileSync(skillPath, "utf8");
1584
1960
  }
1585
1961
  if (content) {
1586
1962
  skillsContext += `
@@ -1603,9 +1979,9 @@ ${content}
1603
1979
  }
1604
1980
  loadMemory(prompt) {
1605
1981
  try {
1606
- const memoryFile = path4.join(this.contextDir, "memoria-agente.json");
1607
- if (fs4.existsSync(memoryFile)) {
1608
- const memories = JSON.parse(fs4.readFileSync(memoryFile, "utf8"));
1982
+ const memoryFile = path5.join(this.contextDir, "memoria-agente.json");
1983
+ if (fs5.existsSync(memoryFile)) {
1984
+ const memories = JSON.parse(fs5.readFileSync(memoryFile, "utf8"));
1609
1985
  const keywords = prompt.toLowerCase().split(" ").filter((w) => w.length > 4);
1610
1986
  const relevantSuccess = memories.filter((m) => {
1611
1987
  if (!m.success)
@@ -1970,19 +2346,94 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
1970
2346
  components: sortedComponents
1971
2347
  };
1972
2348
  }
2349
+ fallbackFailureClassification(failReason, failReasoning = "") {
2350
+ const reason = `${failReason} ${failReasoning}`.toLowerCase();
2351
+ if (reason.includes("network") || reason.includes("backend") || reason.includes("server") || reason.includes("internal_error") || reason.includes("500")) {
2352
+ return "[Backend Bug]";
2353
+ }
2354
+ if (reason.includes("stale") || reason.includes("timeout") || reason.includes("selector") || reason.includes("idx")) {
2355
+ return "[Flaky/Stale Selector]";
2356
+ }
2357
+ if (reason.includes("render") || reason.includes("blank") || reason.includes("ui_validation")) {
2358
+ return "[UI Regression]";
2359
+ }
2360
+ return "[Frontend Bug]";
2361
+ }
2362
+ normalizeFailureClassification(raw) {
2363
+ const text = (raw || "").trim();
2364
+ const allowed = ["[Backend Bug]", "[Frontend Bug]", "[UI Regression]", "[Flaky/Stale Selector]"];
2365
+ return allowed.find((category) => text.includes(category)) || null;
2366
+ }
2367
+ /**
2368
+ * Classifies a terminal QA failure into a normalized bug bucket for reports and ADO.
2369
+ */
2370
+ async classifyFailure(prompt, failReason, failReasoning, history) {
2371
+ const fallback = this.fallbackFailureClassification(failReason, failReasoning);
2372
+ const isProxyMode = !!process.env.ARCALITY_API_URL;
2373
+ if (!isProxyMode && !process.env.ANTHROPIC_API_KEY)
2374
+ return fallback;
2375
+ try {
2376
+ const endpointUrl = isProxyMode ? `${process.env.ARCALITY_API_URL}/api/v1/ai/proxy` : "https://api.anthropic.com/v1/messages";
2377
+ const headers = {
2378
+ "Content-Type": "application/json"
2379
+ };
2380
+ if (isProxyMode) {
2381
+ headers["x-api-key"] = process.env.ARCALITY_API_KEY || "";
2382
+ const missionId = process.env.ARCALITY_MISSION_ID || "";
2383
+ if (missionId)
2384
+ headers["x-mission-id"] = missionId;
2385
+ } else {
2386
+ headers["x-api-key"] = process.env.ANTHROPIC_API_KEY;
2387
+ headers["anthropic-version"] = "2023-06-01";
2388
+ }
2389
+ const diagnosticPayload = {
2390
+ mission: prompt,
2391
+ fail_reason: failReason,
2392
+ fail_reasoning: failReasoning || "None",
2393
+ critical_network_error: this.criticalNetworkError || "None",
2394
+ critical_js_error: this.criticalJsError || "None",
2395
+ console_logs: this.logs.slice(-30),
2396
+ recent_history: history.slice(-12)
2397
+ };
2398
+ const response = await fetch(endpointUrl, {
2399
+ method: "POST",
2400
+ headers,
2401
+ body: JSON.stringify({
2402
+ model: process.env.CLAUDE_MODEL || "claude-4-5-sonnet-latest",
2403
+ max_tokens: 32,
2404
+ system: "You classify terminal E2E QA failures. Reply with exactly one category string and no explanation: [Backend Bug], [Frontend Bug], [UI Regression], or [Flaky/Stale Selector].",
2405
+ messages: [
2406
+ {
2407
+ role: "user",
2408
+ content: `Classify this Arcality QA failure:
2409
+ ${JSON.stringify(diagnosticPayload, null, 2)}`
2410
+ }
2411
+ ],
2412
+ temperature: 0
2413
+ })
2414
+ });
2415
+ if (!response.ok)
2416
+ return fallback;
2417
+ const data = await response.json();
2418
+ const classification = this.normalizeFailureClassification(data.content?.[0]?.text);
2419
+ return classification || fallback;
2420
+ } catch {
2421
+ return fallback;
2422
+ }
2423
+ }
1973
2424
  /**
1974
2425
  * Busca una acción sugerida en la memoria SIN usar tokens.
1975
2426
  * Compara URL, componentes visuales Y palabras clave del prompt.
1976
2427
  */
1977
2428
  async getActionFromGuia(prompt, historyLength) {
1978
2429
  try {
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)) {
2430
+ const fs7 = require("fs");
2431
+ const path7 = require("path");
2432
+ const memoryFile = path7.join(this.contextDir, "memoria-agente.json");
2433
+ if (!fs7.existsSync(memoryFile)) {
1983
2434
  return null;
1984
2435
  }
1985
- const memories = JSON.parse(fs6.readFileSync(memoryFile, "utf8"));
2436
+ const memories = JSON.parse(fs7.readFileSync(memoryFile, "utf8"));
1986
2437
  const state = await this.getPageState();
1987
2438
  const currentUrl = this.page.url();
1988
2439
  const extractCriticalKeywords = (text) => {
@@ -2051,8 +2502,14 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
2051
2502
  let bestMatch = null;
2052
2503
  let bestMatchScore = -1;
2053
2504
  for (const c of state.components) {
2054
- const cleanMemName = (memStep.componentName || "").replace(/\[.*?\]/g, "").replace(/\(.*?\)/g, "").trim().toLowerCase();
2055
- const cleanCurrName = (c.name || "").replace(/\[.*?\]/g, "").replace(/\(.*?\)/g, "").trim().toLowerCase();
2505
+ let cleanMemName = (memStep.componentName || "").replace(/\[.*?\]/g, "").replace(/\(.*?\)/g, "").trim().toLowerCase();
2506
+ if (!cleanMemName) {
2507
+ cleanMemName = (memStep.componentName || "").trim().toLowerCase();
2508
+ }
2509
+ let cleanCurrName = (c.name || "").replace(/\[.*?\]/g, "").replace(/\(.*?\)/g, "").trim().toLowerCase();
2510
+ if (!cleanCurrName) {
2511
+ cleanCurrName = (c.name || "").trim().toLowerCase();
2512
+ }
2056
2513
  let score = -1;
2057
2514
  if (c.selector === memStep.action_data?.selector) {
2058
2515
  score = 100;
@@ -2222,6 +2679,18 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
2222
2679
  parameters: t.input_schema || t.parameters
2223
2680
  }))
2224
2681
  ];
2682
+ const anthropicTools = rawTools.map((t) => ({
2683
+ name: t.name,
2684
+ description: t.description,
2685
+ input_schema: t.parameters || t.input_schema
2686
+ }));
2687
+ if (anthropicTools.length > 0) {
2688
+ const lastTool = anthropicTools[anthropicTools.length - 1];
2689
+ anthropicTools[anthropicTools.length - 1] = {
2690
+ ...lastTool,
2691
+ cache_control: { type: "ephemeral" }
2692
+ };
2693
+ }
2225
2694
  const credentialsContext = process.env.LOGIN_USER && process.env.LOGIN_PASSWORD ? `
2226
2695
  \u{1F511} QA CREDENTIALS (Use if login is needed):
2227
2696
  - User: ${process.env.LOGIN_USER}
@@ -2229,6 +2698,12 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
2229
2698
  ` : "";
2230
2699
  const memoryContext = this.loadMemory(prompt);
2231
2700
  const skillsContext = this.loadSkills();
2701
+ let assetContext = "";
2702
+ try {
2703
+ const { formatUploadAssetContext: formatUploadAssetContext2 } = (init_asset_resolver(), __toCommonJS(asset_resolver_exports));
2704
+ assetContext = formatUploadAssetContext2();
2705
+ } catch {
2706
+ }
2232
2707
  let projectInfo = "";
2233
2708
  try {
2234
2709
  const { getProjectContext } = require("../src/projectInspector");
@@ -2261,18 +2736,20 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
2261
2736
  contextData.fields.forEach((f) => memoryBackendContext += `- ${f.field_identifier} (${f.field_type}) - Required: ${f.is_required}
2262
2737
  `);
2263
2738
  }
2264
- console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Contexto de Memoria Colectiva inyectado (${contextData.rules?.length || 0} reglas, ${contextData.fields?.length || 0} campos).`);
2739
+ if (process.env.DEBUG)
2740
+ console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Contexto de Memoria Colectiva inyectado (${contextData.rules?.length || 0} reglas, ${contextData.fields?.length || 0} campos).`);
2265
2741
  }
2266
2742
  } catch (e) {
2267
- console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Error en hook de contexto: ${e.message}`);
2743
+ if (process.env.DEBUG)
2744
+ console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Error en hook de contexto: ${e.message}`);
2268
2745
  }
2269
2746
  let customUserContext = "";
2270
2747
  try {
2271
- const fs6 = require("fs");
2272
- const path6 = require("path");
2273
- const customContextPath = path6.join(process.cwd(), ".arcality", "qa-context.md");
2748
+ const fs7 = require("fs");
2749
+ const path7 = require("path");
2750
+ const customContextPath = path7.join(process.cwd(), ".arcality", "qa-context.md");
2274
2751
  if (false) {
2275
- const content = fs6.readFileSync(customContextPath, "utf8");
2752
+ const content = fs7.readFileSync(customContextPath, "utf8");
2276
2753
  customUserContext = `
2277
2754
  <CUSTOMER_BUSINESS_RULES>
2278
2755
  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:
@@ -2320,15 +2797,7 @@ ${this.qaContextPromptCache}
2320
2797
  text: `# IDENTITY
2321
2798
  You are **Arcality**, an elite Senior QA Web Testing Agent. You have 10+ years of experience in manual and automated testing. You are meticulous, thorough, and NEVER take shortcuts. You treat every mission as a real client engagement where your professional reputation is on the line. You MUST use your full arsenal of QA tools \u2014 guessing and blind retries are UNACCEPTABLE.
2322
2799
 
2323
- # MISSION
2324
- ${prompt}
2325
-
2326
- ${projectInfo}
2327
- ${skillsContext}
2328
- ${customUserContext}
2329
- ${memoryContext}
2330
- ${credentialsContext}
2331
- ${memoryBackendContext}`
2800
+ `
2332
2801
  },
2333
2802
  {
2334
2803
  type: "text",
@@ -2426,6 +2895,13 @@ These are NOT optional. You MUST use these tools in these situations:
2426
2895
  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
2896
  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.
2428
2897
 
2898
+ 15. **FILE UPLOAD FIELDS (MANDATORY PROTOCOL)**: When the current step requires uploading an image or file (a dropzone component with text like "arrastra", "explora", "formatos", "JPEG", "PNG", or an input with aria-label containing "imagen" or "file"):
2899
+ - You MUST use the \`fill\` action on the file input with one of the values from the \`# AVAILABLE UPLOAD ASSETS\` section below (format: \`arcasset://<asset_id>\`).
2900
+ - If no asset matches the required type, use the value \`"placeholder:image/banner"\` \u2014 the runner will automatically generate a valid placeholder PNG image.
2901
+ - **NEVER** report a bug or inability to proceed just because you see a file upload field. You ALWAYS have a fallback.
2902
+ - **NEVER** skip the image step. If clicking "Siguiente" is blocked by a required image field, locate the file input and use \`fill\` with \`arcasset://\` or \`placeholder:image/banner\`.
2903
+ - To locate the file input: look for a component with type \`[input]\` adjacent to dropzone text, OR use \`inspect_element_details\` on the dropzone container to find its nested \`input[type=file]\`.
2904
+
2429
2905
  # WIZARD & MULTI-STEP FORMS (CRITICAL)
2430
2906
  When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 Step 3):
2431
2907
 
@@ -2465,7 +2941,22 @@ When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 St
2465
2941
 
2466
2942
  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.
2467
2943
 
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.`
2944
+ 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.`,
2945
+ cache_control: { type: "ephemeral" }
2946
+ },
2947
+ {
2948
+ type: "text",
2949
+ text: `
2950
+ # MISSION
2951
+ ${prompt}
2952
+
2953
+ ${projectInfo}
2954
+ ${skillsContext}
2955
+ ${customUserContext}
2956
+ ${memoryContext}
2957
+ ${credentialsContext}
2958
+ ${memoryBackendContext}
2959
+ ${assetContext}`
2469
2960
  }
2470
2961
  ];
2471
2962
  if (this.testInfo) {
@@ -2549,11 +3040,7 @@ ${history.slice(-25).join("\n") || "None"}`
2549
3040
  model: process.env.CLAUDE_MODEL || "claude-4-5-sonnet-latest",
2550
3041
  max_tokens: 4096,
2551
3042
  system: systemPromptBlocks,
2552
- tools: rawTools.map((t) => ({
2553
- name: t.name,
2554
- description: t.description,
2555
- input_schema: t.parameters || t.input_schema
2556
- })),
3043
+ tools: anthropicTools,
2557
3044
  messages: anthropicMessages,
2558
3045
  temperature: 0.2
2559
3046
  })
@@ -2576,7 +3063,7 @@ ${history.slice(-25).join("\n") || "None"}`
2576
3063
  const delay = Math.pow(2, retryCount) * 1e3;
2577
3064
  console.log(`
2578
3065
  >>ARCALITY_STATUS>> \u26A0\uFE0F Arcality est\xE1 saturado (${response.status}). Reintentando...`);
2579
- await new Promise((resolve) => setTimeout(resolve, delay));
3066
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
2580
3067
  continue;
2581
3068
  }
2582
3069
  throw new Error(`Arcality Brain Error ${response.status}: ${errorText}`);
@@ -2891,7 +3378,7 @@ ${history.slice(-25).join("\n") || "None"}`
2891
3378
  }
2892
3379
  }
2893
3380
  } else if (toolName === "test_xss_injection") {
2894
- const { idx, payload_type } = toolInput;
3381
+ const { idx, payload_type = "script" } = toolInput;
2895
3382
  const el = state.components.find((c) => c.idx === idx);
2896
3383
  if (!el) {
2897
3384
  toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Element idx ${idx} not found.` });
@@ -2903,7 +3390,8 @@ ${history.slice(-25).join("\n") || "None"}`
2903
3390
  if (payload_type === "svg")
2904
3391
  payload = "<svg onload=alert(1)>";
2905
3392
  try {
2906
- const locator = this.page.locator(`xpath=${el.selector}`).first();
3393
+ const frame = this.page.frames()[el.frameIdx] || this.page.mainFrame();
3394
+ const locator = frame.locator(el.selector).first();
2907
3395
  await locator.fill(payload);
2908
3396
  toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Payload inyectado. Aseg\xFArate de probar si el payload fue ejecutado revisando el DOM, o realiza el sumbit y observa si el input regres\xF3 renderizado tal cual o sanitizado.` });
2909
3397
  if (this.testInfo)
@@ -3428,9 +3916,10 @@ var SecurityScanner = class {
3428
3916
 
3429
3917
  // tests/_helpers/agentic-runner.spec.ts
3430
3918
  var import_config = require("dotenv/config");
3431
- var fs5 = __toESM(require("fs"));
3432
- var path5 = __toESM(require("path"));
3919
+ var fs6 = __toESM(require("fs"));
3920
+ var path6 = __toESM(require("path"));
3433
3921
  var crypto2 = __toESM(require("crypto"));
3922
+ init_asset_resolver();
3434
3923
  var _sessionRuleCache = /* @__PURE__ */ new Set();
3435
3924
  function captureValidationRule(errorMessage, context) {
3436
3925
  const key = errorMessage.substring(0, 80).toLowerCase().trim();
@@ -3445,6 +3934,31 @@ function captureValidationRule(errorMessage, context) {
3445
3934
  }).catch(() => {
3446
3935
  });
3447
3936
  }
3937
+ async function readLocatorText(locator) {
3938
+ const textContent = await locator.textContent().catch(() => null);
3939
+ if (typeof textContent === "string") {
3940
+ const normalized = textContent.trim();
3941
+ if (normalized)
3942
+ return normalized;
3943
+ }
3944
+ const innerText = await locator.innerText().catch(() => "");
3945
+ return typeof innerText === "string" ? innerText.trim() : "";
3946
+ }
3947
+ function writeBatchMissionResult(payload) {
3948
+ const runDomainDir = process.env.ARCALITY_RUN_DOMAIN_DIR;
3949
+ if (!runDomainDir)
3950
+ return;
3951
+ try {
3952
+ if (!fs6.existsSync(runDomainDir)) {
3953
+ fs6.mkdirSync(runDomainDir, { recursive: true });
3954
+ }
3955
+ fs6.writeFileSync(
3956
+ path6.join(runDomainDir, "mission-result.json"),
3957
+ JSON.stringify(payload, null, 2)
3958
+ );
3959
+ } catch {
3960
+ }
3961
+ }
3448
3962
  (0, import_test.test)("Arcality AI Runner", async ({ page }, testInfo) => {
3449
3963
  import_test.test.setTimeout(12e5);
3450
3964
  const contextDir = process.env.CONTEXT_DIR || "out";
@@ -3460,10 +3974,15 @@ function captureValidationRule(errorMessage, context) {
3460
3974
  const history = [];
3461
3975
  const urlHistory = [];
3462
3976
  const stepsData = [];
3463
- const memoryFile = path5.join(contextDir, "memoria-arcality.json");
3464
- if (fs5.existsSync(memoryFile)) {
3977
+ try {
3978
+ await syncAssetsFromBackend();
3979
+ } catch (e) {
3980
+ console.warn(`>>ARCALITY_STATUS>> \u26A0\uFE0F Error al sincronizar assets: ${e.message}`);
3981
+ }
3982
+ const memoryFile = path6.join(contextDir, "memoria-arcality.json");
3983
+ if (fs6.existsSync(memoryFile)) {
3465
3984
  try {
3466
- fs5.unlinkSync(memoryFile);
3985
+ fs6.unlinkSync(memoryFile);
3467
3986
  } catch {
3468
3987
  }
3469
3988
  }
@@ -3502,18 +4021,34 @@ function captureValidationRule(errorMessage, context) {
3502
4021
  let lastSeenErrorToast = "";
3503
4022
  let accumulatedCost = 0;
3504
4023
  let totalMissionUsage = { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 };
4024
+ let bugClassification = "";
4025
+ const missionSummaryPaths = /* @__PURE__ */ new Set();
4026
+ const updateSavedMissionClassification = () => {
4027
+ if (!bugClassification)
4028
+ return;
4029
+ for (const summaryPath of missionSummaryPaths) {
4030
+ try {
4031
+ if (!fs6.existsSync(summaryPath))
4032
+ continue;
4033
+ const data = JSON.parse(fs6.readFileSync(summaryPath, "utf8"));
4034
+ data.bug_classification = bugClassification;
4035
+ fs6.writeFileSync(summaryPath, JSON.stringify(data, null, 2));
4036
+ } catch {
4037
+ }
4038
+ }
4039
+ };
3505
4040
  const saveMissionResults = () => {
3506
4041
  if (resultsSaved)
3507
4042
  return;
3508
4043
  resultsSaved = true;
3509
- if (!fs5.existsSync(contextDir))
3510
- fs5.mkdirSync(contextDir, { recursive: true });
4044
+ if (!fs6.existsSync(contextDir))
4045
+ fs6.mkdirSync(contextDir, { recursive: true });
3511
4046
  const finalSuccess = aiMarkedSuccess && !hasCriticalError;
3512
4047
  try {
3513
- const memoryFile2 = path5.join(contextDir, "memoria-arcality.json");
4048
+ const memoryFile2 = path6.join(contextDir, "memoria-arcality.json");
3514
4049
  let memories = [];
3515
- if (fs5.existsSync(memoryFile2)) {
3516
- const content = fs5.readFileSync(memoryFile2, "utf8").trim();
4050
+ if (fs6.existsSync(memoryFile2)) {
4051
+ const content = fs6.readFileSync(memoryFile2, "utf8").trim();
3517
4052
  if (content) {
3518
4053
  try {
3519
4054
  memories = JSON.parse(content);
@@ -3584,19 +4119,64 @@ function captureValidationRule(errorMessage, context) {
3584
4119
  error: hasCriticalError,
3585
4120
  fail_reason: failReason,
3586
4121
  fail_reasoning: failReasoning,
4122
+ bug_classification: bugClassification || null,
3587
4123
  timestamp: Date.now()
3588
4124
  };
3589
4125
  memories.push(missionData);
3590
- fs5.writeFileSync(memoryFile2, JSON.stringify(memories, null, 2));
3591
- console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Memoria guardada: ${cleanSteps.length} pasos limpios (${rawSteps.length - cleanSteps.length} corruptos filtrados)`);
4126
+ fs6.writeFileSync(memoryFile2, JSON.stringify(memories, null, 2));
4127
+ if (process.env.DEBUG)
4128
+ console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Memoria guardada: ${cleanSteps.length} pasos limpios (${rawSteps.length - cleanSteps.length} corruptos filtrados)`);
3592
4129
  } catch (err) {
3593
4130
  console.error("\u274C Fall\xF3 al guardar memoria:", err);
3594
4131
  }
3595
4132
  try {
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));
4133
+ const timestampIso = (/* @__PURE__ */ new Date()).toISOString();
4134
+ const timestampMs = Date.now();
4135
+ const summaryPayload = {
4136
+ prompt,
4137
+ history,
4138
+ steps: stepCount,
4139
+ success: finalSuccess,
4140
+ error: hasCriticalError,
4141
+ fail_reason: failReason,
4142
+ fail_reasoning: failReasoning,
4143
+ bug_classification: bugClassification || null,
4144
+ usage: totalMissionUsage,
4145
+ target: process.env.BASE_URL || "",
4146
+ target_path: process.env.TARGET_PATH || "",
4147
+ report_dir: process.env.REPORTS_DIR || "",
4148
+ timestamp: timestampIso
4149
+ };
4150
+ const logPath = path6.join(contextDir, `agent-log-${timestampMs}.json`);
4151
+ fs6.writeFileSync(logPath, JSON.stringify(summaryPayload, null, 2));
4152
+ missionSummaryPaths.add(logPath);
4153
+ const runDomainDir = process.env.ARCALITY_RUN_DOMAIN_DIR || path6.dirname(contextDir);
4154
+ const durableLogsDir = path6.join(runDomainDir, "logs");
4155
+ fs6.mkdirSync(durableLogsDir, { recursive: true });
4156
+ const durableLogPath = path6.join(durableLogsDir, `mission-log-${timestampMs}.json`);
4157
+ fs6.writeFileSync(durableLogPath, JSON.stringify(summaryPayload, null, 2));
4158
+ missionSummaryPaths.add(durableLogPath);
4159
+ writeBatchMissionResult(summaryPayload);
4160
+ if (runDomainDir) {
4161
+ missionSummaryPaths.add(path6.join(runDomainDir, "mission-result.json"));
4162
+ }
3598
4163
  try {
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");
4164
+ const historyLine = JSON.stringify({
4165
+ ts: timestampIso,
4166
+ mission: prompt.substring(0, 150),
4167
+ success: finalSuccess,
4168
+ steps: stepCount,
4169
+ fail_reason: failReason,
4170
+ report_dir: process.env.REPORTS_DIR || ""
4171
+ }) + "\n";
4172
+ fs6.appendFileSync(
4173
+ path6.join(contextDir, "arcality-history.log"),
4174
+ historyLine
4175
+ );
4176
+ fs6.appendFileSync(
4177
+ path6.join(durableLogsDir, "arcality-history.log"),
4178
+ historyLine
4179
+ );
3600
4180
  } catch (e) {
3601
4181
  }
3602
4182
  } catch (err) {
@@ -3618,7 +4198,8 @@ function captureValidationRule(errorMessage, context) {
3618
4198
  return;
3619
4199
  }
3620
4200
  if (!isFailed && stepsData.length === 0 && stepsDataBackup.length === 0) {
3621
- console.warn(`[CollectiveMemory] \u26A0\uFE0F stepsData vac\xEDo en path de \xE9xito \u2014 intentando usar backup...`);
4201
+ if (process.env.DEBUG)
4202
+ console.warn(`[CollectiveMemory] \u26A0\uFE0F stepsData vac\xEDo en path de \xE9xito \u2014 intentando usar backup...`);
3622
4203
  }
3623
4204
  const baseSteps = stepsData.length > 0 ? stepsData : stepsDataBackup;
3624
4205
  const effectiveSteps = baseSteps.filter((step) => {
@@ -3629,11 +4210,15 @@ function captureValidationRule(errorMessage, context) {
3629
4210
  }
3630
4211
  return true;
3631
4212
  });
3632
- console.log(`
4213
+ if (process.env.DEBUG)
4214
+ console.log(`
3633
4215
  ======================================================`);
3634
- console.log(`\u{1F9E0} [ARCHIVE SUBSYSTEM - KNOWLEDGE INGESTION]`);
3635
- console.log(`>>arcality>> Saving telemetry (project: ${pid}, steps: ${effectiveSteps.length}, isFailed: ${isFailed})...`);
3636
- console.log(`======================================================
4216
+ if (process.env.DEBUG)
4217
+ console.log(`\u{1F9E0} [ARCHIVE SUBSYSTEM - KNOWLEDGE INGESTION]`);
4218
+ if (process.env.DEBUG)
4219
+ console.log(`>>arcality>> Saving telemetry (project: ${pid}, steps: ${effectiveSteps.length}, isFailed: ${isFailed})...`);
4220
+ if (process.env.DEBUG)
4221
+ console.log(`======================================================
3637
4222
  `);
3638
4223
  try {
3639
4224
  const stepsNarrative = effectiveSteps.map((s) => `${s.action_data?.action || "action"} "${s.componentName}" en ${s.url}`).join(" \u2192 ").substring(0, 500);
@@ -3644,9 +4229,9 @@ function captureValidationRule(errorMessage, context) {
3644
4229
  title: `Flujo exitoso: ${prompt.substring(0, 100)}`,
3645
4230
  content: stepsNarrative || prompt.substring(0, 500)
3646
4231
  });
3647
- if (knowledgeId)
4232
+ if (process.env.DEBUG && knowledgeId)
3648
4233
  console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Knowledge guardado: ${knowledgeId}`);
3649
- else
4234
+ else if (process.env.DEBUG)
3650
4235
  console.warn(`[CollectiveMemory] \u26A0\uFE0F pushKnowledge retorn\xF3 null (revisar HTTP status arriba)`);
3651
4236
  const uniqueUrls = [...new Set(effectiveSteps.map((s) => s.url).filter(Boolean))];
3652
4237
  if (uniqueUrls.length > 1) {
@@ -3656,7 +4241,7 @@ function captureValidationRule(errorMessage, context) {
3656
4241
  description: `Arcality naveg\xF3 ${uniqueUrls.length} p\xE1ginas: ${uniqueUrls.join(" \u2192 ").substring(0, 400)}`,
3657
4242
  severity: "suggestion"
3658
4243
  });
3659
- if (ruleId)
4244
+ if (process.env.DEBUG && ruleId)
3660
4245
  console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Regla NAVIGATION guardada: ${ruleId}`);
3661
4246
  }
3662
4247
  const submitStep = effectiveSteps.find(
@@ -3669,7 +4254,7 @@ function captureValidationRule(errorMessage, context) {
3669
4254
  description: `Flujo exitoso incluy\xF3 guardado/creaci\xF3n. Pasos: ${stepsNarrative.substring(0, 400)}`,
3670
4255
  severity: "important"
3671
4256
  });
3672
- if (ruleId2)
4257
+ if (process.env.DEBUG && ruleId2)
3673
4258
  console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Regla UI_UX guardada: ${ruleId2}`);
3674
4259
  }
3675
4260
  } else {
@@ -3679,13 +4264,14 @@ function captureValidationRule(errorMessage, context) {
3679
4264
  description: `Arcality no pudo completar: "${prompt.substring(0, 200)}". \xDAltimos pasos: ${stepsNarrative.substring(0, 300)}`,
3680
4265
  severity: "important"
3681
4266
  });
3682
- if (failRuleId)
4267
+ if (process.env.DEBUG && failRuleId)
3683
4268
  console.log(`\u{1F9E0} [COLLECTIVE MEMORY] \u2705 Regla de fallo guardada: ${failRuleId}`);
3684
- else
4269
+ else if (process.env.DEBUG)
3685
4270
  console.warn(`[CollectiveMemory] \u26A0\uFE0F No se pudo persistir misi\xF3n fallida`);
3686
4271
  }
3687
4272
  } catch (err) {
3688
- console.warn(`[CollectiveMemory] \u274C Error inesperado en persistCollectiveMemory: ${err?.message}`);
4273
+ if (process.env.DEBUG)
4274
+ console.warn(`[CollectiveMemory] \u274C Error inesperado en persistCollectiveMemory: ${err?.message}`);
3689
4275
  }
3690
4276
  };
3691
4277
  console.log(`
@@ -3750,7 +4336,8 @@ function captureValidationRule(errorMessage, context) {
3750
4336
  const matches = promptKeywords.filter((kw) => patternKeywords.some((pkw) => pkw.includes(kw) || kw.includes(pkw))).length;
3751
4337
  const isVectorMatch = true;
3752
4338
  if (isVectorMatch) {
3753
- console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Memoria Colectiva detectada (id: ${bestMatch.id}). Descargando a memoria local...`);
4339
+ if (process.env.DEBUG)
4340
+ console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Memoria Colectiva detectada (id: ${bestMatch.id}). Descargando a memoria local...`);
3754
4341
  reusedPatternId = bestMatch.id || "unknown";
3755
4342
  let patternJsonObj = {};
3756
4343
  try {
@@ -3782,11 +4369,11 @@ function captureValidationRule(errorMessage, context) {
3782
4369
  timestamp: Date.now()
3783
4370
  };
3784
4371
  try {
3785
- const memoryFile2 = path5.join(contextDir, "memoria-agente.json");
4372
+ const memoryFile2 = path6.join(contextDir, "memoria-agente.json");
3786
4373
  let memories = [];
3787
- if (fs5.existsSync(memoryFile2)) {
4374
+ if (fs6.existsSync(memoryFile2)) {
3788
4375
  try {
3789
- memories = JSON.parse(fs5.readFileSync(memoryFile2, "utf8"));
4376
+ memories = JSON.parse(fs6.readFileSync(memoryFile2, "utf8"));
3790
4377
  } catch {
3791
4378
  memories = [];
3792
4379
  }
@@ -3798,14 +4385,14 @@ function captureValidationRule(errorMessage, context) {
3798
4385
  if (existingIdx < 0) {
3799
4386
  if (resolvedStepsData.length > 0) {
3800
4387
  memories.push(localMemoryData);
3801
- fs5.writeFileSync(memoryFile2, JSON.stringify(memories, null, 2));
4388
+ fs6.writeFileSync(memoryFile2, JSON.stringify(memories, null, 2));
3802
4389
  console.log(`>>ARCALITY_STATUS>> \u2728 Memoria sincronizada en memoria-agente.json (${resolvedStepsData.length} pasos). Arcality entrar\xE1 en MODO GU\xCDA.`);
3803
4390
  } else {
3804
4391
  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).`);
3805
4392
  }
3806
4393
  } else if (!existingHasSteps && resolvedStepsData.length > 0) {
3807
4394
  memories[existingIdx] = localMemoryData;
3808
- fs5.writeFileSync(memoryFile2, JSON.stringify(memories, null, 2));
4395
+ fs6.writeFileSync(memoryFile2, JSON.stringify(memories, null, 2));
3809
4396
  console.log(`>>ARCALITY_STATUS>> \u{1F504} Patr\xF3n corrupto (0 pasos) sobreescrito en memoria-agente.json (${resolvedStepsData.length} pasos nuevos). MODO GU\xCDA activado.`);
3810
4397
  } else {
3811
4398
  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.`);
@@ -3869,7 +4456,7 @@ ${readableHistory}`;
3869
4456
  let foundSuccessToast = false;
3870
4457
  for (const el of feedbackEls) {
3871
4458
  if (await el.isVisible().catch(() => false)) {
3872
- const txt = await el.innerText().catch(() => "");
4459
+ const txt = await readLocatorText(el);
3873
4460
  if (successKeywords.test(txt.toLowerCase())) {
3874
4461
  foundSuccessToast = true;
3875
4462
  console.log(`>>ARCALITY_STATUS>> \u2728 Auto-Success: Toast de \xE9xito detectado: "${txt.substring(0, 60)}"`);
@@ -3894,7 +4481,7 @@ ${readableHistory}`;
3894
4481
  if (btnCount > 0) {
3895
4482
  const firstBtn = finalSaveBtn.first();
3896
4483
  if (await firstBtn.isVisible().catch(() => false) && await firstBtn.isEnabled().catch(() => false)) {
3897
- const btnText = await firstBtn.innerText().catch(() => "");
4484
+ const btnText = await readLocatorText(firstBtn);
3898
4485
  const isModalOpen = await page.locator('[role="dialog"], .modal, .mat-dialog-container').first().isVisible().catch(() => false);
3899
4486
  if (!isModalOpen) {
3900
4487
  console.log(`
@@ -3907,7 +4494,7 @@ ${readableHistory}`;
3907
4494
  let proactiveSuccess = false;
3908
4495
  for (const fb of postSaveFb) {
3909
4496
  if (await fb.isVisible().catch(() => false)) {
3910
- const fbTxt = await fb.innerText().catch(() => "");
4497
+ const fbTxt = await readLocatorText(fb);
3911
4498
  if (successKeywords.test(fbTxt)) {
3912
4499
  console.log(`>>ARCALITY_STATUS>> \u2728 [PROACTIVE FINISH] \xC9xito confirmado por toast: "${fbTxt.substring(0, 60)}"`);
3913
4500
  proactiveSuccess = true;
@@ -3933,7 +4520,7 @@ ${readableHistory}`;
3933
4520
  const initialToasts = await page.locator('.toast, .alert, [role="status"], [role="alert"]').all();
3934
4521
  for (const t of initialToasts) {
3935
4522
  if (await t.isVisible().catch(() => false)) {
3936
- lastSeenSuccessToast = await t.innerText().catch(() => "");
4523
+ lastSeenSuccessToast = await readLocatorText(t);
3937
4524
  if (lastSeenSuccessToast)
3938
4525
  console.log(`>>ARCALITY_STATUS>> \u{1F4A1} Nota: Ignorando mensaje preexistente: "${lastSeenSuccessToast.substring(0, 30)}..."`);
3939
4526
  }
@@ -3967,7 +4554,7 @@ Detalles de la excepci\xF3n en consola: ${jsError}`;
3967
4554
  const systemToastEls = await page.locator('.toast, .alert, [role="alert"], [role="status"], .snackbar, .notification, .error-message, .mat-snack-bar-container').all();
3968
4555
  for (const el of systemToastEls) {
3969
4556
  if (await el.isVisible().catch(() => false)) {
3970
- const sysErrTxt = await el.innerText().catch(() => "");
4557
+ const sysErrTxt = await readLocatorText(el);
3971
4558
  if (sysErrTxt && systemErrorKeywords.test(sysErrTxt)) {
3972
4559
  console.error(`
3973
4560
  \u{1F6D1} [SYSTEM ERROR DETECTADO] El portal report\xF3 un error de sistema no recuperable en UI:`);
@@ -3992,7 +4579,7 @@ Detalles de la excepci\xF3n en consola: ${jsError}`;
3992
4579
  const errorEls = await page.locator('.toast, .alert, [role="alert"], [role="status"], .snackbar, .notification, .error-message, .mat-snack-bar-container, [class*="alert"], [class*="toast"]').all();
3993
4580
  for (const el of errorEls) {
3994
4581
  if (await el.isVisible().catch(() => false)) {
3995
- const errTxt = await el.innerText().catch(() => "");
4582
+ const errTxt = await readLocatorText(el);
3996
4583
  if (errTxt && failureKeywords.test(errTxt.toLowerCase())) {
3997
4584
  hasVisibleError = true;
3998
4585
  break;
@@ -4022,7 +4609,7 @@ Detalles de la excepci\xF3n en consola: ${jsError}`;
4022
4609
  const errorEls = await page.locator('.toast, .alert, [role="alert"], [role="status"], .snackbar, .notification, .error-message, .mat-snack-bar-container, [class*="alert"], [class*="toast"]').all();
4023
4610
  for (const el of errorEls) {
4024
4611
  if (await el.isVisible().catch(() => false)) {
4025
- const errTxt = await el.innerText().catch(() => "");
4612
+ const errTxt = await readLocatorText(el);
4026
4613
  if (errTxt && failureKeywords.test(errTxt.toLowerCase())) {
4027
4614
  const isDuplicateError = /repetido|duplicado|ya existe|already exists/i.test(errTxt);
4028
4615
  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.`;
@@ -4075,7 +4662,16 @@ ${patternContext}` : prompt;
4075
4662
  saveMissionResults();
4076
4663
  break;
4077
4664
  } else {
4078
- throw err;
4665
+ const errMsg = err?.message || String(err);
4666
+ console.error(`>>ARCALITY_STATUS>> \u{1F6D1} Error terminal del motor IA: ${errMsg}`);
4667
+ aiMarkedSuccess = false;
4668
+ hasCriticalError = true;
4669
+ failReason = "agent_runtime_error";
4670
+ failReasoning = errMsg;
4671
+ isFinished = true;
4672
+ history.push(`Turno ${stepCount}: \u{1F6D1} [AGENT RUNTIME ERROR] ${errMsg}`);
4673
+ saveMissionResults();
4674
+ break;
4079
4675
  }
4080
4676
  }
4081
4677
  if (response.usage) {
@@ -4149,7 +4745,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
4149
4745
  const lastSixHistory = history.slice(-6);
4150
4746
  const consecutiveWaits = lastSixHistory.filter((h) => h.includes('wait en "')).length;
4151
4747
  if (consecutiveWaits >= 3) {
4152
- const blankEvidencePath = path5.join(contextDir, `portal-blank-state-${Date.now()}.png`);
4748
+ const blankEvidencePath = path6.join(contextDir, `portal-blank-state-${Date.now()}.png`);
4153
4749
  await page.screenshot({ path: blankEvidencePath, fullPage: true }).catch(() => {
4154
4750
  });
4155
4751
  const blankUrl = page.url();
@@ -4196,7 +4792,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
4196
4792
  const isCriticalSuccess = textLower.includes("exitosamente") || textLower.includes("guardado") || textLower.includes("creado") || textLower.includes("success") || textLower.includes("correctamente") || textLower.includes("misi\xF3n cumplida");
4197
4793
  const isCriticalFeedback = isCriticalFailure || isCriticalSuccess;
4198
4794
  if (!isCriticalFeedback && step.type && ["span", "p", "label", "i", "svg", "img"].includes(step.type)) {
4199
- const elementText = await loc.innerText().catch(() => "");
4795
+ const elementText = await readLocatorText(loc);
4200
4796
  const elementTextLower = elementText.toLowerCase();
4201
4797
  if (failureKeywords.test(elementTextLower)) {
4202
4798
  console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Feedback de ERROR en elemento peque\xF1o: "${elementTextLower.substring(0, 50)}..."`);
@@ -4288,7 +4884,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
4288
4884
  let foundSuccessPost = false;
4289
4885
  for (const fb of postSubmitFeedback) {
4290
4886
  if (await fb.isVisible().catch(() => false)) {
4291
- const fbText = await fb.innerText().catch(() => "");
4887
+ const fbText = await readLocatorText(fb);
4292
4888
  const postSuccessKw = /exitosamente|creado correctamente|guardado correctamente|operación exitosa|registro guardado|saved successfully|created successfully/i;
4293
4889
  if (postSuccessKw.test(fbText)) {
4294
4890
  console.log(`>>ARCALITY_STATUS>> \u2728 \xC9xito visual detectado en toast: "${fbText.substring(0, 60)}"`);
@@ -4341,7 +4937,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
4341
4937
  let hasPostNavError = false;
4342
4938
  for (const alertEl of postNavAlerts) {
4343
4939
  if (await alertEl.isVisible().catch(() => false)) {
4344
- const alertTxt = await alertEl.innerText().catch(() => "");
4940
+ const alertTxt = await readLocatorText(alertEl);
4345
4941
  if (failureKeywords.test(alertTxt.toLowerCase())) {
4346
4942
  hasPostNavError = true;
4347
4943
  console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Error en alerta post-guardado: "${alertTxt.substring(0, 60)}"`);
@@ -4364,12 +4960,17 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
4364
4960
  } else {
4365
4961
  await page.waitForTimeout(500);
4366
4962
  }
4367
- } else if (step.action === "fill" && loc && step.value) {
4963
+ } else if (step.action === "fill" && loc) {
4368
4964
  const inputType = await loc.evaluate((el) => el.tagName === "INPUT" ? (el.type || "").toLowerCase() : null).catch(() => null);
4369
4965
  if (inputType === "file") {
4370
- console.log(` \u{1F4C2} Detectado input de archivo. Subiendo: "${step.value}"`);
4371
- const filePath = step.value.replace(/^"|"$/g, "").trim();
4372
- await loc.setInputFiles(filePath);
4966
+ const rawUploadValue = String(step.value || "placeholder:image/banner");
4967
+ console.log(` \u{1F4C2} Detectado input de archivo. Subiendo: "${rawUploadValue}"`);
4968
+ const fileVal = rawUploadValue.replace(/^"|"$/g, "").trim();
4969
+ const desc = step.description || "file upload";
4970
+ const resolvedAsset = resolveUploadAsset(fileVal, desc);
4971
+ await loc.setInputFiles(resolvedAsset);
4972
+ } else if (!step.value) {
4973
+ console.log(` \u26A0\uFE0F Acci\xF3n fill sin valor para un campo no-file. Se omite para evitar corrupci\xF3n de estado.`);
4373
4974
  } else if (inputType === "date" || inputType === "time" || inputType === "datetime-local") {
4374
4975
  console.log(` \u{1F39B}\uFE0F Detectado input nativo (${inputType}). Usando estrategia segura para: "${step.value}"`);
4375
4976
  try {
@@ -4413,7 +5014,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
4413
5014
  const panels = await page.locator("igc-dockmanager [slot]").all();
4414
5015
  let emptyPanelCount = 0;
4415
5016
  for (const panel of panels.slice(0, 6)) {
4416
- const txt = (await panel.innerText().catch(() => "")).trim();
5017
+ const txt = await readLocatorText(panel);
4417
5018
  const isVisible = await panel.isVisible().catch(() => false);
4418
5019
  if (isVisible && txt.length < 10)
4419
5020
  emptyPanelCount++;
@@ -4421,7 +5022,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
4421
5022
  const panelCount = Math.min(panels.length, 6);
4422
5023
  if (panelCount >= 2 && emptyPanelCount >= panelCount * 0.6) {
4423
5024
  const blankUrl = page.url();
4424
- const blankEvidencePath = path5.join(contextDir, `portal-blank-dashboard-${Date.now()}.png`);
5025
+ const blankEvidencePath = path6.join(contextDir, `portal-blank-dashboard-${Date.now()}.png`);
4425
5026
  await page.screenshot({ path: blankEvidencePath, fullPage: true }).catch(() => {
4426
5027
  });
4427
5028
  console.error(`
@@ -4534,7 +5135,8 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
4534
5135
  }
4535
5136
  if (response.finish) {
4536
5137
  if (!containsError) {
4537
- console.log(">>ARCALITY_STATUS>> \u2705 Arcality solicit\xF3 finalizar misi\xF3n.");
5138
+ if (process.env.DEBUG)
5139
+ console.log(">>ARCALITY_STATUS>> \u2705 Arcality solicit\xF3 finalizar misi\xF3n.");
4538
5140
  isFinished = true;
4539
5141
  if (!response.actions || response.actions.length === 0) {
4540
5142
  stepsData.push({
@@ -4554,7 +5156,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
4554
5156
  let hasVisibleFailure = false;
4555
5157
  for (const el of finishFeedbackEls) {
4556
5158
  if (await el.isVisible().catch(() => false)) {
4557
- const txt = await el.innerText().catch(() => "");
5159
+ const txt = await readLocatorText(el);
4558
5160
  if (failureKeywords.test(txt.toLowerCase())) {
4559
5161
  hasVisibleFailure = true;
4560
5162
  console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Error detectado en toast/alerta: "${txt.substring(0, 60)}"`);
@@ -4592,8 +5194,10 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
4592
5194
  if (stepCount > 2) {
4593
5195
  const allFeedback = await feedbackLocator.all();
4594
5196
  for (const fb of allFeedback) {
4595
- if (await fb.isVisible()) {
4596
- const rawTxt = await fb.innerText();
5197
+ if (await fb.isVisible().catch(() => false)) {
5198
+ const rawTxt = await readLocatorText(fb);
5199
+ if (!rawTxt)
5200
+ continue;
4597
5201
  const txt = rawTxt.toLowerCase();
4598
5202
  if (systemErrorKeywords.test(rawTxt)) {
4599
5203
  console.error(`
@@ -4702,8 +5306,8 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
4702
5306
  console.log("\u{1F916} [SMART-YAML] Generating mission card...");
4703
5307
  const smartYaml = await agent.summarizeMissionToYaml(prompt, history, target);
4704
5308
  if (smartYaml) {
4705
- const yamlPath = path5.join(contextDir, "last-mission-smart.yaml");
4706
- fs5.writeFileSync(yamlPath, smartYaml);
5309
+ const yamlPath = path6.join(contextDir, "last-mission-smart.yaml");
5310
+ fs6.writeFileSync(yamlPath, smartYaml);
4707
5311
  console.log(`>>ARCALITY_STATUS>> \u{1F4DD} Smart Mission Card generated.`);
4708
5312
  await testInfo.attach("smart_mission_card", {
4709
5313
  body: Buffer.from(smartYaml, "utf-8"),
@@ -4735,7 +5339,8 @@ Evidence: ${JSON.stringify(v.evidence).substring(0, 400)}`,
4735
5339
  });
4736
5340
  }
4737
5341
  } catch (e) {
4738
- console.warn(">>ARCALITY_STATUS>> \u26A0\uFE0F Fall\xF3 persistir hallazgos de seguridad en Memoria Colectiva.");
5342
+ if (process.env.DEBUG)
5343
+ console.warn(">>ARCALITY_STATUS>> \u26A0\uFE0F Fall\xF3 persistir hallazgos de seguridad en Memoria Colectiva.");
4739
5344
  }
4740
5345
  } else {
4741
5346
  console.log(`>>ARCALITY_STATUS>> \u{1F6E1}\uFE0F Escaneo de seguridad completado. No se encontraron problemas.`);
@@ -4754,13 +5359,41 @@ Evidence: ${JSON.stringify(v.evidence).substring(0, 400)}`,
4754
5359
  }
4755
5360
  saveMissionResults();
4756
5361
  if (!aiMarkedSuccess) {
4757
- 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}`);
5362
+ if (process.env.DEBUG)
5363
+ 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}`);
5364
+ bugClassification = await agent.classifyFailure(prompt, failReason || "unknown_failure", failReasoning, history);
5365
+ console.log(`>>ARCALITY_STATUS>> Bug classification: ${bugClassification}`);
5366
+ updateSavedMissionClassification();
5367
+ await testInfo.attach("bug_classification", {
5368
+ body: Buffer.from(bugClassification, "utf-8"),
5369
+ contentType: "text/plain"
5370
+ });
5371
+ try {
5372
+ const { createAdoTask: createAdoTask2 } = await Promise.resolve().then(() => (init_arcalityClient(), arcalityClient_exports));
5373
+ const shortPrompt = prompt.length > 90 ? prompt.substring(0, 87).trimEnd() + "..." : prompt;
5374
+ const adoTitle = `[Arcality QA] ${shortPrompt}`;
5375
+ const adoDescription = [
5376
+ `**Bug detectado automaticamente por Arcality QA**`,
5377
+ ``,
5378
+ `**Clasificacion:** ${bugClassification}`,
5379
+ `**Fail reason:** ${failReason || "unknown_failure"}`,
5380
+ failReasoning ? `**Analisis:** ${failReasoning}` : `**Analisis:** No disponible.`,
5381
+ `**Mision:** ${prompt}`,
5382
+ `**Historial reciente:**`,
5383
+ history.slice(-8).map((h) => `- ${h}`).join("\n"),
5384
+ `**Fecha:** ${(/* @__PURE__ */ new Date()).toISOString()}`
5385
+ ].join("\n");
5386
+ await createAdoTask2(adoTitle, adoDescription, { classification: bugClassification }).catch(() => null);
5387
+ } catch {
5388
+ }
4758
5389
  await persistCollectiveMemory(true).catch(() => {
4759
5390
  });
4760
5391
  let finalErrorMsg = `Misi\xF3n no completada o finalizada con errores.
4761
5392
 
4762
5393
  `;
4763
5394
  finalErrorMsg += `======================================================
5395
+ `;
5396
+ finalErrorMsg += `Bug classification: ${bugClassification}
4764
5397
  `;
4765
5398
  finalErrorMsg += `\u{1F3AF} TIPO DE FALLO: ${failReason}
4766
5399
  `;
@@ -4773,17 +5406,18 @@ ${failReasoning}
4773
5406
  `;
4774
5407
  throw new Error(finalErrorMsg);
4775
5408
  } else {
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}`);
5409
+ if (process.env.DEBUG)
5410
+ 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}`);
4777
5411
  }
4778
- if (fs5.existsSync(contextDir)) {
5412
+ if (fs6.existsSync(contextDir)) {
4779
5413
  try {
4780
- const files = fs5.readdirSync(contextDir);
5414
+ const files = fs6.readdirSync(contextDir);
4781
5415
  for (const file of files) {
4782
5416
  if (file.endsWith(".json") || file.endsWith(".png")) {
4783
- fs5.unlinkSync(path5.join(contextDir, file));
5417
+ fs6.unlinkSync(path6.join(contextDir, file));
4784
5418
  }
4785
5419
  }
4786
- fs5.rmSync(contextDir, { recursive: true, force: true });
5420
+ fs6.rmSync(contextDir, { recursive: true, force: true });
4787
5421
  console.log(`>>ARCALITY_STATUS>> \u{1F9F9} Carpeta context eliminada (Memoria ef\xEDmera completada).`);
4788
5422
  } catch (err) {
4789
5423
  console.warn(`No se pudo limpiar la carpeta context: ${err}`);