@arcadialdev/arcality 3.0.3 → 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/package.json +1 -1
- package/scripts/gen-and-run.mjs +24 -7
- package/src/KnowledgeService.ts +29 -29
- package/src/arcalityClient.mjs +31 -18
- package/src/configLoader.mjs +35 -35
- package/tests/_helpers/ArcalityReporter.js +15 -0
- package/tests/_helpers/agentic-runner.bundle.spec.js +660 -99
|
@@ -27,6 +27,375 @@ 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
|
+
});
|
|
30
399
|
|
|
31
400
|
// src/configManager.mjs
|
|
32
401
|
function isRealProjectId2(projectId) {
|
|
@@ -277,7 +646,12 @@ function simpleHash(str) {
|
|
|
277
646
|
}
|
|
278
647
|
return "ph_" + Math.abs(hash).toString(36);
|
|
279
648
|
}
|
|
280
|
-
|
|
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 = {}) {
|
|
281
655
|
const apiBase = getEffectiveApiBase();
|
|
282
656
|
if (!apiBase) {
|
|
283
657
|
if (process.env.DEBUG)
|
|
@@ -299,13 +673,18 @@ async function createAdoTask(title, description) {
|
|
|
299
673
|
try {
|
|
300
674
|
const controller = new AbortController();
|
|
301
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;
|
|
302
681
|
const res = await fetch(`${apiBase}/api/v1/integrations/azuredevops/task`, {
|
|
303
682
|
method: "POST",
|
|
304
683
|
headers: {
|
|
305
684
|
"Content-Type": "application/json",
|
|
306
685
|
"x-api-key": key
|
|
307
686
|
},
|
|
308
|
-
body: JSON.stringify({ project_id: projectId, title, description }),
|
|
687
|
+
body: JSON.stringify({ project_id: projectId, title: taskTitle, description: taskDescription }),
|
|
309
688
|
signal: controller.signal
|
|
310
689
|
});
|
|
311
690
|
clearTimeout(timeout);
|
|
@@ -772,8 +1151,8 @@ async function scan_sensitive_data_exposure(page) {
|
|
|
772
1151
|
}
|
|
773
1152
|
|
|
774
1153
|
// tests/_helpers/ai-agent-helper.ts
|
|
775
|
-
var
|
|
776
|
-
var
|
|
1154
|
+
var fs5 = __toESM(require("fs"));
|
|
1155
|
+
var path5 = __toESM(require("path"));
|
|
777
1156
|
|
|
778
1157
|
// src/KnowledgeService.ts
|
|
779
1158
|
var crypto = __toESM(require("crypto"));
|
|
@@ -1443,25 +1822,29 @@ function formatQaContextSummary(analysis) {
|
|
|
1443
1822
|
var qaSecurityTools = [
|
|
1444
1823
|
{
|
|
1445
1824
|
name: "test_xss_injection",
|
|
1446
|
-
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.",
|
|
1447
1826
|
parameters: {
|
|
1448
1827
|
type: "object",
|
|
1449
1828
|
properties: {
|
|
1450
|
-
|
|
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
|
+
}
|
|
1451
1835
|
},
|
|
1452
|
-
required: ["
|
|
1836
|
+
required: ["idx"]
|
|
1453
1837
|
}
|
|
1454
1838
|
},
|
|
1455
1839
|
{
|
|
1456
1840
|
name: "test_auth_bypass",
|
|
1457
|
-
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.",
|
|
1458
1842
|
parameters: {
|
|
1459
1843
|
type: "object",
|
|
1460
1844
|
properties: {
|
|
1461
|
-
|
|
1462
|
-
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." }
|
|
1463
1846
|
},
|
|
1464
|
-
required: ["
|
|
1847
|
+
required: ["target_url"]
|
|
1465
1848
|
}
|
|
1466
1849
|
},
|
|
1467
1850
|
{
|
|
@@ -1551,29 +1934,29 @@ var AIAgentHelper = class {
|
|
|
1551
1934
|
try {
|
|
1552
1935
|
let skillsContext = "";
|
|
1553
1936
|
let loadedCount = 0;
|
|
1554
|
-
const toolsRoot = process.env.ARCALITY_ROOT ||
|
|
1937
|
+
const toolsRoot = process.env.ARCALITY_ROOT || path5.join(__dirname, "..", "..");
|
|
1555
1938
|
const possibleDirs = [
|
|
1556
|
-
|
|
1557
|
-
|
|
1939
|
+
path5.join(toolsRoot, ".agent", "skills"),
|
|
1940
|
+
path5.join(toolsRoot, ".agents", "skills")
|
|
1558
1941
|
];
|
|
1559
1942
|
for (const rootDir of possibleDirs) {
|
|
1560
|
-
if (!
|
|
1943
|
+
if (!fs5.existsSync(rootDir))
|
|
1561
1944
|
continue;
|
|
1562
1945
|
if (loadedCount === 0) {
|
|
1563
1946
|
skillsContext += "\n\u{1F6E0}\uFE0F SKILLS INSTALADAS (Metodolog\xEDas activas):\n";
|
|
1564
1947
|
}
|
|
1565
|
-
const entries =
|
|
1948
|
+
const entries = fs5.readdirSync(rootDir, { withFileTypes: true });
|
|
1566
1949
|
for (const entry of entries) {
|
|
1567
1950
|
let content = "";
|
|
1568
1951
|
let skillName = entry.name;
|
|
1569
1952
|
if (entry.isDirectory()) {
|
|
1570
|
-
const skillPath =
|
|
1571
|
-
if (
|
|
1572
|
-
content =
|
|
1953
|
+
const skillPath = path5.join(rootDir, entry.name, "SKILL.md");
|
|
1954
|
+
if (fs5.existsSync(skillPath)) {
|
|
1955
|
+
content = fs5.readFileSync(skillPath, "utf8");
|
|
1573
1956
|
}
|
|
1574
1957
|
} else if (entry.name.endsWith(".md")) {
|
|
1575
|
-
const skillPath =
|
|
1576
|
-
content =
|
|
1958
|
+
const skillPath = path5.join(rootDir, entry.name);
|
|
1959
|
+
content = fs5.readFileSync(skillPath, "utf8");
|
|
1577
1960
|
}
|
|
1578
1961
|
if (content) {
|
|
1579
1962
|
skillsContext += `
|
|
@@ -1596,9 +1979,9 @@ ${content}
|
|
|
1596
1979
|
}
|
|
1597
1980
|
loadMemory(prompt) {
|
|
1598
1981
|
try {
|
|
1599
|
-
const memoryFile =
|
|
1600
|
-
if (
|
|
1601
|
-
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"));
|
|
1602
1985
|
const keywords = prompt.toLowerCase().split(" ").filter((w) => w.length > 4);
|
|
1603
1986
|
const relevantSuccess = memories.filter((m) => {
|
|
1604
1987
|
if (!m.success)
|
|
@@ -1963,19 +2346,94 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
1963
2346
|
components: sortedComponents
|
|
1964
2347
|
};
|
|
1965
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
|
+
}
|
|
1966
2424
|
/**
|
|
1967
2425
|
* Busca una acción sugerida en la memoria SIN usar tokens.
|
|
1968
2426
|
* Compara URL, componentes visuales Y palabras clave del prompt.
|
|
1969
2427
|
*/
|
|
1970
2428
|
async getActionFromGuia(prompt, historyLength) {
|
|
1971
2429
|
try {
|
|
1972
|
-
const
|
|
1973
|
-
const
|
|
1974
|
-
const memoryFile =
|
|
1975
|
-
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)) {
|
|
1976
2434
|
return null;
|
|
1977
2435
|
}
|
|
1978
|
-
const memories = JSON.parse(
|
|
2436
|
+
const memories = JSON.parse(fs7.readFileSync(memoryFile, "utf8"));
|
|
1979
2437
|
const state = await this.getPageState();
|
|
1980
2438
|
const currentUrl = this.page.url();
|
|
1981
2439
|
const extractCriticalKeywords = (text) => {
|
|
@@ -2044,8 +2502,14 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
2044
2502
|
let bestMatch = null;
|
|
2045
2503
|
let bestMatchScore = -1;
|
|
2046
2504
|
for (const c of state.components) {
|
|
2047
|
-
|
|
2048
|
-
|
|
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
|
+
}
|
|
2049
2513
|
let score = -1;
|
|
2050
2514
|
if (c.selector === memStep.action_data?.selector) {
|
|
2051
2515
|
score = 100;
|
|
@@ -2215,6 +2679,18 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
2215
2679
|
parameters: t.input_schema || t.parameters
|
|
2216
2680
|
}))
|
|
2217
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
|
+
}
|
|
2218
2694
|
const credentialsContext = process.env.LOGIN_USER && process.env.LOGIN_PASSWORD ? `
|
|
2219
2695
|
\u{1F511} QA CREDENTIALS (Use if login is needed):
|
|
2220
2696
|
- User: ${process.env.LOGIN_USER}
|
|
@@ -2222,6 +2698,12 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
2222
2698
|
` : "";
|
|
2223
2699
|
const memoryContext = this.loadMemory(prompt);
|
|
2224
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
|
+
}
|
|
2225
2707
|
let projectInfo = "";
|
|
2226
2708
|
try {
|
|
2227
2709
|
const { getProjectContext } = require("../src/projectInspector");
|
|
@@ -2263,11 +2745,11 @@ ${relevantFailures.map((m) => `- ${m.prompt}`).join("\n")}
|
|
|
2263
2745
|
}
|
|
2264
2746
|
let customUserContext = "";
|
|
2265
2747
|
try {
|
|
2266
|
-
const
|
|
2267
|
-
const
|
|
2268
|
-
const customContextPath =
|
|
2748
|
+
const fs7 = require("fs");
|
|
2749
|
+
const path7 = require("path");
|
|
2750
|
+
const customContextPath = path7.join(process.cwd(), ".arcality", "qa-context.md");
|
|
2269
2751
|
if (false) {
|
|
2270
|
-
const content =
|
|
2752
|
+
const content = fs7.readFileSync(customContextPath, "utf8");
|
|
2271
2753
|
customUserContext = `
|
|
2272
2754
|
<CUSTOMER_BUSINESS_RULES>
|
|
2273
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:
|
|
@@ -2315,15 +2797,7 @@ ${this.qaContextPromptCache}
|
|
|
2315
2797
|
text: `# IDENTITY
|
|
2316
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.
|
|
2317
2799
|
|
|
2318
|
-
|
|
2319
|
-
${prompt}
|
|
2320
|
-
|
|
2321
|
-
${projectInfo}
|
|
2322
|
-
${skillsContext}
|
|
2323
|
-
${customUserContext}
|
|
2324
|
-
${memoryContext}
|
|
2325
|
-
${credentialsContext}
|
|
2326
|
-
${memoryBackendContext}`
|
|
2800
|
+
`
|
|
2327
2801
|
},
|
|
2328
2802
|
{
|
|
2329
2803
|
type: "text",
|
|
@@ -2421,6 +2895,13 @@ These are NOT optional. You MUST use these tools in these situations:
|
|
|
2421
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.
|
|
2422
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.
|
|
2423
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
|
+
|
|
2424
2905
|
# WIZARD & MULTI-STEP FORMS (CRITICAL)
|
|
2425
2906
|
When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 Step 3):
|
|
2426
2907
|
|
|
@@ -2460,7 +2941,22 @@ When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 St
|
|
|
2460
2941
|
|
|
2461
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.
|
|
2462
2943
|
|
|
2463
|
-
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}`
|
|
2464
2960
|
}
|
|
2465
2961
|
];
|
|
2466
2962
|
if (this.testInfo) {
|
|
@@ -2544,11 +3040,7 @@ ${history.slice(-25).join("\n") || "None"}`
|
|
|
2544
3040
|
model: process.env.CLAUDE_MODEL || "claude-4-5-sonnet-latest",
|
|
2545
3041
|
max_tokens: 4096,
|
|
2546
3042
|
system: systemPromptBlocks,
|
|
2547
|
-
tools:
|
|
2548
|
-
name: t.name,
|
|
2549
|
-
description: t.description,
|
|
2550
|
-
input_schema: t.parameters || t.input_schema
|
|
2551
|
-
})),
|
|
3043
|
+
tools: anthropicTools,
|
|
2552
3044
|
messages: anthropicMessages,
|
|
2553
3045
|
temperature: 0.2
|
|
2554
3046
|
})
|
|
@@ -2571,7 +3063,7 @@ ${history.slice(-25).join("\n") || "None"}`
|
|
|
2571
3063
|
const delay = Math.pow(2, retryCount) * 1e3;
|
|
2572
3064
|
console.log(`
|
|
2573
3065
|
>>ARCALITY_STATUS>> \u26A0\uFE0F Arcality est\xE1 saturado (${response.status}). Reintentando...`);
|
|
2574
|
-
await new Promise((
|
|
3066
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
2575
3067
|
continue;
|
|
2576
3068
|
}
|
|
2577
3069
|
throw new Error(`Arcality Brain Error ${response.status}: ${errorText}`);
|
|
@@ -2886,7 +3378,7 @@ ${history.slice(-25).join("\n") || "None"}`
|
|
|
2886
3378
|
}
|
|
2887
3379
|
}
|
|
2888
3380
|
} else if (toolName === "test_xss_injection") {
|
|
2889
|
-
const { idx, payload_type } = toolInput;
|
|
3381
|
+
const { idx, payload_type = "script" } = toolInput;
|
|
2890
3382
|
const el = state.components.find((c) => c.idx === idx);
|
|
2891
3383
|
if (!el) {
|
|
2892
3384
|
toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Element idx ${idx} not found.` });
|
|
@@ -2898,7 +3390,8 @@ ${history.slice(-25).join("\n") || "None"}`
|
|
|
2898
3390
|
if (payload_type === "svg")
|
|
2899
3391
|
payload = "<svg onload=alert(1)>";
|
|
2900
3392
|
try {
|
|
2901
|
-
const
|
|
3393
|
+
const frame = this.page.frames()[el.frameIdx] || this.page.mainFrame();
|
|
3394
|
+
const locator = frame.locator(el.selector).first();
|
|
2902
3395
|
await locator.fill(payload);
|
|
2903
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.` });
|
|
2904
3397
|
if (this.testInfo)
|
|
@@ -3423,9 +3916,10 @@ var SecurityScanner = class {
|
|
|
3423
3916
|
|
|
3424
3917
|
// tests/_helpers/agentic-runner.spec.ts
|
|
3425
3918
|
var import_config = require("dotenv/config");
|
|
3426
|
-
var
|
|
3427
|
-
var
|
|
3919
|
+
var fs6 = __toESM(require("fs"));
|
|
3920
|
+
var path6 = __toESM(require("path"));
|
|
3428
3921
|
var crypto2 = __toESM(require("crypto"));
|
|
3922
|
+
init_asset_resolver();
|
|
3429
3923
|
var _sessionRuleCache = /* @__PURE__ */ new Set();
|
|
3430
3924
|
function captureValidationRule(errorMessage, context) {
|
|
3431
3925
|
const key = errorMessage.substring(0, 80).toLowerCase().trim();
|
|
@@ -3455,11 +3949,11 @@ function writeBatchMissionResult(payload) {
|
|
|
3455
3949
|
if (!runDomainDir)
|
|
3456
3950
|
return;
|
|
3457
3951
|
try {
|
|
3458
|
-
if (!
|
|
3459
|
-
|
|
3952
|
+
if (!fs6.existsSync(runDomainDir)) {
|
|
3953
|
+
fs6.mkdirSync(runDomainDir, { recursive: true });
|
|
3460
3954
|
}
|
|
3461
|
-
|
|
3462
|
-
|
|
3955
|
+
fs6.writeFileSync(
|
|
3956
|
+
path6.join(runDomainDir, "mission-result.json"),
|
|
3463
3957
|
JSON.stringify(payload, null, 2)
|
|
3464
3958
|
);
|
|
3465
3959
|
} catch {
|
|
@@ -3480,10 +3974,15 @@ function writeBatchMissionResult(payload) {
|
|
|
3480
3974
|
const history = [];
|
|
3481
3975
|
const urlHistory = [];
|
|
3482
3976
|
const stepsData = [];
|
|
3483
|
-
|
|
3484
|
-
|
|
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)) {
|
|
3485
3984
|
try {
|
|
3486
|
-
|
|
3985
|
+
fs6.unlinkSync(memoryFile);
|
|
3487
3986
|
} catch {
|
|
3488
3987
|
}
|
|
3489
3988
|
}
|
|
@@ -3522,18 +4021,34 @@ function writeBatchMissionResult(payload) {
|
|
|
3522
4021
|
let lastSeenErrorToast = "";
|
|
3523
4022
|
let accumulatedCost = 0;
|
|
3524
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
|
+
};
|
|
3525
4040
|
const saveMissionResults = () => {
|
|
3526
4041
|
if (resultsSaved)
|
|
3527
4042
|
return;
|
|
3528
4043
|
resultsSaved = true;
|
|
3529
|
-
if (!
|
|
3530
|
-
|
|
4044
|
+
if (!fs6.existsSync(contextDir))
|
|
4045
|
+
fs6.mkdirSync(contextDir, { recursive: true });
|
|
3531
4046
|
const finalSuccess = aiMarkedSuccess && !hasCriticalError;
|
|
3532
4047
|
try {
|
|
3533
|
-
const memoryFile2 =
|
|
4048
|
+
const memoryFile2 = path6.join(contextDir, "memoria-arcality.json");
|
|
3534
4049
|
let memories = [];
|
|
3535
|
-
if (
|
|
3536
|
-
const content =
|
|
4050
|
+
if (fs6.existsSync(memoryFile2)) {
|
|
4051
|
+
const content = fs6.readFileSync(memoryFile2, "utf8").trim();
|
|
3537
4052
|
if (content) {
|
|
3538
4053
|
try {
|
|
3539
4054
|
memories = JSON.parse(content);
|
|
@@ -3604,10 +4119,11 @@ function writeBatchMissionResult(payload) {
|
|
|
3604
4119
|
error: hasCriticalError,
|
|
3605
4120
|
fail_reason: failReason,
|
|
3606
4121
|
fail_reasoning: failReasoning,
|
|
4122
|
+
bug_classification: bugClassification || null,
|
|
3607
4123
|
timestamp: Date.now()
|
|
3608
4124
|
};
|
|
3609
4125
|
memories.push(missionData);
|
|
3610
|
-
|
|
4126
|
+
fs6.writeFileSync(memoryFile2, JSON.stringify(memories, null, 2));
|
|
3611
4127
|
if (process.env.DEBUG)
|
|
3612
4128
|
console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Memoria guardada: ${cleanSteps.length} pasos limpios (${rawSteps.length - cleanSteps.length} corruptos filtrados)`);
|
|
3613
4129
|
} catch (err) {
|
|
@@ -3624,22 +4140,26 @@ function writeBatchMissionResult(payload) {
|
|
|
3624
4140
|
error: hasCriticalError,
|
|
3625
4141
|
fail_reason: failReason,
|
|
3626
4142
|
fail_reasoning: failReasoning,
|
|
4143
|
+
bug_classification: bugClassification || null,
|
|
3627
4144
|
usage: totalMissionUsage,
|
|
3628
4145
|
target: process.env.BASE_URL || "",
|
|
3629
4146
|
target_path: process.env.TARGET_PATH || "",
|
|
3630
4147
|
report_dir: process.env.REPORTS_DIR || "",
|
|
3631
4148
|
timestamp: timestampIso
|
|
3632
4149
|
};
|
|
3633
|
-
const logPath =
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
const
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
);
|
|
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);
|
|
3642
4159
|
writeBatchMissionResult(summaryPayload);
|
|
4160
|
+
if (runDomainDir) {
|
|
4161
|
+
missionSummaryPaths.add(path6.join(runDomainDir, "mission-result.json"));
|
|
4162
|
+
}
|
|
3643
4163
|
try {
|
|
3644
4164
|
const historyLine = JSON.stringify({
|
|
3645
4165
|
ts: timestampIso,
|
|
@@ -3649,12 +4169,12 @@ function writeBatchMissionResult(payload) {
|
|
|
3649
4169
|
fail_reason: failReason,
|
|
3650
4170
|
report_dir: process.env.REPORTS_DIR || ""
|
|
3651
4171
|
}) + "\n";
|
|
3652
|
-
|
|
3653
|
-
|
|
4172
|
+
fs6.appendFileSync(
|
|
4173
|
+
path6.join(contextDir, "arcality-history.log"),
|
|
3654
4174
|
historyLine
|
|
3655
4175
|
);
|
|
3656
|
-
|
|
3657
|
-
|
|
4176
|
+
fs6.appendFileSync(
|
|
4177
|
+
path6.join(durableLogsDir, "arcality-history.log"),
|
|
3658
4178
|
historyLine
|
|
3659
4179
|
);
|
|
3660
4180
|
} catch (e) {
|
|
@@ -3849,11 +4369,11 @@ function writeBatchMissionResult(payload) {
|
|
|
3849
4369
|
timestamp: Date.now()
|
|
3850
4370
|
};
|
|
3851
4371
|
try {
|
|
3852
|
-
const memoryFile2 =
|
|
4372
|
+
const memoryFile2 = path6.join(contextDir, "memoria-agente.json");
|
|
3853
4373
|
let memories = [];
|
|
3854
|
-
if (
|
|
4374
|
+
if (fs6.existsSync(memoryFile2)) {
|
|
3855
4375
|
try {
|
|
3856
|
-
memories = JSON.parse(
|
|
4376
|
+
memories = JSON.parse(fs6.readFileSync(memoryFile2, "utf8"));
|
|
3857
4377
|
} catch {
|
|
3858
4378
|
memories = [];
|
|
3859
4379
|
}
|
|
@@ -3865,14 +4385,14 @@ function writeBatchMissionResult(payload) {
|
|
|
3865
4385
|
if (existingIdx < 0) {
|
|
3866
4386
|
if (resolvedStepsData.length > 0) {
|
|
3867
4387
|
memories.push(localMemoryData);
|
|
3868
|
-
|
|
4388
|
+
fs6.writeFileSync(memoryFile2, JSON.stringify(memories, null, 2));
|
|
3869
4389
|
console.log(`>>ARCALITY_STATUS>> \u2728 Memoria sincronizada en memoria-agente.json (${resolvedStepsData.length} pasos). Arcality entrar\xE1 en MODO GU\xCDA.`);
|
|
3870
4390
|
} else {
|
|
3871
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).`);
|
|
3872
4392
|
}
|
|
3873
4393
|
} else if (!existingHasSteps && resolvedStepsData.length > 0) {
|
|
3874
4394
|
memories[existingIdx] = localMemoryData;
|
|
3875
|
-
|
|
4395
|
+
fs6.writeFileSync(memoryFile2, JSON.stringify(memories, null, 2));
|
|
3876
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.`);
|
|
3877
4397
|
} else {
|
|
3878
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.`);
|
|
@@ -4142,7 +4662,16 @@ ${patternContext}` : prompt;
|
|
|
4142
4662
|
saveMissionResults();
|
|
4143
4663
|
break;
|
|
4144
4664
|
} else {
|
|
4145
|
-
|
|
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;
|
|
4146
4675
|
}
|
|
4147
4676
|
}
|
|
4148
4677
|
if (response.usage) {
|
|
@@ -4216,7 +4745,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
4216
4745
|
const lastSixHistory = history.slice(-6);
|
|
4217
4746
|
const consecutiveWaits = lastSixHistory.filter((h) => h.includes('wait en "')).length;
|
|
4218
4747
|
if (consecutiveWaits >= 3) {
|
|
4219
|
-
const blankEvidencePath =
|
|
4748
|
+
const blankEvidencePath = path6.join(contextDir, `portal-blank-state-${Date.now()}.png`);
|
|
4220
4749
|
await page.screenshot({ path: blankEvidencePath, fullPage: true }).catch(() => {
|
|
4221
4750
|
});
|
|
4222
4751
|
const blankUrl = page.url();
|
|
@@ -4431,12 +4960,17 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
4431
4960
|
} else {
|
|
4432
4961
|
await page.waitForTimeout(500);
|
|
4433
4962
|
}
|
|
4434
|
-
} else if (step.action === "fill" && loc
|
|
4963
|
+
} else if (step.action === "fill" && loc) {
|
|
4435
4964
|
const inputType = await loc.evaluate((el) => el.tagName === "INPUT" ? (el.type || "").toLowerCase() : null).catch(() => null);
|
|
4436
4965
|
if (inputType === "file") {
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
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.`);
|
|
4440
4974
|
} else if (inputType === "date" || inputType === "time" || inputType === "datetime-local") {
|
|
4441
4975
|
console.log(` \u{1F39B}\uFE0F Detectado input nativo (${inputType}). Usando estrategia segura para: "${step.value}"`);
|
|
4442
4976
|
try {
|
|
@@ -4488,7 +5022,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
4488
5022
|
const panelCount = Math.min(panels.length, 6);
|
|
4489
5023
|
if (panelCount >= 2 && emptyPanelCount >= panelCount * 0.6) {
|
|
4490
5024
|
const blankUrl = page.url();
|
|
4491
|
-
const blankEvidencePath =
|
|
5025
|
+
const blankEvidencePath = path6.join(contextDir, `portal-blank-dashboard-${Date.now()}.png`);
|
|
4492
5026
|
await page.screenshot({ path: blankEvidencePath, fullPage: true }).catch(() => {
|
|
4493
5027
|
});
|
|
4494
5028
|
console.error(`
|
|
@@ -4772,8 +5306,8 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
4772
5306
|
console.log("\u{1F916} [SMART-YAML] Generating mission card...");
|
|
4773
5307
|
const smartYaml = await agent.summarizeMissionToYaml(prompt, history, target);
|
|
4774
5308
|
if (smartYaml) {
|
|
4775
|
-
const yamlPath =
|
|
4776
|
-
|
|
5309
|
+
const yamlPath = path6.join(contextDir, "last-mission-smart.yaml");
|
|
5310
|
+
fs6.writeFileSync(yamlPath, smartYaml);
|
|
4777
5311
|
console.log(`>>ARCALITY_STATUS>> \u{1F4DD} Smart Mission Card generated.`);
|
|
4778
5312
|
await testInfo.attach("smart_mission_card", {
|
|
4779
5313
|
body: Buffer.from(smartYaml, "utf-8"),
|
|
@@ -4827,12 +5361,39 @@ Evidence: ${JSON.stringify(v.evidence).substring(0, 400)}`,
|
|
|
4827
5361
|
if (!aiMarkedSuccess) {
|
|
4828
5362
|
if (process.env.DEBUG)
|
|
4829
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
|
+
}
|
|
4830
5389
|
await persistCollectiveMemory(true).catch(() => {
|
|
4831
5390
|
});
|
|
4832
5391
|
let finalErrorMsg = `Misi\xF3n no completada o finalizada con errores.
|
|
4833
5392
|
|
|
4834
5393
|
`;
|
|
4835
5394
|
finalErrorMsg += `======================================================
|
|
5395
|
+
`;
|
|
5396
|
+
finalErrorMsg += `Bug classification: ${bugClassification}
|
|
4836
5397
|
`;
|
|
4837
5398
|
finalErrorMsg += `\u{1F3AF} TIPO DE FALLO: ${failReason}
|
|
4838
5399
|
`;
|
|
@@ -4848,15 +5409,15 @@ ${failReasoning}
|
|
|
4848
5409
|
if (process.env.DEBUG)
|
|
4849
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}`);
|
|
4850
5411
|
}
|
|
4851
|
-
if (
|
|
5412
|
+
if (fs6.existsSync(contextDir)) {
|
|
4852
5413
|
try {
|
|
4853
|
-
const files =
|
|
5414
|
+
const files = fs6.readdirSync(contextDir);
|
|
4854
5415
|
for (const file of files) {
|
|
4855
5416
|
if (file.endsWith(".json") || file.endsWith(".png")) {
|
|
4856
|
-
|
|
5417
|
+
fs6.unlinkSync(path6.join(contextDir, file));
|
|
4857
5418
|
}
|
|
4858
5419
|
}
|
|
4859
|
-
|
|
5420
|
+
fs6.rmSync(contextDir, { recursive: true, force: true });
|
|
4860
5421
|
console.log(`>>ARCALITY_STATUS>> \u{1F9F9} Carpeta context eliminada (Memoria ef\xEDmera completada).`);
|
|
4861
5422
|
} catch (err) {
|
|
4862
5423
|
console.warn(`No se pudo limpiar la carpeta context: ${err}`);
|