@arcadialdev/arcality 3.0.4 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -6
- package/bin/arcality.mjs +26 -17
- package/package.json +2 -1
- package/playwright.config.js +22 -21
- package/scripts/gen-and-run.mjs +2610 -2608
- package/scripts/generate.mjs +215 -0
- package/scripts/init.mjs +278 -253
- package/scripts/rebrand-report.mjs +19 -18
- package/src/configManager.mjs +57 -51
- package/src/services/codebaseAnalyzer.mjs +59 -0
- package/src/services/generatedMissionSchema.mjs +76 -0
- package/src/services/generatedMissionStore.mjs +117 -0
- package/src/services/generationContext.mjs +242 -0
- package/src/services/missionGenerator.mjs +328 -0
- package/src/services/routeDiscovery.mjs +747 -0
- package/src/testRunner.ts +2 -1
- package/tests/_helpers/agentic-runner.bundle.spec.js +401 -60
|
@@ -980,6 +980,34 @@ var sendKeyboardEventTool = {
|
|
|
980
980
|
required: ["key"]
|
|
981
981
|
}
|
|
982
982
|
};
|
|
983
|
+
var typeSequentiallyTool = {
|
|
984
|
+
name: "type_sequentially",
|
|
985
|
+
description: "QA Skill: Type a full text string character by character into a specific field. Use this for masked numeric inputs, OTP/PIN/CLABE/identification codes, virtual-keyboard flows, or whenever the mission explicitly says to type manually, digit by digit, or character by character. Prefer this over repeated clicks when the field is already visible but normal fill may not reflect the mask correctly.",
|
|
986
|
+
input_schema: {
|
|
987
|
+
type: "object",
|
|
988
|
+
properties: {
|
|
989
|
+
idx: {
|
|
990
|
+
type: "number",
|
|
991
|
+
description: "The IDX of the input-like element to focus before typing"
|
|
992
|
+
},
|
|
993
|
+
text: {
|
|
994
|
+
type: "string",
|
|
995
|
+
description: "The exact text to type sequentially"
|
|
996
|
+
},
|
|
997
|
+
delay_ms: {
|
|
998
|
+
type: "number",
|
|
999
|
+
description: "Delay between keystrokes in milliseconds (default: 80)",
|
|
1000
|
+
default: 80
|
|
1001
|
+
},
|
|
1002
|
+
clear_first: {
|
|
1003
|
+
type: "boolean",
|
|
1004
|
+
description: "Whether to clear any existing value before typing (default: true)",
|
|
1005
|
+
default: true
|
|
1006
|
+
}
|
|
1007
|
+
},
|
|
1008
|
+
required: ["idx", "text"]
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
983
1011
|
var interactNativeControlTool = {
|
|
984
1012
|
name: "interact_native_control",
|
|
985
1013
|
description: "QA Skill: Interact with native HTML controls that standard click/fill may not handle correctly. This includes: <input type='time'>, <input type='date'>, <input type='datetime-local'>, <input type='color'>, <input type='range'>, <select> with optgroup, and contenteditable elements. This tool uses Playwright's specialized methods (selectOption, fill with format coercion, press sequences) to reliably set values. ALWAYS prefer this tool over manual fill for time/date inputs.",
|
|
@@ -1035,6 +1063,7 @@ var qaAdvancedTools = [
|
|
|
1035
1063
|
restoreNavigationCheckpointTool,
|
|
1036
1064
|
createTestEvidenceTool,
|
|
1037
1065
|
sendKeyboardEventTool,
|
|
1066
|
+
typeSequentiallyTool,
|
|
1038
1067
|
interactNativeControlTool,
|
|
1039
1068
|
scrollPageTool
|
|
1040
1069
|
];
|
|
@@ -1679,6 +1708,112 @@ function isPlaceholderRule(rule) {
|
|
|
1679
1708
|
function isRecognizedSection(title) {
|
|
1680
1709
|
return RECOGNIZED_SECTION_PATTERNS.some((pattern) => pattern.test(title));
|
|
1681
1710
|
}
|
|
1711
|
+
function asTrimmedString(value) {
|
|
1712
|
+
if (typeof value !== "string")
|
|
1713
|
+
return null;
|
|
1714
|
+
const trimmed = value.trim();
|
|
1715
|
+
return trimmed ? trimmed : null;
|
|
1716
|
+
}
|
|
1717
|
+
function asStringList(value) {
|
|
1718
|
+
if (!Array.isArray(value))
|
|
1719
|
+
return [];
|
|
1720
|
+
return value.map((item) => asTrimmedString(item)).filter((item) => Boolean(item));
|
|
1721
|
+
}
|
|
1722
|
+
function loadQaContextGovernance(projectRoot, contextExists) {
|
|
1723
|
+
const metadataPath = path.join(projectRoot, ".arcality", "qa-context.meta.json");
|
|
1724
|
+
const warnings = [];
|
|
1725
|
+
const emptyResult = {
|
|
1726
|
+
path: metadataPath,
|
|
1727
|
+
exists: false,
|
|
1728
|
+
isValid: false,
|
|
1729
|
+
enforcementMode: getQaContextGovernanceMode(),
|
|
1730
|
+
version: null,
|
|
1731
|
+
updatedBy: null,
|
|
1732
|
+
approvedBy: null,
|
|
1733
|
+
ownerTeam: null,
|
|
1734
|
+
changeSummary: null,
|
|
1735
|
+
effectiveFrom: null,
|
|
1736
|
+
tags: [],
|
|
1737
|
+
warnings
|
|
1738
|
+
};
|
|
1739
|
+
if (!fs.existsSync(metadataPath)) {
|
|
1740
|
+
if (contextExists) {
|
|
1741
|
+
warnings.push("No se encontro qa-context.meta.json. Agrega metadata de gobernanza para versionar responsables y cambios.");
|
|
1742
|
+
}
|
|
1743
|
+
return emptyResult;
|
|
1744
|
+
}
|
|
1745
|
+
try {
|
|
1746
|
+
const raw = fs.readFileSync(metadataPath, "utf8");
|
|
1747
|
+
const parsed = JSON.parse(raw);
|
|
1748
|
+
const metadata = {
|
|
1749
|
+
path: metadataPath,
|
|
1750
|
+
exists: true,
|
|
1751
|
+
isValid: true,
|
|
1752
|
+
enforcementMode: getQaContextGovernanceMode(),
|
|
1753
|
+
version: asTrimmedString(parsed?.version),
|
|
1754
|
+
updatedBy: asTrimmedString(parsed?.updated_by ?? parsed?.updatedBy),
|
|
1755
|
+
approvedBy: asTrimmedString(parsed?.approved_by ?? parsed?.approvedBy),
|
|
1756
|
+
ownerTeam: asTrimmedString(parsed?.owner_team ?? parsed?.ownerTeam),
|
|
1757
|
+
changeSummary: asTrimmedString(parsed?.change_summary ?? parsed?.changeSummary),
|
|
1758
|
+
effectiveFrom: asTrimmedString(parsed?.effective_from ?? parsed?.effectiveFrom),
|
|
1759
|
+
tags: asStringList(parsed?.tags),
|
|
1760
|
+
warnings
|
|
1761
|
+
};
|
|
1762
|
+
if (!metadata.version)
|
|
1763
|
+
warnings.push('qa-context.meta.json no define "version".');
|
|
1764
|
+
if (!metadata.updatedBy)
|
|
1765
|
+
warnings.push('qa-context.meta.json no define "updated_by".');
|
|
1766
|
+
if (!metadata.ownerTeam)
|
|
1767
|
+
warnings.push('qa-context.meta.json no define "owner_team".');
|
|
1768
|
+
if (!metadata.changeSummary)
|
|
1769
|
+
warnings.push('qa-context.meta.json no define "change_summary".');
|
|
1770
|
+
metadata.isValid = warnings.length === 0;
|
|
1771
|
+
return metadata;
|
|
1772
|
+
} catch (error) {
|
|
1773
|
+
warnings.push("qa-context.meta.json no se pudo leer o parsear correctamente.");
|
|
1774
|
+
return {
|
|
1775
|
+
...emptyResult,
|
|
1776
|
+
exists: true,
|
|
1777
|
+
warnings
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
function getQaContextGovernanceMode() {
|
|
1782
|
+
const raw = String(process.env.ARCALITY_QA_CONTEXT_GOVERNANCE || "").trim().toLowerCase();
|
|
1783
|
+
if (raw === "off")
|
|
1784
|
+
return "off";
|
|
1785
|
+
if (raw === "strict" || raw === "enforce" || raw === "required")
|
|
1786
|
+
return "strict";
|
|
1787
|
+
if (raw === "warn")
|
|
1788
|
+
return "warn";
|
|
1789
|
+
if (process.env.CI === "true")
|
|
1790
|
+
return "strict";
|
|
1791
|
+
return "warn";
|
|
1792
|
+
}
|
|
1793
|
+
function getQaContextGovernanceIssues(analysis) {
|
|
1794
|
+
if (!analysis.exists)
|
|
1795
|
+
return [];
|
|
1796
|
+
const issues = [];
|
|
1797
|
+
if (!analysis.governance.exists) {
|
|
1798
|
+
issues.push("Falta el archivo .arcality/qa-context.meta.json para gobernanza del contexto.");
|
|
1799
|
+
return issues;
|
|
1800
|
+
}
|
|
1801
|
+
if (!analysis.governance.version)
|
|
1802
|
+
issues.push("La metadata no define version.");
|
|
1803
|
+
if (!analysis.governance.updatedBy)
|
|
1804
|
+
issues.push("La metadata no define updated_by.");
|
|
1805
|
+
if (!analysis.governance.ownerTeam)
|
|
1806
|
+
issues.push("La metadata no define owner_team.");
|
|
1807
|
+
if (!analysis.governance.changeSummary)
|
|
1808
|
+
issues.push("La metadata no define change_summary.");
|
|
1809
|
+
return issues;
|
|
1810
|
+
}
|
|
1811
|
+
function shouldBlockOnQaContextGovernance(analysis) {
|
|
1812
|
+
const mode = analysis.governance.enforcementMode;
|
|
1813
|
+
if (mode !== "strict")
|
|
1814
|
+
return false;
|
|
1815
|
+
return getQaContextGovernanceIssues(analysis).length > 0;
|
|
1816
|
+
}
|
|
1682
1817
|
function analyzeQaContext(projectRoot = process.cwd()) {
|
|
1683
1818
|
const filePath = path.join(projectRoot, ".arcality", "qa-context.md");
|
|
1684
1819
|
const emptyResult = {
|
|
@@ -1690,7 +1825,12 @@ function analyzeQaContext(projectRoot = process.cwd()) {
|
|
|
1690
1825
|
ruleCount: 0,
|
|
1691
1826
|
sectionCount: 0,
|
|
1692
1827
|
recognizedSectionCount: 0,
|
|
1828
|
+
unrecognizedSectionCount: 0,
|
|
1693
1829
|
ignoredExampleCount: 0,
|
|
1830
|
+
duplicateRuleCount: 0,
|
|
1831
|
+
lastModifiedAt: null,
|
|
1832
|
+
fingerprint: null,
|
|
1833
|
+
governance: loadQaContextGovernance(projectRoot, false),
|
|
1694
1834
|
warnings: [],
|
|
1695
1835
|
status: "missing",
|
|
1696
1836
|
isValid: false
|
|
@@ -1699,12 +1839,16 @@ function analyzeQaContext(projectRoot = process.cwd()) {
|
|
|
1699
1839
|
return emptyResult;
|
|
1700
1840
|
}
|
|
1701
1841
|
const rawContent = fs.readFileSync(filePath, "utf8");
|
|
1842
|
+
const stat = fs.statSync(filePath);
|
|
1702
1843
|
const cleanedContent = stripHtmlComments(rawContent);
|
|
1703
1844
|
const lines = cleanedContent.split(/\r?\n/);
|
|
1704
|
-
const
|
|
1845
|
+
const governance = loadQaContextGovernance(projectRoot, true);
|
|
1846
|
+
const warnings = [...governance.warnings];
|
|
1705
1847
|
const sections = [];
|
|
1706
1848
|
let currentSection = null;
|
|
1707
1849
|
let ignoredExampleCount = 0;
|
|
1850
|
+
let duplicateRuleCount = 0;
|
|
1851
|
+
const seenRules = /* @__PURE__ */ new Set();
|
|
1708
1852
|
const openSection = (title) => {
|
|
1709
1853
|
currentSection = {
|
|
1710
1854
|
title: title.trim(),
|
|
@@ -1731,6 +1875,12 @@ function analyzeQaContext(projectRoot = process.cwd()) {
|
|
|
1731
1875
|
ignoredExampleCount += 1;
|
|
1732
1876
|
continue;
|
|
1733
1877
|
}
|
|
1878
|
+
const dedupeKey = rule.toLowerCase();
|
|
1879
|
+
if (seenRules.has(dedupeKey)) {
|
|
1880
|
+
duplicateRuleCount += 1;
|
|
1881
|
+
continue;
|
|
1882
|
+
}
|
|
1883
|
+
seenRules.add(dedupeKey);
|
|
1734
1884
|
if (currentSection) {
|
|
1735
1885
|
currentSection.rules.push(rule);
|
|
1736
1886
|
}
|
|
@@ -1738,6 +1888,7 @@ function analyzeQaContext(projectRoot = process.cwd()) {
|
|
|
1738
1888
|
const nonEmptySections = sections.filter((section) => section.rules.length > 0);
|
|
1739
1889
|
const ruleCount = nonEmptySections.reduce((total, section) => total + section.rules.length, 0);
|
|
1740
1890
|
const recognizedSectionCount = nonEmptySections.filter((section) => section.recognized).length;
|
|
1891
|
+
const unrecognizedSectionCount = nonEmptySections.length - recognizedSectionCount;
|
|
1741
1892
|
if (!rawContent.trim()) {
|
|
1742
1893
|
warnings.push("El archivo existe pero esta vacio.");
|
|
1743
1894
|
}
|
|
@@ -1754,6 +1905,12 @@ function analyzeQaContext(projectRoot = process.cwd()) {
|
|
|
1754
1905
|
if (ruleCount > 0 && recognizedSectionCount === 0) {
|
|
1755
1906
|
warnings.push("Hay reglas, pero ninguna cae en las secciones sugeridas. Revisa la estructura del archivo.");
|
|
1756
1907
|
}
|
|
1908
|
+
if (duplicateRuleCount > 0) {
|
|
1909
|
+
warnings.push(`Se omitieron ${duplicateRuleCount} regla(s) duplicadas del QA Context.`);
|
|
1910
|
+
}
|
|
1911
|
+
if (unrecognizedSectionCount > 0) {
|
|
1912
|
+
warnings.push(`Hay ${unrecognizedSectionCount} seccion(es) con reglas que no coinciden con la taxonomia sugerida.`);
|
|
1913
|
+
}
|
|
1757
1914
|
const sanitizedContent = nonEmptySections.map((section) => {
|
|
1758
1915
|
const rules = section.rules.map((rule) => `- ${rule}`).join("\n");
|
|
1759
1916
|
return `## ${section.title}
|
|
@@ -1775,7 +1932,12 @@ ${rules}`;
|
|
|
1775
1932
|
ruleCount,
|
|
1776
1933
|
sectionCount: nonEmptySections.length,
|
|
1777
1934
|
recognizedSectionCount,
|
|
1935
|
+
unrecognizedSectionCount,
|
|
1778
1936
|
ignoredExampleCount,
|
|
1937
|
+
duplicateRuleCount,
|
|
1938
|
+
lastModifiedAt: stat.mtime.toISOString(),
|
|
1939
|
+
fingerprint: `${stat.size}:${stat.mtimeMs}`,
|
|
1940
|
+
governance,
|
|
1779
1941
|
warnings,
|
|
1780
1942
|
status,
|
|
1781
1943
|
isValid: ruleCount > 0
|
|
@@ -1793,10 +1955,28 @@ function formatQaContextSummary(analysis) {
|
|
|
1793
1955
|
lines.push("QA Context Summary");
|
|
1794
1956
|
lines.push(`Status: ${analysis.status}`);
|
|
1795
1957
|
lines.push(`Path: ${analysis.filePath}`);
|
|
1958
|
+
lines.push(`Priority: local qa-context > backend collective memory > base heuristics`);
|
|
1796
1959
|
lines.push(`Rules loaded: ${analysis.ruleCount}`);
|
|
1797
1960
|
lines.push(`Sections with rules: ${analysis.sectionCount}`);
|
|
1798
1961
|
lines.push(`Recognized sections: ${analysis.recognizedSectionCount}`);
|
|
1962
|
+
lines.push(`Unrecognized sections: ${analysis.unrecognizedSectionCount}`);
|
|
1799
1963
|
lines.push(`Ignored examples/placeholders: ${analysis.ignoredExampleCount}`);
|
|
1964
|
+
lines.push(`Duplicate rules removed: ${analysis.duplicateRuleCount}`);
|
|
1965
|
+
lines.push(`Last modified: ${analysis.lastModifiedAt || "N/A"}`);
|
|
1966
|
+
lines.push(`Fingerprint: ${analysis.fingerprint || "N/A"}`);
|
|
1967
|
+
lines.push(`Governance metadata: ${analysis.governance.exists ? analysis.governance.isValid ? "valid" : "warning" : "missing"}`);
|
|
1968
|
+
lines.push(`Governance enforcement: ${analysis.governance.enforcementMode}`);
|
|
1969
|
+
if (analysis.governance.exists) {
|
|
1970
|
+
lines.push("");
|
|
1971
|
+
lines.push("Governance metadata:");
|
|
1972
|
+
lines.push(`- Version: ${analysis.governance.version || "N/A"}`);
|
|
1973
|
+
lines.push(`- Updated by: ${analysis.governance.updatedBy || "N/A"}`);
|
|
1974
|
+
lines.push(`- Approved by: ${analysis.governance.approvedBy || "N/A"}`);
|
|
1975
|
+
lines.push(`- Owner team: ${analysis.governance.ownerTeam || "N/A"}`);
|
|
1976
|
+
lines.push(`- Effective from: ${analysis.governance.effectiveFrom || "N/A"}`);
|
|
1977
|
+
lines.push(`- Change summary: ${analysis.governance.changeSummary || "N/A"}`);
|
|
1978
|
+
lines.push(`- Tags: ${analysis.governance.tags.length > 0 ? analysis.governance.tags.join(", ") : "N/A"}`);
|
|
1979
|
+
}
|
|
1800
1980
|
if (analysis.sections.length > 0) {
|
|
1801
1981
|
lines.push("");
|
|
1802
1982
|
lines.push("Sections applied:");
|
|
@@ -1811,12 +1991,78 @@ function formatQaContextSummary(analysis) {
|
|
|
1811
1991
|
lines.push(`- ${warning}`);
|
|
1812
1992
|
}
|
|
1813
1993
|
}
|
|
1994
|
+
const governanceIssues = getQaContextGovernanceIssues(analysis);
|
|
1995
|
+
if (governanceIssues.length > 0) {
|
|
1996
|
+
lines.push("");
|
|
1997
|
+
lines.push("Governance issues:");
|
|
1998
|
+
for (const issue of governanceIssues) {
|
|
1999
|
+
lines.push(`- ${issue}`);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
1814
2002
|
if (!analysis.exists) {
|
|
1815
2003
|
lines.push("");
|
|
1816
2004
|
lines.push("No local QA Context file was found for this mission.");
|
|
1817
2005
|
}
|
|
1818
2006
|
return lines.join("\n");
|
|
1819
2007
|
}
|
|
2008
|
+
function buildEffectiveContextBlock(params) {
|
|
2009
|
+
const { localAnalysis, localPromptBlock, backendContext } = params;
|
|
2010
|
+
const backendRules = backendContext?.rules || [];
|
|
2011
|
+
const backendFields = backendContext?.fields || [];
|
|
2012
|
+
const blocks = [];
|
|
2013
|
+
if (localPromptBlock) {
|
|
2014
|
+
const governanceLines = [
|
|
2015
|
+
`Version: ${localAnalysis.governance.version || "N/A"}`,
|
|
2016
|
+
`Updated by: ${localAnalysis.governance.updatedBy || "N/A"}`,
|
|
2017
|
+
`Approved by: ${localAnalysis.governance.approvedBy || "N/A"}`,
|
|
2018
|
+
`Owner team: ${localAnalysis.governance.ownerTeam || "N/A"}`,
|
|
2019
|
+
`Change summary: ${localAnalysis.governance.changeSummary || "N/A"}`
|
|
2020
|
+
].join("\n");
|
|
2021
|
+
blocks.push(
|
|
2022
|
+
`<LOCAL_QA_CONTEXT>
|
|
2023
|
+
Priority: Highest.
|
|
2024
|
+
If any local rule conflicts with backend collective memory, follow the local rule.
|
|
2025
|
+
Governance metadata:
|
|
2026
|
+
${governanceLines}
|
|
2027
|
+
${localPromptBlock}
|
|
2028
|
+
</LOCAL_QA_CONTEXT>`
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
if (backendRules.length > 0 || backendFields.length > 0) {
|
|
2032
|
+
const backendLines = [];
|
|
2033
|
+
backendLines.push(`<BACKEND_COLLECTIVE_MEMORY>`);
|
|
2034
|
+
backendLines.push(`Priority: Secondary. Use this when local qa-context does not define the rule explicitly.`);
|
|
2035
|
+
if (backendRules.length > 0) {
|
|
2036
|
+
backendLines.push(`Rules:`);
|
|
2037
|
+
for (const rule of backendRules) {
|
|
2038
|
+
backendLines.push(`- [${rule.severity}] ${rule.title}: ${rule.description}`);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
if (backendFields.length > 0) {
|
|
2042
|
+
backendLines.push(`Known fields:`);
|
|
2043
|
+
for (const field of backendFields) {
|
|
2044
|
+
backendLines.push(`- ${field.field_identifier} (${field.field_type}) - Required: ${field.is_required}`);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
backendLines.push(`</BACKEND_COLLECTIVE_MEMORY>`);
|
|
2048
|
+
blocks.push(backendLines.join("\n"));
|
|
2049
|
+
}
|
|
2050
|
+
if (blocks.length === 0)
|
|
2051
|
+
return "";
|
|
2052
|
+
return `
|
|
2053
|
+
<CUSTOMER_CONTEXT_POLICY>
|
|
2054
|
+
Use customer context with this precedence order:
|
|
2055
|
+
1. Local qa-context file
|
|
2056
|
+
2. Backend collective memory
|
|
2057
|
+
3. Base QA heuristics
|
|
2058
|
+
Never ignore a valid local qa-context rule because of a conflicting backend memory entry.
|
|
2059
|
+
Local context status: ${localAnalysis.status}
|
|
2060
|
+
Local context fingerprint: ${localAnalysis.fingerprint || "N/A"}
|
|
2061
|
+
</CUSTOMER_CONTEXT_POLICY>
|
|
2062
|
+
|
|
2063
|
+
` + blocks.join("\n\n") + `
|
|
2064
|
+
`;
|
|
2065
|
+
}
|
|
1820
2066
|
|
|
1821
2067
|
// tests/_helpers/ai-agent-helper.ts
|
|
1822
2068
|
var qaSecurityTools = [
|
|
@@ -1873,7 +2119,7 @@ var AIAgentHelper = class {
|
|
|
1873
2119
|
qaContextAnalysisCache = null;
|
|
1874
2120
|
qaContextPromptCache = "";
|
|
1875
2121
|
qaContextStatusLogged = false;
|
|
1876
|
-
constructor(page, contextDir
|
|
2122
|
+
constructor(page, contextDir, testInfo, knowledgeService) {
|
|
1877
2123
|
this.page = page;
|
|
1878
2124
|
this.contextDir = contextDir;
|
|
1879
2125
|
this.testInfo = testInfo;
|
|
@@ -2707,7 +2953,8 @@ ${JSON.stringify(diagnosticPayload, null, 2)}`
|
|
|
2707
2953
|
let projectInfo = "";
|
|
2708
2954
|
try {
|
|
2709
2955
|
const { getProjectContext } = require("../src/projectInspector");
|
|
2710
|
-
const
|
|
2956
|
+
const runtimeProjectRoot = process.env.ARCALITY_PROJECT_ROOT || process.cwd();
|
|
2957
|
+
const ctx = getProjectContext(runtimeProjectRoot);
|
|
2711
2958
|
projectInfo = `
|
|
2712
2959
|
\u{1F3D7}\uFE0F PROJECT CONTEXT:
|
|
2713
2960
|
- Name: ${ctx.name}
|
|
@@ -2717,51 +2964,22 @@ ${JSON.stringify(diagnosticPayload, null, 2)}`
|
|
|
2717
2964
|
`;
|
|
2718
2965
|
} catch {
|
|
2719
2966
|
}
|
|
2720
|
-
let
|
|
2967
|
+
let backendContextData = null;
|
|
2721
2968
|
try {
|
|
2722
|
-
|
|
2723
|
-
if (
|
|
2724
|
-
memoryBackendContext = `
|
|
2725
|
-
\u{1F9E0} BACKEND COLLECTIVE MEMORY FOR THIS PAGE:
|
|
2726
|
-
`;
|
|
2727
|
-
if (contextData.rules?.length > 0) {
|
|
2728
|
-
memoryBackendContext += `BUSINESS RULES:
|
|
2729
|
-
`;
|
|
2730
|
-
contextData.rules.forEach((r) => memoryBackendContext += `- [${r.severity}] ${r.title}: ${r.description}
|
|
2731
|
-
`);
|
|
2732
|
-
}
|
|
2733
|
-
if (contextData.fields?.length > 0) {
|
|
2734
|
-
memoryBackendContext += `KNOWN FIELDS:
|
|
2735
|
-
`;
|
|
2736
|
-
contextData.fields.forEach((f) => memoryBackendContext += `- ${f.field_identifier} (${f.field_type}) - Required: ${f.is_required}
|
|
2737
|
-
`);
|
|
2738
|
-
}
|
|
2969
|
+
backendContextData = await this.knowledgeService.getContext(state.url);
|
|
2970
|
+
if (backendContextData && (backendContextData.rules?.length > 0 || backendContextData.fields?.length > 0)) {
|
|
2739
2971
|
if (process.env.DEBUG)
|
|
2740
|
-
console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Contexto de Memoria Colectiva inyectado (${
|
|
2972
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Contexto de Memoria Colectiva inyectado (${backendContextData.rules?.length || 0} reglas, ${backendContextData.fields?.length || 0} campos).`);
|
|
2741
2973
|
}
|
|
2742
2974
|
} catch (e) {
|
|
2743
2975
|
if (process.env.DEBUG)
|
|
2744
2976
|
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Error en hook de contexto: ${e.message}`);
|
|
2745
2977
|
}
|
|
2746
2978
|
let customUserContext = "";
|
|
2747
|
-
try {
|
|
2748
|
-
const fs7 = require("fs");
|
|
2749
|
-
const path7 = require("path");
|
|
2750
|
-
const customContextPath = path7.join(process.cwd(), ".arcality", "qa-context.md");
|
|
2751
|
-
if (false) {
|
|
2752
|
-
const content = fs7.readFileSync(customContextPath, "utf8");
|
|
2753
|
-
customUserContext = `
|
|
2754
|
-
<CUSTOMER_BUSINESS_RULES>
|
|
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:
|
|
2756
|
-
${content}
|
|
2757
|
-
</CUSTOMER_BUSINESS_RULES>
|
|
2758
|
-
`;
|
|
2759
|
-
}
|
|
2760
|
-
} catch (e) {
|
|
2761
|
-
}
|
|
2762
2979
|
try {
|
|
2763
2980
|
if (!this.qaContextAnalysisCache) {
|
|
2764
|
-
const
|
|
2981
|
+
const runtimeProjectRoot = process.env.ARCALITY_PROJECT_ROOT || process.cwd();
|
|
2982
|
+
const qaContextState = loadQaContextForPrompt(runtimeProjectRoot);
|
|
2765
2983
|
this.qaContextAnalysisCache = qaContextState.analysis;
|
|
2766
2984
|
this.qaContextPromptCache = qaContextState.promptBlock;
|
|
2767
2985
|
}
|
|
@@ -2777,18 +2995,16 @@ ${content}
|
|
|
2777
2995
|
console.log(`>>ARCALITY_STATUS>> QA Context warning: ${warning}`);
|
|
2778
2996
|
}
|
|
2779
2997
|
}
|
|
2998
|
+
if (qaContextAnalysis.exists || backendContextData?.rules?.length || backendContextData?.fields?.length) {
|
|
2999
|
+
console.log(`>>ARCALITY_STATUS>> Pol\xEDtica efectiva de contexto: local qa-context > memoria backend > heur\xEDsticas base.`);
|
|
3000
|
+
}
|
|
2780
3001
|
this.qaContextStatusLogged = true;
|
|
2781
3002
|
}
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
</CUSTOMER_BUSINESS_RULES>
|
|
2788
|
-
`;
|
|
2789
|
-
} else {
|
|
2790
|
-
customUserContext = "";
|
|
2791
|
-
}
|
|
3003
|
+
customUserContext = buildEffectiveContextBlock({
|
|
3004
|
+
localAnalysis: qaContextAnalysis,
|
|
3005
|
+
localPromptBlock: this.qaContextPromptCache,
|
|
3006
|
+
backendContext: backendContextData
|
|
3007
|
+
});
|
|
2792
3008
|
} catch (e) {
|
|
2793
3009
|
}
|
|
2794
3010
|
const systemPromptBlocks = [
|
|
@@ -2851,6 +3067,7 @@ These are NOT optional. You MUST use these tools in these situations:
|
|
|
2851
3067
|
| Bug found in application | \`create_test_evidence\` | To document with annotated screenshot |
|
|
2852
3068
|
| Mission complete | \`create_test_evidence\` | To document the final successful state |
|
|
2853
3069
|
| Modal/popup blocking page | \`send_keyboard_event\` (Escape) | To close the overlay |
|
|
3070
|
+
| Masked numeric/code input, OTP/PIN/CLABE/INE identifier, or prompt says "manualmente", "d\xEDgito por d\xEDgito", "car\xE1cter por car\xE1cter" | \`type_sequentially\` | After ONE focus attempt on the field; do not keep clicking |
|
|
2854
3071
|
| Time/Date/Color picker field | \`interact_native_control\` | INSTEAD of fill \u2014 native inputs need special handling |
|
|
2855
3072
|
| Element not found on screen | \`scroll_page\` | To reveal off-screen content |
|
|
2856
3073
|
| Need to close dropdown/menu | \`send_keyboard_event\` (Escape) | IMMEDIATELY after selecting any option \u2014 MANDATORY |
|
|
@@ -2895,7 +3112,13 @@ These are NOT optional. You MUST use these tools in these situations:
|
|
|
2895
3112
|
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.
|
|
2896
3113
|
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.
|
|
2897
3114
|
|
|
2898
|
-
15. **
|
|
3115
|
+
15. **MASKED / SEQUENTIAL INPUTS (MANDATORY PROTOCOL)**: If the field is a masked identifier/code input (examples: placeholders like \`000-000-000-0000\`, OTP/PIN boxes, CLABE/account identifiers, ID/INE codes) OR the mission explicitly says "manualmente", "n\xFAmero por n\xFAmero", "d\xEDgito por d\xEDgito", or "car\xE1cter por car\xE1cter":
|
|
3116
|
+
- You get EXACTLY ONE focus attempt: click the field once, or click a nearby keyboard button/icon ONCE if the UI shows one (e.g. "Teclado").
|
|
3117
|
+
- Then you MUST use \`type_sequentially\` with the full string. Do NOT simulate manual typing by repeatedly clicking the field.
|
|
3118
|
+
- If the field still does not accept input after that, use \`inspect_element_details\` on the field or the adjacent keyboard trigger. Never click the same input 3+ times in a row.
|
|
3119
|
+
- For numeric codes, preserve exact length. Count the digits from the mission and type all of them.
|
|
3120
|
+
|
|
3121
|
+
16. **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
3122
|
- 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
3123
|
- 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
3124
|
- **NEVER** report a bug or inability to proceed just because you see a file upload field. You ALWAYS have a fallback.
|
|
@@ -2941,7 +3164,9 @@ When a form is divided into numbered steps (e.g., Step 1 \u2192 Step 2 \u2192 St
|
|
|
2941
3164
|
|
|
2942
3165
|
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.
|
|
2943
3166
|
|
|
2944
|
-
12. **
|
|
3167
|
+
12. **SEQUENTIAL CODE INPUT FALLBACK (CRITICAL)**: If you are on a code/identifier step and the UI shows a field like "Identificador", an adjacent "Teclado" button, or a mask such as \`000-000-000-0000\`, NEVER spend multiple turns trying to focus the field. One click is enough. After that, use \`type_sequentially\` with the complete code string. If the keyboard button exists and the first direct focus did not work, click the keyboard button ONCE and then use \`type_sequentially\`.
|
|
3168
|
+
|
|
3169
|
+
13. **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
3170
|
cache_control: { type: "ephemeral" }
|
|
2946
3171
|
},
|
|
2947
3172
|
{
|
|
@@ -2955,7 +3180,6 @@ ${skillsContext}
|
|
|
2955
3180
|
${customUserContext}
|
|
2956
3181
|
${memoryContext}
|
|
2957
3182
|
${credentialsContext}
|
|
2958
|
-
${memoryBackendContext}
|
|
2959
3183
|
${assetContext}`
|
|
2960
3184
|
}
|
|
2961
3185
|
];
|
|
@@ -3534,6 +3758,45 @@ ${report}` });
|
|
|
3534
3758
|
} catch (e) {
|
|
3535
3759
|
toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Error sending key: ${e.message}` });
|
|
3536
3760
|
}
|
|
3761
|
+
} else if (toolName === "type_sequentially") {
|
|
3762
|
+
const { idx, text, delay_ms = 80, clear_first = true } = toolInput;
|
|
3763
|
+
console.log(`>>ARCALITY_STATUS>> \u2328\uFE0F\u270D\uFE0F Type sequentially IDX:${idx} \u2192 "${text}"`);
|
|
3764
|
+
const c = state.components.find((comp) => comp.idx === idx);
|
|
3765
|
+
if (!c) {
|
|
3766
|
+
toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: "Element not found." });
|
|
3767
|
+
} else {
|
|
3768
|
+
try {
|
|
3769
|
+
const frame = this.page.frames()[c.frameIdx];
|
|
3770
|
+
const loc = frame.locator(c.selector).first();
|
|
3771
|
+
await loc.scrollIntoViewIfNeeded({ timeout: 5e3 }).catch(() => {
|
|
3772
|
+
});
|
|
3773
|
+
await loc.focus({ timeout: 3e3 }).catch(async () => {
|
|
3774
|
+
await loc.click({ timeout: 5e3, force: true });
|
|
3775
|
+
});
|
|
3776
|
+
if (clear_first) {
|
|
3777
|
+
await this.page.keyboard.press("Control+a").catch(() => {
|
|
3778
|
+
});
|
|
3779
|
+
await this.page.keyboard.press("Delete").catch(() => {
|
|
3780
|
+
});
|
|
3781
|
+
await this.page.waitForTimeout(100);
|
|
3782
|
+
}
|
|
3783
|
+
await this.page.keyboard.type(String(text), { delay: Math.max(10, Math.min(Number(delay_ms) || 80, 400)) });
|
|
3784
|
+
const finalValue = await loc.evaluate((el) => {
|
|
3785
|
+
if (typeof el.value === "string")
|
|
3786
|
+
return el.value;
|
|
3787
|
+
if (el.isContentEditable)
|
|
3788
|
+
return el.innerText || "";
|
|
3789
|
+
return document.activeElement?.value || "";
|
|
3790
|
+
}).catch(() => "");
|
|
3791
|
+
toolResults.push({
|
|
3792
|
+
type: "tool_result",
|
|
3793
|
+
tool_use_id: toolUse.id,
|
|
3794
|
+
content: `\u2705 Sequential typing completed. Current value: "${finalValue}".`
|
|
3795
|
+
});
|
|
3796
|
+
} catch (e) {
|
|
3797
|
+
toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Error typing sequentially: ${e.message}` });
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3537
3800
|
} else if (toolName === "interact_native_control") {
|
|
3538
3801
|
const { idx, control_type, value } = toolInput;
|
|
3539
3802
|
const c = state.components.find((comp) => comp.idx === idx);
|
|
@@ -3944,6 +4207,33 @@ async function readLocatorText(locator) {
|
|
|
3944
4207
|
const innerText = await locator.innerText().catch(() => "");
|
|
3945
4208
|
return typeof innerText === "string" ? innerText.trim() : "";
|
|
3946
4209
|
}
|
|
4210
|
+
async function readHiddenPortalResult(page) {
|
|
4211
|
+
const selectors = [
|
|
4212
|
+
"input#result",
|
|
4213
|
+
'input[name="result"]',
|
|
4214
|
+
'input[readonly][id*="result" i]',
|
|
4215
|
+
'input[readonly][name*="result" i]'
|
|
4216
|
+
];
|
|
4217
|
+
for (const selector of selectors) {
|
|
4218
|
+
try {
|
|
4219
|
+
const locator = page.locator(selector).first();
|
|
4220
|
+
if (!await locator.count().catch(() => 0))
|
|
4221
|
+
continue;
|
|
4222
|
+
const rawValue = await locator.inputValue().catch(async () => await locator.getAttribute("value"));
|
|
4223
|
+
const raw = typeof rawValue === "string" ? rawValue.trim() : "";
|
|
4224
|
+
if (!raw)
|
|
4225
|
+
continue;
|
|
4226
|
+
const parsed = JSON.parse(raw);
|
|
4227
|
+
const status = String(parsed?.status || parsed?.Status || "").trim().toLowerCase();
|
|
4228
|
+
const message = String(parsed?.message || parsed?.Message || parsed?.detail || parsed?.error || "").trim();
|
|
4229
|
+
if (status) {
|
|
4230
|
+
return { status, message, raw };
|
|
4231
|
+
}
|
|
4232
|
+
} catch {
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
return null;
|
|
4236
|
+
}
|
|
3947
4237
|
function writeBatchMissionResult(payload) {
|
|
3948
4238
|
const runDomainDir = process.env.ARCALITY_RUN_DOMAIN_DIR;
|
|
3949
4239
|
if (!runDomainDir)
|
|
@@ -3961,7 +4251,10 @@ function writeBatchMissionResult(payload) {
|
|
|
3961
4251
|
}
|
|
3962
4252
|
(0, import_test.test)("Arcality AI Runner", async ({ page }, testInfo) => {
|
|
3963
4253
|
import_test.test.setTimeout(12e5);
|
|
3964
|
-
const contextDir = process.env.CONTEXT_DIR
|
|
4254
|
+
const contextDir = process.env.CONTEXT_DIR;
|
|
4255
|
+
if (!contextDir) {
|
|
4256
|
+
throw new Error("Missing CONTEXT_DIR. Arcality must run through the CLI wrapper so outputs stay inside the user project .arcality directory.");
|
|
4257
|
+
}
|
|
3965
4258
|
const activeConfig = process.env.ACTIVE_CONFIG || "Default";
|
|
3966
4259
|
if (!process.env.BASE_URL && process.env[`${activeConfig}_URL`])
|
|
3967
4260
|
process.env.BASE_URL = process.env[`${activeConfig}_URL`];
|
|
@@ -3988,7 +4281,8 @@ function writeBatchMissionResult(payload) {
|
|
|
3988
4281
|
}
|
|
3989
4282
|
const agent = new AIAgentHelper(page, contextDir, testInfo);
|
|
3990
4283
|
const prompt = process.env.SMART_PROMPT || "Navega al inicio y verifica que el sitio cargue";
|
|
3991
|
-
const
|
|
4284
|
+
const runtimeProjectRoot = process.env.ARCALITY_PROJECT_ROOT || process.cwd();
|
|
4285
|
+
const qaContextAnalysis = analyzeQaContext(runtimeProjectRoot);
|
|
3992
4286
|
if (qaContextAnalysis.exists && qaContextAnalysis.isValid) {
|
|
3993
4287
|
console.log(`>>ARCALITY_STATUS>> QA Context validado al arranque (${qaContextAnalysis.ruleCount} reglas, ${qaContextAnalysis.sectionCount} secciones).`);
|
|
3994
4288
|
} else if (qaContextAnalysis.exists) {
|
|
@@ -3999,10 +4293,24 @@ function writeBatchMissionResult(payload) {
|
|
|
3999
4293
|
} else {
|
|
4000
4294
|
console.log(`>>ARCALITY_STATUS>> QA Context local no configurado para esta mision.`);
|
|
4001
4295
|
}
|
|
4296
|
+
if (qaContextAnalysis.governance.exists) {
|
|
4297
|
+
console.log(`>>ARCALITY_STATUS>> QA Context metadata: version=${qaContextAnalysis.governance.version || "N/A"} | updated_by=${qaContextAnalysis.governance.updatedBy || "N/A"} | owner_team=${qaContextAnalysis.governance.ownerTeam || "N/A"}`);
|
|
4298
|
+
}
|
|
4002
4299
|
await testInfo.attach("qa_context_summary", {
|
|
4003
4300
|
body: Buffer.from(formatQaContextSummary(qaContextAnalysis), "utf-8"),
|
|
4004
4301
|
contentType: "text/plain"
|
|
4005
4302
|
});
|
|
4303
|
+
const governanceIssues = getQaContextGovernanceIssues(qaContextAnalysis);
|
|
4304
|
+
if (governanceIssues.length > 0) {
|
|
4305
|
+
for (const issue of governanceIssues) {
|
|
4306
|
+
console.log(`>>ARCALITY_STATUS>> QA Context governance issue: ${issue}`);
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
if (shouldBlockOnQaContextGovernance(qaContextAnalysis)) {
|
|
4310
|
+
throw new Error(
|
|
4311
|
+
`QA Context governance is required for this run (${qaContextAnalysis.governance.enforcementMode}) and is incomplete. Fix .arcality/qa-context.meta.json before continuing.`
|
|
4312
|
+
);
|
|
4313
|
+
}
|
|
4006
4314
|
const successKeywords = /exitosamente|creado correctamente|guardado correctamente|misión cumplida|registered successfully|created successfully|saved successfully|operación exitosa|registro guardado|successfully|correctly/i;
|
|
4007
4315
|
const failureKeywords = /\berror\b|falló|failed|no se puede|cannot|unable|ya existe|already exists|existente|exist|inválido|invalid|incorrecto|incorrect|ligado|linked|depende|depends|duplicado|duplicate|repetido|repeated|conflict|reintentar|retry|missing|required|obligatorio|bad request|400|500/i;
|
|
4008
4316
|
const systemErrorKeywords = /error interno|internal error|intentarlo más tarde|try again later|inténtelo más tarde|servicio no disponible|service unavailable|something went wrong|algo salió mal|ha ocurrido un error inesperado|unexpected error|server error|no se pudo procesar|could not be processed|comuníquese con soporte|contact support/i;
|
|
@@ -4150,7 +4458,10 @@ function writeBatchMissionResult(payload) {
|
|
|
4150
4458
|
const logPath = path6.join(contextDir, `agent-log-${timestampMs}.json`);
|
|
4151
4459
|
fs6.writeFileSync(logPath, JSON.stringify(summaryPayload, null, 2));
|
|
4152
4460
|
missionSummaryPaths.add(logPath);
|
|
4153
|
-
const runDomainDir = process.env.ARCALITY_RUN_DOMAIN_DIR
|
|
4461
|
+
const runDomainDir = process.env.ARCALITY_RUN_DOMAIN_DIR;
|
|
4462
|
+
if (!runDomainDir) {
|
|
4463
|
+
throw new Error("Missing ARCALITY_RUN_DOMAIN_DIR. Arcality must run through the CLI wrapper so durable logs stay inside the user project .arcality directory.");
|
|
4464
|
+
}
|
|
4154
4465
|
const durableLogsDir = path6.join(runDomainDir, "logs");
|
|
4155
4466
|
fs6.mkdirSync(durableLogsDir, { recursive: true });
|
|
4156
4467
|
const durableLogPath = path6.join(durableLogsDir, `mission-log-${timestampMs}.json`);
|
|
@@ -4187,9 +4498,11 @@ function writeBatchMissionResult(payload) {
|
|
|
4187
4498
|
const pid = process.env.ARCALITY_PROJECT_ID || "";
|
|
4188
4499
|
const apiUrl = process.env.ARCALITY_API_URL || "";
|
|
4189
4500
|
const apiKey = process.env.ARCALITY_API_KEY || "";
|
|
4190
|
-
|
|
4501
|
+
const orgId = process.env.ARCALITY_ORG_ID || "";
|
|
4502
|
+
if (!pid || !apiUrl || !apiKey || !orgId) {
|
|
4503
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Memoria Colectiva omitida por configuraci\xF3n incompleta.`);
|
|
4191
4504
|
if (process.env.DEBUG)
|
|
4192
|
-
console.warn(`[CollectiveMemory] \u26A0\uFE0F Vars faltantes \u2014 project_id='${pid}' api_url='${apiUrl}' api_key='${apiKey ? "OK" : "MISSING"}' \u2192 NO persiste.`);
|
|
4505
|
+
console.warn(`[CollectiveMemory] \u26A0\uFE0F Vars faltantes \u2014 project_id='${pid}' api_url='${apiUrl}' api_key='${apiKey ? "OK" : "MISSING"}' org_id='${orgId || "MISSING"}' \u2192 NO persiste.`);
|
|
4193
4506
|
return;
|
|
4194
4507
|
}
|
|
4195
4508
|
if (collectiveMemoryPersisted && !isFailed) {
|
|
@@ -4336,8 +4649,7 @@ function writeBatchMissionResult(payload) {
|
|
|
4336
4649
|
const matches = promptKeywords.filter((kw) => patternKeywords.some((pkw) => pkw.includes(kw) || kw.includes(pkw))).length;
|
|
4337
4650
|
const isVectorMatch = true;
|
|
4338
4651
|
if (isVectorMatch) {
|
|
4339
|
-
|
|
4340
|
-
console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Memoria Colectiva detectada (id: ${bestMatch.id}). Descargando a memoria local...`);
|
|
4652
|
+
console.log(`>>ARCALITY_STATUS>> \u{1F9E0} Memoria Colectiva detectada (id: ${bestMatch.id}). Descargando a memoria local...`);
|
|
4341
4653
|
reusedPatternId = bestMatch.id || "unknown";
|
|
4342
4654
|
let patternJsonObj = {};
|
|
4343
4655
|
try {
|
|
@@ -4574,6 +4886,35 @@ Detalles de la excepci\xF3n en consola: ${jsError}`;
|
|
|
4574
4886
|
}
|
|
4575
4887
|
if (isFinished)
|
|
4576
4888
|
break;
|
|
4889
|
+
try {
|
|
4890
|
+
const hiddenResult = await readHiddenPortalResult(page);
|
|
4891
|
+
if (hiddenResult) {
|
|
4892
|
+
const hiddenStatus = hiddenResult.status.toLowerCase();
|
|
4893
|
+
const hiddenMessage = hiddenResult.message || hiddenResult.raw;
|
|
4894
|
+
if (/^(failed|error|invalid|rejected)$/.test(hiddenStatus)) {
|
|
4895
|
+
console.log(`>>ARCALITY_STATUS>> \u26A0\uFE0F Resultado oculto detectado: "${hiddenStatus}" - ${hiddenMessage.substring(0, 120)}`);
|
|
4896
|
+
hasCriticalError = true;
|
|
4897
|
+
failReason = "portal_hidden_result_error";
|
|
4898
|
+
failReasoning = `El portal public\xF3 un resultado interno oculto indicando fallo: "${hiddenMessage}". Arcality detuvo la misi\xF3n para evitar loops sobre una operaci\xF3n ya rechazada.`;
|
|
4899
|
+
aiMarkedSuccess = false;
|
|
4900
|
+
isFinished = true;
|
|
4901
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} [HIDDEN_RESULT_ERROR] El portal devolvi\xF3 status="${hiddenStatus}" con mensaje "${hiddenMessage.substring(0, 200)}".`);
|
|
4902
|
+
saveMissionResults();
|
|
4903
|
+
break;
|
|
4904
|
+
}
|
|
4905
|
+
if (/^(success|succeeded|ok|completed)$/.test(hiddenStatus)) {
|
|
4906
|
+
console.log(`>>ARCALITY_STATUS>> \u2728 Resultado oculto de \xE9xito detectado: "${hiddenStatus}"`);
|
|
4907
|
+
aiMarkedSuccess = true;
|
|
4908
|
+
hasCriticalError = false;
|
|
4909
|
+
isFinished = true;
|
|
4910
|
+
history.push(`Turno ${stepCount}: \u2705 [HIDDEN_RESULT_SUCCESS] El portal devolvi\xF3 status="${hiddenStatus}".`);
|
|
4911
|
+
await persistCollectiveMemory();
|
|
4912
|
+
saveMissionResults();
|
|
4913
|
+
break;
|
|
4914
|
+
}
|
|
4915
|
+
}
|
|
4916
|
+
} catch {
|
|
4917
|
+
}
|
|
4577
4918
|
let hasVisibleError = false;
|
|
4578
4919
|
try {
|
|
4579
4920
|
const errorEls = await page.locator('.toast, .alert, [role="alert"], [role="status"], .snackbar, .notification, .error-message, .mat-snack-bar-container, [class*="alert"], [class*="toast"]').all();
|
|
@@ -4727,7 +5068,7 @@ ${patternContext}` : prompt;
|
|
|
4727
5068
|
const loopType = isClickLoop ? "click" : "fill";
|
|
4728
5069
|
const uniqueTargets = isClickLoop ? uniqueClicks : uniqueFills;
|
|
4729
5070
|
console.warn(`
|
|
4730
|
-
\u26A0
|
|
5071
|
+
\u26A0 Bucle repetitivo de ${loopType} detectado:`);
|
|
4731
5072
|
console.warn(` \xDAltimas acciones: ${lastActions.join(" \u2192 ")}`);
|
|
4732
5073
|
response.thought = `\u{1F6D1} ERROR: Bucle repetitivo en ${loopType} sobre: ${Array.from(uniqueTargets).join(", ")}.`;
|
|
4733
5074
|
response.finish = true;
|
|
@@ -4844,7 +5185,7 @@ Acci\xF3n bloqueada: Bucle de tipo ${loopType} sobre los elementos ${Array.from(
|
|
|
4844
5185
|
}
|
|
4845
5186
|
console.log(`
|
|
4846
5187
|
\u{1F6D1} [AUTO-STOP] Bloqueando click repetido en "${desc}" (${sameActionCount} veces). Forzando re-evaluaci\xF3n.`);
|
|
4847
|
-
history.push(`Turno ${stepCount}: \u{1F6D1} [AUTO-STOP] El click en "${desc}" fue bloqueado despu\xE9s de ${sameActionCount} repeticiones. Si es un dropdown/combobox que no abre sus opciones, usa send_keyboard_event con key "ArrowDown"
|
|
5188
|
+
history.push(`Turno ${stepCount}: \u{1F6D1} [AUTO-STOP] El click en "${desc}" fue bloqueado despu\xE9s de ${sameActionCount} repeticiones. Si es un dropdown/combobox que no abre sus opciones, usa send_keyboard_event con key "ArrowDown" y luego "Enter". Si es un input enmascarado o un c\xF3digo/identificador, usa type_sequentially con el valor completo despu\xE9s de UN solo enfoque. NO vuelvas a hacer click en el mismo elemento.`);
|
|
4848
5189
|
await page.waitForTimeout(800);
|
|
4849
5190
|
break;
|
|
4850
5191
|
}
|