@arcadialdev/arcality 2.4.36 → 2.4.48
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 +5 -3
- package/scripts/gen-and-run.mjs +60 -9
- package/src/EvidenceUploader.mjs +45 -0
- package/src/KnowledgeService.ts +13 -5
- package/src/arcalityClient.mjs +53 -9
- package/src/configLoader.mjs +2 -1
- package/src/index.ts +19 -1
- package/src/services/collectiveMemoryService.ts +98 -7
- package/tests/_helpers/agentic-runner.bundle.spec.js +847 -92
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcadialdev/arcality",
|
|
3
|
-
"version": "2.4.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.4.48",
|
|
4
|
+
"description": "El primer ingeniero QA Autónomo integrado al CI/CD. Creado por Arcadial.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "node scripts/gen-and-run.mjs",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"esbuild": "^0.20.2"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
+
"@azure/storage-blob": "^12.31.0",
|
|
57
58
|
"@clack/prompts": "^1.0.1",
|
|
58
59
|
"@inquirer/prompts": "^8.2.0",
|
|
59
60
|
"@playwright/test": "^1.57.0",
|
|
@@ -62,9 +63,10 @@
|
|
|
62
63
|
"dotenv": "^17.2.3",
|
|
63
64
|
"figlet": "^1.9.4",
|
|
64
65
|
"js-yaml": "^4.1.1",
|
|
66
|
+
"mime-types": "^3.0.2",
|
|
65
67
|
"node-fetch": "^3.3.2",
|
|
66
68
|
"prompts": "^2.4.2",
|
|
67
69
|
"terminal-image": "^1.1.0",
|
|
68
70
|
"tsx": "^4.21.0"
|
|
69
71
|
}
|
|
70
|
-
}
|
|
72
|
+
}
|
package/scripts/gen-and-run.mjs
CHANGED
|
@@ -620,6 +620,14 @@ async function main() {
|
|
|
620
620
|
const { validateApiKey, startMission: requestMission } = await import('../src/arcalityClient.mjs');
|
|
621
621
|
|
|
622
622
|
const validation = await validateApiKey();
|
|
623
|
+
if (process.env.DEBUG || process.argv.includes('--debug')) {
|
|
624
|
+
console.log(chalk.gray(`[DEBUG] Validation result: ${JSON.stringify(validation, null, 2)}`));
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const detectedOrgId = validation.organization_id || validation.organizationId || validation.OrganizationId;
|
|
628
|
+
if (detectedOrgId) {
|
|
629
|
+
process.env.ARCALITY_ORG_ID = detectedOrgId;
|
|
630
|
+
}
|
|
623
631
|
if (!validation.valid) {
|
|
624
632
|
const errorMessages = {
|
|
625
633
|
'no_api_key': '🔑 API Key not found.\n Configure it via `arcality init` or in .env (ARCALITY_API_KEY)',
|
|
@@ -636,7 +644,7 @@ async function main() {
|
|
|
636
644
|
|
|
637
645
|
// Show plan info
|
|
638
646
|
const modeLabel = validation.mode === 'mock' ? chalk.gray('(local)') : chalk.green('(live)');
|
|
639
|
-
const dailyLimitDisplay = validation.daily_limit === -1 ? '∞' : (validation.daily_limit ||
|
|
647
|
+
const dailyLimitDisplay = validation.daily_limit === -1 ? '∞' : (validation.daily_limit || 35);
|
|
640
648
|
const remainingDisplay = validation.remaining >= 999999 ? '∞' : validation.remaining;
|
|
641
649
|
note(
|
|
642
650
|
chalk.bold.white('✅ Valid API Key ') + modeLabel + '\n' +
|
|
@@ -653,9 +661,13 @@ async function main() {
|
|
|
653
661
|
if (!selectedProjectId) {
|
|
654
662
|
// If no project ID from config, try to create/select from backend
|
|
655
663
|
try {
|
|
664
|
+
const projController = new AbortController();
|
|
665
|
+
const projTimeout = setTimeout(() => projController.abort(), 5000);
|
|
656
666
|
const projRes = await fetch(`${apiBase}/api/v1/projects`, {
|
|
657
|
-
headers: { 'x-api-key': process.env.ARCALITY_API_KEY || '' }
|
|
667
|
+
headers: { 'x-api-key': process.env.ARCALITY_API_KEY || '' },
|
|
668
|
+
signal: projController.signal
|
|
658
669
|
});
|
|
670
|
+
clearTimeout(projTimeout);
|
|
659
671
|
|
|
660
672
|
if (projRes.ok) {
|
|
661
673
|
const data = await projRes.json();
|
|
@@ -745,7 +757,8 @@ async function main() {
|
|
|
745
757
|
...process.env,
|
|
746
758
|
ARCALITY_API_URL: getApiUrl(), // Internal URL — always injected explicitly
|
|
747
759
|
ARCALITY_PROJECT_ID: finalProjectId,
|
|
748
|
-
ARCALITY_MISSION_ID: mission.mission_id || '', // ← Propagate mission_id to every proxy call
|
|
760
|
+
ARCALITY_MISSION_ID: mission.mission_id || mission.missionId || mission.MissionId || '', // ← Propagate mission_id to every proxy call
|
|
761
|
+
ARCALITY_ORG_ID: validation.organization_id || validation.organizationId || validation.OrganizationId || process.env.ARCALITY_ORG_ID || '', // ← Propagate org_id for pattern persistence
|
|
749
762
|
BASE_URL: projectConfig?.project?.baseUrl || dotEnv.BASE_URL || process.env.BASE_URL || fallbackBaseUrl || 'http://localhost:3000',
|
|
750
763
|
LOGIN_USER: projectConfig?.auth?.username || dotEnv.LOGIN_USER || process.env.LOGIN_USER,
|
|
751
764
|
LOGIN_PASSWORD: projectConfig?.auth?.password || dotEnv.LOGIN_PASSWORD || process.env.LOGIN_PASSWORD,
|
|
@@ -758,7 +771,8 @@ async function main() {
|
|
|
758
771
|
};
|
|
759
772
|
|
|
760
773
|
if (finalProjectId) {
|
|
761
|
-
console.log(chalk.gray(
|
|
774
|
+
console.log(chalk.gray(`>> ARCALITY_PROJECT_ID: ${finalProjectId}`));
|
|
775
|
+
console.log(chalk.gray(`>> ARCALITY_ORG_ID: ${mergedEnv.ARCALITY_ORG_ID || 'NONE'}`));
|
|
762
776
|
}
|
|
763
777
|
|
|
764
778
|
// ── Resolve execution paths ──
|
|
@@ -805,9 +819,6 @@ async function main() {
|
|
|
805
819
|
if (!process.argv.includes('--debug')) s.stop(chalk.green('✅ Mission completed successfully.'));
|
|
806
820
|
else console.log(chalk.green('✅ Mission completed successfully.'));
|
|
807
821
|
|
|
808
|
-
// ── End Mission ──
|
|
809
|
-
const { endMission } = await import('../src/arcalityClient.mjs');
|
|
810
|
-
|
|
811
822
|
let finalUsage = undefined;
|
|
812
823
|
try {
|
|
813
824
|
const ctxDir = process.env.CONTEXT_DIR;
|
|
@@ -822,6 +833,27 @@ async function main() {
|
|
|
822
833
|
}
|
|
823
834
|
} catch(e) {}
|
|
824
835
|
|
|
836
|
+
// ── Upload Evidence ──
|
|
837
|
+
const { endMission, getEvidenceSasToken } = await import('../src/arcalityClient.mjs');
|
|
838
|
+
const { uploadEvidence } = await import('../src/EvidenceUploader.mjs');
|
|
839
|
+
|
|
840
|
+
try {
|
|
841
|
+
const sasData = await getEvidenceSasToken(mission.mission_id, finalProjectId);
|
|
842
|
+
if (sasData && sasData.sasUrl) {
|
|
843
|
+
if (!process.argv.includes('--debug')) s.start('☁️ Uploading evidence to Azure...');
|
|
844
|
+
const reportsDir = process.env.REPORTS_DIR || path.join(ORIGINAL_CWD, 'tests-report');
|
|
845
|
+
|
|
846
|
+
await run("node", [path.join(PROJECT_ROOT, "scripts/rebrand-report.mjs")]).catch(() => { });
|
|
847
|
+
|
|
848
|
+
await uploadEvidence(reportsDir, sasData.sasUrl, sasData.basePath);
|
|
849
|
+
if (!process.argv.includes('--debug')) s.stop(chalk.green('✅ Evidence uploaded successfully.'));
|
|
850
|
+
}
|
|
851
|
+
} catch (e) {
|
|
852
|
+
if (process.argv.includes('--debug')) console.log(chalk.yellow(`⚠️ Could not upload evidence: ${e.message}`));
|
|
853
|
+
if (!process.argv.includes('--debug')) s.stop(chalk.yellow('⚠️ Evidence upload failed.'));
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// ── End Mission ──
|
|
825
857
|
await endMission(mission.mission_id, 'success', finalUsage);
|
|
826
858
|
|
|
827
859
|
// ── Ping Project ──
|
|
@@ -881,8 +913,8 @@ async function main() {
|
|
|
881
913
|
|
|
882
914
|
// End mission with failure
|
|
883
915
|
try {
|
|
884
|
-
const { endMission } = await import('../src/arcalityClient.mjs');
|
|
885
916
|
let finalUsage = undefined;
|
|
917
|
+
let failReason = "timeout_or_crash";
|
|
886
918
|
try {
|
|
887
919
|
const ctxDir = process.env.CONTEXT_DIR;
|
|
888
920
|
if (fs.existsSync(ctxDir)) {
|
|
@@ -892,10 +924,29 @@ async function main() {
|
|
|
892
924
|
const lastLog = path.join(ctxDir, logFiles[logFiles.length - 1]);
|
|
893
925
|
const logData = JSON.parse(fs.readFileSync(lastLog, 'utf8'));
|
|
894
926
|
finalUsage = logData.usage;
|
|
927
|
+
failReason = logData.fail_reason || failReason;
|
|
895
928
|
}
|
|
896
929
|
}
|
|
897
930
|
} catch(e) {}
|
|
898
|
-
|
|
931
|
+
|
|
932
|
+
// ── Upload Evidence ──
|
|
933
|
+
const { endMission, getEvidenceSasToken } = await import('../src/arcalityClient.mjs');
|
|
934
|
+
const { uploadEvidence } = await import('../src/EvidenceUploader.mjs');
|
|
935
|
+
|
|
936
|
+
try {
|
|
937
|
+
const sasData = await getEvidenceSasToken(mission.mission_id, finalProjectId);
|
|
938
|
+
if (sasData && sasData.sasUrl) {
|
|
939
|
+
if (!process.argv.includes('--debug')) s.start('☁️ Uploading evidence to Azure...');
|
|
940
|
+
const reportsDir = process.env.REPORTS_DIR || path.join(ORIGINAL_CWD, 'tests-report');
|
|
941
|
+
|
|
942
|
+
await run("node", [path.join(PROJECT_ROOT, "scripts/rebrand-report.mjs")]).catch(() => { });
|
|
943
|
+
|
|
944
|
+
await uploadEvidence(reportsDir, sasData.sasUrl, sasData.basePath);
|
|
945
|
+
if (!process.argv.includes('--debug')) s.stop(chalk.green('✅ Evidence uploaded successfully.'));
|
|
946
|
+
}
|
|
947
|
+
} catch (e) {}
|
|
948
|
+
|
|
949
|
+
await endMission(mission.mission_id, 'failed', finalUsage, failReason);
|
|
899
950
|
} catch { }
|
|
900
951
|
} finally {
|
|
901
952
|
const sRep = spinner();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ContainerClient } from '@azure/storage-blob';
|
|
4
|
+
import mime from 'mime-types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Subes la evidencia de prueba iterativamente a Azure Blob Storage usando una URL de SAS.
|
|
8
|
+
* @param {string} reportsDir Directorio local de reportes.
|
|
9
|
+
* @param {string} sasUrl URL de SAS para el ContainerClient.
|
|
10
|
+
* @param {string} basePath Path base dentro del contenedor de Azure.
|
|
11
|
+
*/
|
|
12
|
+
export async function uploadEvidence(reportsDir, sasUrl, basePath) {
|
|
13
|
+
if (!fs.existsSync(reportsDir)) return;
|
|
14
|
+
|
|
15
|
+
const containerClient = new ContainerClient(sasUrl);
|
|
16
|
+
|
|
17
|
+
async function walkAndUpload(currentDir, relativePath = '') {
|
|
18
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
19
|
+
const uploadPromises = [];
|
|
20
|
+
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
23
|
+
const entryRelativePath = path.posix.join(relativePath, entry.name);
|
|
24
|
+
|
|
25
|
+
if (entry.isDirectory()) {
|
|
26
|
+
uploadPromises.push(walkAndUpload(fullPath, entryRelativePath));
|
|
27
|
+
} else {
|
|
28
|
+
// Blob path en Azure: basePath/ruta_relativa_del_archivo
|
|
29
|
+
const blobPath = `${basePath}/${entryRelativePath}`;
|
|
30
|
+
const blockBlobClient = containerClient.getBlockBlobClient(blobPath);
|
|
31
|
+
|
|
32
|
+
const contentType = mime.lookup(entry.name) || 'application/octet-stream';
|
|
33
|
+
|
|
34
|
+
uploadPromises.push(
|
|
35
|
+
blockBlobClient.uploadFile(fullPath, {
|
|
36
|
+
blobHTTPHeaders: { blobContentType: contentType }
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
await Promise.all(uploadPromises);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await walkAndUpload(reportsDir);
|
|
45
|
+
}
|
package/src/KnowledgeService.ts
CHANGED
|
@@ -41,12 +41,14 @@ export class KnowledgeService {
|
|
|
41
41
|
private apiBase: string;
|
|
42
42
|
private apiKey: string;
|
|
43
43
|
private projectId: string | null = null;
|
|
44
|
+
private orgId: string | null = null;
|
|
44
45
|
|
|
45
46
|
private constructor() {
|
|
46
47
|
// API URL is internal-only, loaded from the tool's .env via dotenv
|
|
47
48
|
this.apiBase = process.env.ARCALITY_API_URL || 'https://arcalityqadev.arcadial.lat';
|
|
48
49
|
this.apiKey = process.env.ARCALITY_API_KEY || '';
|
|
49
50
|
this.projectId = process.env.ARCALITY_PROJECT_ID || null;
|
|
51
|
+
this.orgId = process.env.ARCALITY_ORG_ID || null;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
/**
|
|
@@ -140,7 +142,7 @@ export class KnowledgeService {
|
|
|
140
142
|
if (res.status === 200) {
|
|
141
143
|
const data = await res.json();
|
|
142
144
|
if (data.status === 'skipped_duplicate_dom') {
|
|
143
|
-
|
|
145
|
+
// console.log(chalk.gray(`[Knowledge] Ingesta omitida (Duplicado).`));
|
|
144
146
|
} else {
|
|
145
147
|
console.log(chalk.cyan(`[Knowledge] Ingesta exitosa (Project: ${payload.project_id})`));
|
|
146
148
|
}
|
|
@@ -158,10 +160,13 @@ export class KnowledgeService {
|
|
|
158
160
|
if (!pid || pid === 'undefined') return null;
|
|
159
161
|
try {
|
|
160
162
|
const pathUrl = new URL(url).pathname;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
let contextUrl = `${this.apiBase}/api/v1/portal/context?project_id=${pid}&path=${encodeURIComponent(pathUrl)}`;
|
|
164
|
+
if (this.orgId) {
|
|
165
|
+
contextUrl += `&organization_id=${this.orgId}`;
|
|
166
|
+
}
|
|
167
|
+
const res = await this.fetchWithTimeout(contextUrl, {
|
|
168
|
+
headers: { 'x-api-key': this.apiKey }
|
|
169
|
+
});
|
|
165
170
|
if (!res.ok) return null;
|
|
166
171
|
return await res.json();
|
|
167
172
|
} catch (e: any) {
|
|
@@ -185,6 +190,7 @@ export class KnowledgeService {
|
|
|
185
190
|
'x-api-key': this.apiKey
|
|
186
191
|
},
|
|
187
192
|
body: JSON.stringify({
|
|
193
|
+
organization_id: this.orgId,
|
|
188
194
|
project_id: pid,
|
|
189
195
|
rule_type: "UI_UX", // Default según especificación
|
|
190
196
|
title,
|
|
@@ -210,6 +216,7 @@ export class KnowledgeService {
|
|
|
210
216
|
'x-api-key': this.apiKey
|
|
211
217
|
},
|
|
212
218
|
body: JSON.stringify({
|
|
219
|
+
organization_id: this.orgId,
|
|
213
220
|
project_id: pid,
|
|
214
221
|
field_identifier: identifier,
|
|
215
222
|
field_type: type,
|
|
@@ -235,6 +242,7 @@ export class KnowledgeService {
|
|
|
235
242
|
'x-api-key': this.apiKey
|
|
236
243
|
},
|
|
237
244
|
body: JSON.stringify({
|
|
245
|
+
organization_id: this.orgId,
|
|
238
246
|
project_id: pid,
|
|
239
247
|
doc_type: docType,
|
|
240
248
|
title,
|
package/src/arcalityClient.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import path from 'node:path';
|
|
|
7
7
|
import { getApiKey, getApiUrl, CONFIG_DIR } from './configLoader.mjs';
|
|
8
8
|
|
|
9
9
|
// ── Backend config (internal, not user-configurable) ──
|
|
10
|
-
const DAILY_MISSION_LIMIT =
|
|
10
|
+
const DAILY_MISSION_LIMIT = 35;
|
|
11
11
|
const USAGE_FILE = path.join(CONFIG_DIR, 'usage.json');
|
|
12
12
|
|
|
13
13
|
// ═══════════════════════════════════════════════════════
|
|
@@ -128,22 +128,31 @@ export async function validateApiKey() {
|
|
|
128
128
|
// ── LIVE mode: Backend available ──
|
|
129
129
|
if (apiBase) {
|
|
130
130
|
try {
|
|
131
|
+
const controller = new AbortController();
|
|
132
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
131
133
|
const res = await fetch(`${apiBase}/api/v1/auth/validate`, {
|
|
132
134
|
method: 'POST',
|
|
133
135
|
headers: {
|
|
134
136
|
'Content-Type': 'application/json',
|
|
135
137
|
'x-api-key': key
|
|
136
|
-
}
|
|
138
|
+
},
|
|
139
|
+
signal: controller.signal
|
|
137
140
|
});
|
|
141
|
+
clearTimeout(timeout);
|
|
138
142
|
|
|
139
143
|
if (res.status === 401) return { valid: false, error: 'invalid_api_key', mode: 'live' };
|
|
140
144
|
if (res.status === 403) return { valid: false, error: 'plan_expired', mode: 'live' };
|
|
141
145
|
if (!res.ok) return { valid: false, error: 'server_error', mode: 'live' };
|
|
142
146
|
|
|
143
147
|
const data = await res.json();
|
|
148
|
+
if (process.env.DEBUG || process.argv.includes('--debug')) {
|
|
149
|
+
console.log(`[DEBUG] Auth validation response keys:`, Object.keys(data));
|
|
150
|
+
console.log(`[DEBUG] Auth validation response:`, JSON.stringify(data, null, 2));
|
|
151
|
+
}
|
|
144
152
|
return { ...data, valid: true, mode: 'live' };
|
|
145
|
-
} catch {
|
|
146
|
-
|
|
153
|
+
} catch (e) {
|
|
154
|
+
const reason = e?.name === 'AbortError' ? 'timeout (5s)' : (e?.message || 'unknown');
|
|
155
|
+
console.warn(`⚠️ Backend not available (${apiBase}): ${reason}. Using local mode.`);
|
|
147
156
|
}
|
|
148
157
|
}
|
|
149
158
|
|
|
@@ -175,6 +184,8 @@ export async function startMission(prompt, targetUrl) {
|
|
|
175
184
|
try {
|
|
176
185
|
const projectId = process.env.ARCALITY_PROJECT_ID || loadArcalityConfig()?.projectId;
|
|
177
186
|
|
|
187
|
+
const controller = new AbortController();
|
|
188
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
178
189
|
const res = await fetch(`${apiBase}/api/v1/missions/start`, {
|
|
179
190
|
method: 'POST',
|
|
180
191
|
headers: {
|
|
@@ -186,8 +197,10 @@ export async function startMission(prompt, targetUrl) {
|
|
|
186
197
|
prompt_hash: simpleHash(prompt),
|
|
187
198
|
target_url: targetUrl,
|
|
188
199
|
project_id: projectId || undefined,
|
|
189
|
-
})
|
|
200
|
+
}),
|
|
201
|
+
signal: controller.signal
|
|
190
202
|
});
|
|
203
|
+
clearTimeout(timeout);
|
|
191
204
|
|
|
192
205
|
if (res.status === 429) {
|
|
193
206
|
const data = await res.json();
|
|
@@ -196,8 +209,9 @@ export async function startMission(prompt, targetUrl) {
|
|
|
196
209
|
if (!res.ok) return { allowed: false, error: 'server_error' };
|
|
197
210
|
|
|
198
211
|
return { allowed: true, ...(await res.json()) };
|
|
199
|
-
} catch {
|
|
200
|
-
|
|
212
|
+
} catch (e) {
|
|
213
|
+
const reason = e?.name === 'AbortError' ? 'timeout (5s)' : (e?.message || 'unknown');
|
|
214
|
+
console.warn(`⚠️ startMission failed (${apiBase}): ${reason}. Falling back to mock mode.`);
|
|
201
215
|
}
|
|
202
216
|
}
|
|
203
217
|
|
|
@@ -236,7 +250,7 @@ export async function startMission(prompt, targetUrl) {
|
|
|
236
250
|
* @param {'success'|'failed'|'cancelled'} result
|
|
237
251
|
* @param {object} usage - Optional usage statistics directly from the AI
|
|
238
252
|
*/
|
|
239
|
-
export async function endMission(missionId, result, usage = null) {
|
|
253
|
+
export async function endMission(missionId, result, usage = null, failReason = null) {
|
|
240
254
|
const apiBase = getEffectiveApiBase();
|
|
241
255
|
if (!apiBase || !missionId) return { ok: true };
|
|
242
256
|
|
|
@@ -248,7 +262,7 @@ export async function endMission(missionId, result, usage = null) {
|
|
|
248
262
|
'Content-Type': 'application/json',
|
|
249
263
|
'x-api-key': key
|
|
250
264
|
},
|
|
251
|
-
body: JSON.stringify({ result, usage })
|
|
265
|
+
body: JSON.stringify({ result, usage, failReason })
|
|
252
266
|
});
|
|
253
267
|
} catch { }
|
|
254
268
|
return { ok: true };
|
|
@@ -266,3 +280,33 @@ function simpleHash(str) {
|
|
|
266
280
|
}
|
|
267
281
|
return 'ph_' + Math.abs(hash).toString(36);
|
|
268
282
|
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Obtiene el Token SAS y el path base para subir evidencias.
|
|
286
|
+
* @param {string} missionId
|
|
287
|
+
* @param {string} projectId
|
|
288
|
+
*/
|
|
289
|
+
export async function getEvidenceSasToken(missionId, projectId) {
|
|
290
|
+
const apiBase = getEffectiveApiBase();
|
|
291
|
+
if (!apiBase || !missionId || !projectId) return null;
|
|
292
|
+
|
|
293
|
+
const key = getEffectiveApiKey();
|
|
294
|
+
if (!key) return null;
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const res = await fetch(`${apiBase}/api/v1/missions/${missionId}/evidence/sas`, {
|
|
298
|
+
method: 'POST',
|
|
299
|
+
headers: {
|
|
300
|
+
'Content-Type': 'application/json',
|
|
301
|
+
'Authorization': `Bearer ${key}`
|
|
302
|
+
},
|
|
303
|
+
body: JSON.stringify({ projectId })
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
if (!res.ok) return null;
|
|
307
|
+
|
|
308
|
+
return await res.json();
|
|
309
|
+
} catch {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
}
|
package/src/configLoader.mjs
CHANGED
|
@@ -18,7 +18,8 @@ export const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
|
18
18
|
* @returns {string}
|
|
19
19
|
*/
|
|
20
20
|
export function getApiUrl() {
|
|
21
|
-
return
|
|
21
|
+
// return 'https://arcalityqadev.arcadial.lat';
|
|
22
|
+
return 'https://arcalityqadev.arcadial.lat';
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
package/src/index.ts
CHANGED
|
@@ -2,13 +2,31 @@ import 'dotenv/config';
|
|
|
2
2
|
import { showBanner } from './consoleBanner';
|
|
3
3
|
import { promptAndRunPlaywrightTests } from './testRunner';
|
|
4
4
|
import { KnowledgeService } from './KnowledgeService';
|
|
5
|
-
import { ArcalityClient } from './arcalityClient';
|
|
5
|
+
import { ArcalityClient, validateApiKey } from './arcalityClient';
|
|
6
6
|
import { loadConfig } from './configLoader';
|
|
7
|
+
import chalk from 'chalk';
|
|
7
8
|
|
|
8
9
|
async function main() {
|
|
9
10
|
showBanner();
|
|
10
11
|
|
|
11
12
|
const config = loadConfig();
|
|
13
|
+
|
|
14
|
+
// Validar API Key y obtener contexto de organización
|
|
15
|
+
console.log(chalk.blue('Validating API Key...'));
|
|
16
|
+
const auth = await validateApiKey();
|
|
17
|
+
if (auth.valid) {
|
|
18
|
+
const orgId = auth.organization_id || auth.organizationId || auth.OrganizationId;
|
|
19
|
+
if (orgId) {
|
|
20
|
+
process.env.ARCALITY_ORG_ID = orgId;
|
|
21
|
+
console.log(chalk.green(`✅ Session initialized for organization: ${auth.organization_name || auth.organizationName || auth.OrganizationName || orgId}`));
|
|
22
|
+
} else {
|
|
23
|
+
console.warn(chalk.yellow('⚠️ API Key is valid but no organization_id was returned.'));
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
console.error(chalk.red(`❌ Invalid API Key: ${auth.error || 'Unknown error'}`));
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
12
30
|
const arcalityClient = new ArcalityClient(config.ARCALITY_API_KEY);
|
|
13
31
|
const knowledgeService = KnowledgeService.getInstance();
|
|
14
32
|
|
|
@@ -11,18 +11,32 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
const getBase = () => process.env.ARCALITY_API_URL ? `${process.env.ARCALITY_API_URL}/api/v1` : null;
|
|
14
|
-
const getKey
|
|
15
|
-
const getPid
|
|
14
|
+
const getKey = () => process.env.ARCALITY_API_KEY || '';
|
|
15
|
+
const getPid = () => process.env.ARCALITY_PROJECT_ID || '';
|
|
16
|
+
const getOrgId = () => process.env.ARCALITY_ORG_ID;
|
|
17
|
+
const getMissionId = () => process.env.ARCALITY_MISSION_ID || EMPTY_GUID;
|
|
16
18
|
|
|
17
19
|
const EMPTY_GUID = '00000000-0000-0000-0000-000000000000';
|
|
18
20
|
|
|
21
|
+
let configWarningLogged = false;
|
|
22
|
+
|
|
19
23
|
function isConfigured(): boolean {
|
|
20
24
|
const base = getBase();
|
|
21
|
-
const key
|
|
22
|
-
const pid
|
|
25
|
+
const key = getKey();
|
|
26
|
+
const pid = getPid();
|
|
27
|
+
const orgId = getOrgId();
|
|
23
28
|
|
|
24
|
-
if (!base || !key || !pid || pid === EMPTY_GUID) {
|
|
25
|
-
|
|
29
|
+
if (!base || !key || !pid || pid === EMPTY_GUID || !orgId) {
|
|
30
|
+
if (!configWarningLogged) {
|
|
31
|
+
const missing = [];
|
|
32
|
+
if (!base) missing.push('ARCALITY_API_URL');
|
|
33
|
+
if (!key) missing.push('ARCALITY_API_KEY');
|
|
34
|
+
if (!pid || pid === EMPTY_GUID) missing.push('ARCALITY_PROJECT_ID');
|
|
35
|
+
if (!orgId) missing.push('ARCALITY_ORG_ID');
|
|
36
|
+
|
|
37
|
+
console.warn(`[CollectiveMemory] Service disabled. Missing: ${missing.join(', ')}`);
|
|
38
|
+
configWarningLogged = true;
|
|
39
|
+
}
|
|
26
40
|
return false;
|
|
27
41
|
}
|
|
28
42
|
return true;
|
|
@@ -48,6 +62,7 @@ export async function pushRule(rule: {
|
|
|
48
62
|
'x-api-key': getKey()
|
|
49
63
|
},
|
|
50
64
|
body: JSON.stringify({
|
|
65
|
+
organization_id: getOrgId(),
|
|
51
66
|
project_id: getPid(),
|
|
52
67
|
rule_type: rule.rule_type,
|
|
53
68
|
title: rule.title.substring(0, 100),
|
|
@@ -98,6 +113,7 @@ export async function pushField(field: {
|
|
|
98
113
|
'x-api-key': getKey()
|
|
99
114
|
},
|
|
100
115
|
body: JSON.stringify({
|
|
116
|
+
organization_id: getOrgId(),
|
|
101
117
|
project_id: getPid(),
|
|
102
118
|
page_id: field.page_id ?? null,
|
|
103
119
|
field_identifier: field.field_identifier.substring(0, 100),
|
|
@@ -138,6 +154,7 @@ export async function pushKnowledge(knowledge: {
|
|
|
138
154
|
'x-api-key': getKey()
|
|
139
155
|
},
|
|
140
156
|
body: JSON.stringify({
|
|
157
|
+
organization_id: getOrgId(),
|
|
141
158
|
project_id: getPid(),
|
|
142
159
|
doc_type: knowledge.doc_type,
|
|
143
160
|
title: knowledge.title.substring(0, 150),
|
|
@@ -160,13 +177,87 @@ export async function pushKnowledge(knowledge: {
|
|
|
160
177
|
}
|
|
161
178
|
}
|
|
162
179
|
|
|
180
|
+
// ─── 4. PROMPT EXECUTION PATTERNS ─────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
export async function searchPromptPattern(prompt: string, limit: number = 5): Promise<any[]> {
|
|
183
|
+
if (!isConfigured()) return [];
|
|
184
|
+
try {
|
|
185
|
+
const apiUrl = process.env.ARCALITY_API_URL ? `${process.env.ARCALITY_API_URL}/api/v1/prompt-execution-patterns/search` : `${getBase()}/prompt-execution-patterns/search`;
|
|
186
|
+
|
|
187
|
+
const payload = {
|
|
188
|
+
organization_id: getOrgId(),
|
|
189
|
+
project_id: getPid(),
|
|
190
|
+
prompt: prompt,
|
|
191
|
+
limit: limit
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
console.log(`[PatternSearch] 🔍 Buscando patrones en: ${apiUrl}`);
|
|
195
|
+
console.log(`[PatternSearch] 📦 Payload: ${JSON.stringify(payload, null, 2)}`);
|
|
196
|
+
|
|
197
|
+
const res = await fetch(apiUrl, {
|
|
198
|
+
method: 'POST',
|
|
199
|
+
headers: {
|
|
200
|
+
'Content-Type': 'application/json',
|
|
201
|
+
'x-api-key': getKey()
|
|
202
|
+
},
|
|
203
|
+
body: JSON.stringify(payload)
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (!res.ok) {
|
|
207
|
+
console.warn(`[PatternSearch] HTTP ${res.status} from ${apiUrl}`);
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
const data = await res.json();
|
|
211
|
+
const results = Array.isArray(data) ? data : (data.matches || data.results || []);
|
|
212
|
+
console.log(`[PatternSearch] ✅ ${results.length} patrones encontrados.`);
|
|
213
|
+
return results;
|
|
214
|
+
} catch (err: any) {
|
|
215
|
+
console.warn(`[PatternSearch] ⚠️ Error buscando patrones: ${err?.message || 'unknown'}`);
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export async function savePromptPattern(patternData: any): Promise<boolean> {
|
|
221
|
+
if (!isConfigured()) return false;
|
|
222
|
+
try {
|
|
223
|
+
const apiUrl = process.env.ARCALITY_API_URL ? `${process.env.ARCALITY_API_URL}/api/v1/prompt-execution-patterns` : `${getBase()}/prompt-execution-patterns`;
|
|
224
|
+
|
|
225
|
+
const payload = {
|
|
226
|
+
organization_id: getOrgId(),
|
|
227
|
+
project_id: getPid(),
|
|
228
|
+
mission_id: getMissionId(),
|
|
229
|
+
...patternData
|
|
230
|
+
};
|
|
231
|
+
console.log(`[PatternSave] 📤 POST ${apiUrl} | org=${payload.organization_id} proj=${payload.project_id} mission=${payload.mission_id}`);
|
|
232
|
+
|
|
233
|
+
const res = await fetch(apiUrl, {
|
|
234
|
+
method: 'POST',
|
|
235
|
+
headers: {
|
|
236
|
+
'Content-Type': 'application/json',
|
|
237
|
+
'x-api-key': getKey()
|
|
238
|
+
},
|
|
239
|
+
body: JSON.stringify(payload)
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
if (!res.ok) {
|
|
243
|
+
console.warn(`[PatternSave] ⚠️ HTTP ${res.status} al guardar patrón en ${apiUrl}: ${await res.text().catch(() => '')}`);
|
|
244
|
+
} else {
|
|
245
|
+
console.log(`[PatternSave] ✅ Patrón guardado exitosamente.`);
|
|
246
|
+
}
|
|
247
|
+
return res.ok;
|
|
248
|
+
} catch (err: any) {
|
|
249
|
+
console.warn(`[PatternSave] ⚠️ Error guardando patrón: ${err?.message || 'unknown'}`);
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
163
254
|
// ─── HELPER: Obtener campos ya conocidos para deduplicación ─────────────────
|
|
164
255
|
|
|
165
256
|
export async function getKnownFieldIdentifiers(pathUrl: string): Promise<string[]> {
|
|
166
257
|
if (!isConfigured()) return [];
|
|
167
258
|
try {
|
|
168
259
|
const res = await fetch(
|
|
169
|
-
`${getBase()}/portal/context?project_id=${getPid()}&path=${encodeURIComponent(pathUrl)}`,
|
|
260
|
+
`${getBase()}/portal/context?organization_id=${getOrgId()}&project_id=${getPid()}&path=${encodeURIComponent(pathUrl)}`,
|
|
170
261
|
{ headers: { 'x-api-key': getKey() } }
|
|
171
262
|
);
|
|
172
263
|
if (!res.ok) return [];
|