@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.
- package/README.md +12 -6
- package/bin/arcality.mjs +9 -8
- package/package.json +1 -1
- package/playwright.config.js +18 -16
- package/scripts/arcality-logs.mjs +167 -93
- package/scripts/gen-and-run.mjs +1723 -1158
- package/scripts/init.mjs +55 -19
- package/scripts/setup.mjs +120 -166
- package/src/KnowledgeService.ts +20 -11
- package/src/arcalityClient.mjs +64 -81
- package/src/configLoader.mjs +9 -13
- package/src/configManager.mjs +48 -39
- package/src/playwrightAssets.mjs +27 -0
- package/src/services/collectiveMemoryService.ts +37 -26
- package/tests/_helpers/ArcalityReporter.js +19 -1
- package/tests/_helpers/agentic-runner.bundle.spec.js +856 -222
|
@@ -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
|
-
|
|
147
|
-
|
|
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: "
|
|
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: "
|
|
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(
|
|
535
|
+
console.warn(`Backend not available (${apiBase}): ${reason}.`);
|
|
536
|
+
return { valid: false, error: "backend_unavailable", mode: "live", reason };
|
|
187
537
|
}
|
|
188
538
|
}
|
|
189
|
-
|
|
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(
|
|
578
|
+
console.warn(`startMission failed (${apiBase}): ${reason}.`);
|
|
579
|
+
return { allowed: false, error: "backend_unavailable", reason };
|
|
234
580
|
}
|
|
235
581
|
}
|
|
236
|
-
|
|
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:
|
|
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
|
|
299
|
-
return { ok:
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
819
|
-
var
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
907
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ||
|
|
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
|
|
1432
|
+
if (!hasRealProjectId(pid))
|
|
1068
1433
|
missing.push("ARCALITY_PROJECT_ID");
|
|
1069
1434
|
if (!orgId)
|
|
1070
1435
|
missing.push("ARCALITY_ORG_ID");
|
|
1071
|
-
|
|
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
|
-
|
|
1192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1576
|
+
if (debugPatternSearch)
|
|
1577
|
+
console.log(`[PatternSearch] ${results.length} patrones encontrados.`);
|
|
1208
1578
|
return results;
|
|
1209
1579
|
} catch (err) {
|
|
1210
|
-
|
|
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: "
|
|
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
|
-
|
|
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: ["
|
|
1836
|
+
required: ["idx"]
|
|
1460
1837
|
}
|
|
1461
1838
|
},
|
|
1462
1839
|
{
|
|
1463
1840
|
name: "test_auth_bypass",
|
|
1464
|
-
description: "Attempts to directly navigate to a
|
|
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
|
-
|
|
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: ["
|
|
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 ||
|
|
1937
|
+
const toolsRoot = process.env.ARCALITY_ROOT || path5.join(__dirname, "..", "..");
|
|
1562
1938
|
const possibleDirs = [
|
|
1563
|
-
|
|
1564
|
-
|
|
1939
|
+
path5.join(toolsRoot, ".agent", "skills"),
|
|
1940
|
+
path5.join(toolsRoot, ".agents", "skills")
|
|
1565
1941
|
];
|
|
1566
1942
|
for (const rootDir of possibleDirs) {
|
|
1567
|
-
if (!
|
|
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 =
|
|
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 =
|
|
1578
|
-
if (
|
|
1579
|
-
content =
|
|
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 =
|
|
1583
|
-
content =
|
|
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 =
|
|
1607
|
-
if (
|
|
1608
|
-
const memories = JSON.parse(
|
|
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
|
|
1980
|
-
const
|
|
1981
|
-
const memoryFile =
|
|
1982
|
-
if (!
|
|
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(
|
|
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
|
-
|
|
2055
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2272
|
-
const
|
|
2273
|
-
const customContextPath =
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
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((
|
|
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
|
|
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
|
|
3432
|
-
var
|
|
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
|
-
|
|
3464
|
-
|
|
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
|
-
|
|
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 (!
|
|
3510
|
-
|
|
4044
|
+
if (!fs6.existsSync(contextDir))
|
|
4045
|
+
fs6.mkdirSync(contextDir, { recursive: true });
|
|
3511
4046
|
const finalSuccess = aiMarkedSuccess && !hasCriticalError;
|
|
3512
4047
|
try {
|
|
3513
|
-
const memoryFile2 =
|
|
4048
|
+
const memoryFile2 = path6.join(contextDir, "memoria-arcality.json");
|
|
3514
4049
|
let memories = [];
|
|
3515
|
-
if (
|
|
3516
|
-
const content =
|
|
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
|
-
|
|
3591
|
-
|
|
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
|
|
3597
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4213
|
+
if (process.env.DEBUG)
|
|
4214
|
+
console.log(`
|
|
3633
4215
|
======================================================`);
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
4372
|
+
const memoryFile2 = path6.join(contextDir, "memoria-agente.json");
|
|
3786
4373
|
let memories = [];
|
|
3787
|
-
if (
|
|
4374
|
+
if (fs6.existsSync(memoryFile2)) {
|
|
3788
4375
|
try {
|
|
3789
|
-
memories = JSON.parse(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
4706
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
5412
|
+
if (fs6.existsSync(contextDir)) {
|
|
4779
5413
|
try {
|
|
4780
|
-
const files =
|
|
5414
|
+
const files = fs6.readdirSync(contextDir);
|
|
4781
5415
|
for (const file of files) {
|
|
4782
5416
|
if (file.endsWith(".json") || file.endsWith(".png")) {
|
|
4783
|
-
|
|
5417
|
+
fs6.unlinkSync(path6.join(contextDir, file));
|
|
4784
5418
|
}
|
|
4785
5419
|
}
|
|
4786
|
-
|
|
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}`);
|