@arcadialdev/arcality 2.4.50 → 2.6.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 +25 -21
- package/package.json +1 -1
- package/scripts/gen-and-run.mjs +72 -84
- package/src/EvidenceUploader.mjs +45 -45
- package/src/arcalityClient.mjs +15 -8
- package/tests/_helpers/ArcalityReporter.js +729 -729
package/README.md
CHANGED
|
@@ -29,43 +29,47 @@
|
|
|
29
29
|
npm install @arcadialdev/arcality
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
> After install, Arcality **automatically adds the following scripts** to your `package.json`:
|
|
33
|
-
> ```json
|
|
34
|
-
> "arcality": "arcality run",
|
|
35
|
-
> "arcality:init": "arcality init",
|
|
36
|
-
> "arcality:run": "arcality run"
|
|
37
|
-
> ```
|
|
38
32
|
|
|
39
33
|
### 2. Initialize your project
|
|
40
|
-
Run the setup wizard
|
|
34
|
+
Run the setup wizard to connect your project and generate the configuration.
|
|
41
35
|
|
|
42
36
|
```bash
|
|
43
|
-
|
|
37
|
+
npx arcality init
|
|
44
38
|
```
|
|
45
39
|
|
|
46
|
-
### 3. Run
|
|
47
|
-
|
|
40
|
+
### 3. Run your first mission
|
|
48
41
|
```bash
|
|
49
|
-
#
|
|
50
|
-
|
|
42
|
+
# Open interactive menu
|
|
43
|
+
npx arcality
|
|
44
|
+
|
|
45
|
+
# Or run directly (Ghost Mode)
|
|
46
|
+
npx arcality run
|
|
47
|
+
```
|
|
48
|
+
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
### 📦 Updating Arcality
|
|
51
|
+
Run the following to get the latest version:
|
|
52
|
+
```bash
|
|
53
|
+
npm update @arcadialdev/arcality
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
### ⚙️ Configuration (`arcality.config.json`)
|
|
57
|
+
After initializing with `npx arcality init`, a config file is created in the project root. Edit it to set your project name, base URL, and authentication details. The CLI reads this file on every run, ensuring consistent environment variables.
|
|
58
|
+
|
|
59
|
+
|
|
56
60
|
---
|
|
57
61
|
|
|
58
62
|
## 💻 CLI Commands Cheatsheet
|
|
59
63
|
|
|
60
|
-
The Arcality flow is designed to be used globally or project-wide via
|
|
64
|
+
The Arcality flow is designed to be used globally or project-wide via `npx`. Below are all currently supported commands:
|
|
61
65
|
|
|
62
66
|
| Command | Description | Internal Behavior |
|
|
63
67
|
| :--- | :--- | :--- |
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
|
|
|
67
|
-
|
|
|
68
|
-
|
|
68
|
+
| `npx arcality` | **Interactive Menu**<br/>Launches a visual menu in the terminal to guide the user. | Prompts for the Mission to execute. Ideal for new users who want a guided experience. |
|
|
69
|
+
| `npx arcality init` | **Initial Configuration**<br/>Connects the current project to Arcadial. | Prompts for your API Key and Project ID, generates `arcality.config.json`, and validates the portal connection. |
|
|
70
|
+
| `npx arcality run` | **Mission Runner (Agent Mode)**<br/>Launches the QA Agent directly. | Skips the interactive menu, scans the _package.json_ ("Ghost Mode"), and unleashes the AI agent. Ideal for CI/CD. |
|
|
71
|
+
| `npx arcality --logs` | **History Viewer**<br/>Displays test performance and tokens spent. | Reads the local `arcality-history.log` and renders a colorful table listing each mission and success status. |
|
|
72
|
+
|
|
69
73
|
|
|
70
74
|
---
|
|
71
75
|
|
|
@@ -83,7 +87,7 @@ Arcality operates on a highly decoupled modular system designed for maximum resi
|
|
|
83
87
|
|
|
84
88
|
## 📁 Configuration (`arcality.config.json`)
|
|
85
89
|
|
|
86
|
-
When you run `arcality init`, a configuration is generated in your project root.
|
|
90
|
+
When you run `npx arcality init`, a configuration is generated in your project root.
|
|
87
91
|
```json
|
|
88
92
|
{
|
|
89
93
|
"project": {
|
package/package.json
CHANGED
package/scripts/gen-and-run.mjs
CHANGED
|
@@ -770,12 +770,8 @@ async function main() {
|
|
|
770
770
|
}
|
|
771
771
|
} catch { /* silencioso */ }
|
|
772
772
|
|
|
773
|
-
// 2.
|
|
774
|
-
|
|
775
|
-
const { endMission: endM } = await import('../src/arcalityClient.mjs');
|
|
776
|
-
await endM(mission.mission_id, 'canceled', null, 'user_canceled');
|
|
777
|
-
console.log(chalk.gray(' >> Misión registrada como "cancelada" en el servidor.'));
|
|
778
|
-
} catch { /* silencioso si el backend no está disponible */ }
|
|
773
|
+
// 2. Registraremos la misión como 'canceled' en el bloque finally
|
|
774
|
+
console.log(chalk.gray(' >> Registrando misión como "cancelada"...'));
|
|
779
775
|
|
|
780
776
|
// 3. Matar el proceso hijo de Playwright — hacerlo AL FINAL para dar tiempo
|
|
781
777
|
// a que el agente-log y endMission terminen antes del kill.
|
|
@@ -852,6 +848,11 @@ async function main() {
|
|
|
852
848
|
process.exit(1);
|
|
853
849
|
}
|
|
854
850
|
|
|
851
|
+
let missionResult = null;
|
|
852
|
+
let finalFailReason = null;
|
|
853
|
+
let finalUsageData = undefined;
|
|
854
|
+
let finalReportUrl = null;
|
|
855
|
+
|
|
855
856
|
try {
|
|
856
857
|
// No test file path is passed — playwright.config.js uses testMatch:'agentic-runner.spec.ts'
|
|
857
858
|
// This eliminates the Windows regex path issue permanently on any user's machine.
|
|
@@ -872,46 +873,15 @@ async function main() {
|
|
|
872
873
|
process.off('SIGINT', cancelHandler);
|
|
873
874
|
process.off('SIGTERM', cancelHandler);
|
|
874
875
|
|
|
875
|
-
if (userCanceledMission)
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
let finalUsage = undefined;
|
|
880
|
-
try {
|
|
881
|
-
const ctxDir = process.env.CONTEXT_DIR;
|
|
882
|
-
if (fs.existsSync(ctxDir)) {
|
|
883
|
-
const files = fs.readdirSync(ctxDir);
|
|
884
|
-
const logFiles = files.filter(f => f.startsWith('agent-log-') && f.endsWith('.json')).sort();
|
|
885
|
-
if (logFiles.length > 0) {
|
|
886
|
-
const lastLog = path.join(ctxDir, logFiles[logFiles.length - 1]);
|
|
887
|
-
const logData = JSON.parse(fs.readFileSync(lastLog, 'utf8'));
|
|
888
|
-
finalUsage = logData.usage;
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
} catch(e) {}
|
|
892
|
-
|
|
893
|
-
// ── Upload Evidence ──
|
|
894
|
-
const { endMission, getEvidenceSasToken } = await import('../src/arcalityClient.mjs');
|
|
895
|
-
const { uploadEvidence } = await import('../src/EvidenceUploader.mjs');
|
|
896
|
-
|
|
897
|
-
try {
|
|
898
|
-
const sasData = await getEvidenceSasToken(mission.mission_id, finalProjectId);
|
|
899
|
-
if (sasData && sasData.sasUrl) {
|
|
900
|
-
if (!process.argv.includes('--debug')) s.start('☁️ Uploading evidence to Azure...');
|
|
901
|
-
const reportsDir = process.env.REPORTS_DIR || path.join(ORIGINAL_CWD, 'tests-report');
|
|
902
|
-
|
|
903
|
-
await run("node", [path.join(PROJECT_ROOT, "scripts/rebrand-report.mjs")]).catch(() => { });
|
|
904
|
-
|
|
905
|
-
await uploadEvidence(reportsDir, sasData.sasUrl, sasData.basePath);
|
|
906
|
-
if (!process.argv.includes('--debug')) s.stop(chalk.green('✅ Evidence uploaded successfully.'));
|
|
907
|
-
}
|
|
908
|
-
} catch (e) {
|
|
909
|
-
if (process.argv.includes('--debug')) console.log(chalk.yellow(`⚠️ Could not upload evidence: ${e.message}`));
|
|
910
|
-
if (!process.argv.includes('--debug')) s.stop(chalk.yellow('⚠️ Evidence upload failed.'));
|
|
876
|
+
if (userCanceledMission) {
|
|
877
|
+
missionResult = 'canceled';
|
|
878
|
+
finalFailReason = 'user_canceled';
|
|
879
|
+
return; // El handler ya tomó el control, finally se ejecutará y luego saldrá del CLI
|
|
911
880
|
}
|
|
912
881
|
|
|
913
|
-
|
|
914
|
-
|
|
882
|
+
missionResult = 'success';
|
|
883
|
+
if (!process.argv.includes('--debug')) s.stop(chalk.green('✅ Mission completed successfully.'));
|
|
884
|
+
else console.log(chalk.green('✅ Mission completed successfully.'));
|
|
915
885
|
|
|
916
886
|
// ── Ping Project ──
|
|
917
887
|
await pingProject(finalProjectId, process.env.ARCALITY_API_KEY);
|
|
@@ -967,58 +937,24 @@ async function main() {
|
|
|
967
937
|
process.off('SIGTERM', cancelHandler);
|
|
968
938
|
|
|
969
939
|
if (userCanceledMission) {
|
|
940
|
+
missionResult = 'canceled';
|
|
941
|
+
finalFailReason = 'user_canceled';
|
|
970
942
|
// El cancelHandler ya registró la cancelación en el servidor.
|
|
971
943
|
// Solo mostramos un mensaje y dejamos que el finally genere el reporte.
|
|
972
944
|
console.log(chalk.yellow('\n⚠️ Misión cancelada — generando reporte de evidencia...'));
|
|
973
945
|
} else {
|
|
946
|
+
missionResult = 'failed';
|
|
947
|
+
finalFailReason = 'timeout_or_crash';
|
|
974
948
|
s.stop(chalk.red(`❌ Mission could not be completed.`));
|
|
975
949
|
console.error(chalk.red(`\nError Details: ${e.message}`));
|
|
976
950
|
if (e.stack && process.argv.includes('--debug')) {
|
|
977
951
|
console.error(chalk.gray(e.stack));
|
|
978
952
|
}
|
|
979
|
-
|
|
980
|
-
// Si NO fue cancelación de usuario, cerrar la misión como fallida en el servidor
|
|
981
|
-
try {
|
|
982
|
-
let finalUsage = undefined;
|
|
983
|
-
let failReason = "timeout_or_crash";
|
|
984
|
-
try {
|
|
985
|
-
const ctxDir = process.env.CONTEXT_DIR;
|
|
986
|
-
if (fs.existsSync(ctxDir)) {
|
|
987
|
-
const files = fs.readdirSync(ctxDir);
|
|
988
|
-
const logFiles = files.filter(f => f.startsWith('agent-log-') && f.endsWith('.json')).sort();
|
|
989
|
-
if (logFiles.length > 0) {
|
|
990
|
-
const lastLog = path.join(ctxDir, logFiles[logFiles.length - 1]);
|
|
991
|
-
const logData = JSON.parse(fs.readFileSync(lastLog, 'utf8'));
|
|
992
|
-
finalUsage = logData.usage;
|
|
993
|
-
failReason = logData.fail_reason || failReason;
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
} catch(e) {}
|
|
997
|
-
|
|
998
|
-
// ── Upload Evidence ──
|
|
999
|
-
const { endMission, getEvidenceSasToken } = await import('../src/arcalityClient.mjs');
|
|
1000
|
-
const { uploadEvidence } = await import('../src/EvidenceUploader.mjs');
|
|
1001
|
-
|
|
1002
|
-
try {
|
|
1003
|
-
const sasData = await getEvidenceSasToken(mission.mission_id, finalProjectId);
|
|
1004
|
-
if (sasData && sasData.sasUrl) {
|
|
1005
|
-
if (!process.argv.includes('--debug')) s.start('☁️ Uploading evidence to Azure...');
|
|
1006
|
-
const reportsDir = process.env.REPORTS_DIR || path.join(ORIGINAL_CWD, 'tests-report');
|
|
1007
|
-
|
|
1008
|
-
await run("node", [path.join(PROJECT_ROOT, "scripts/rebrand-report.mjs")]).catch(() => { });
|
|
1009
|
-
|
|
1010
|
-
await uploadEvidence(reportsDir, sasData.sasUrl, sasData.basePath);
|
|
1011
|
-
if (!process.argv.includes('--debug')) s.stop(chalk.green('✅ Evidence uploaded successfully.'));
|
|
1012
|
-
}
|
|
1013
|
-
} catch (e) {}
|
|
1014
|
-
|
|
1015
|
-
await endMission(mission.mission_id, 'failed', finalUsage, failReason);
|
|
1016
|
-
} catch { }
|
|
1017
953
|
}
|
|
1018
954
|
} finally {
|
|
1019
|
-
// Si fue cancelación de usuario, dar un breve margen para que el cancelHandler
|
|
1020
|
-
// async (endMission, escritura de log) termine antes de generar el reporte.
|
|
1021
955
|
if (userCanceledMission) {
|
|
956
|
+
missionResult = 'canceled';
|
|
957
|
+
finalFailReason = 'user_canceled';
|
|
1022
958
|
await new Promise(r => setTimeout(r, 800));
|
|
1023
959
|
}
|
|
1024
960
|
|
|
@@ -1029,6 +965,58 @@ async function main() {
|
|
|
1029
965
|
const reportDir = process.env.REPORTS_DIR || 'tests-report';
|
|
1030
966
|
const arcalityPath = path.resolve(reportDir, 'index.html');
|
|
1031
967
|
|
|
968
|
+
sRep.stop(chalk.cyan('Report process initialized.'));
|
|
969
|
+
|
|
970
|
+
// Read final usage and failReason from agent-log
|
|
971
|
+
try {
|
|
972
|
+
const ctxDir = process.env.CONTEXT_DIR;
|
|
973
|
+
if (fs.existsSync(ctxDir)) {
|
|
974
|
+
const files = fs.readdirSync(ctxDir);
|
|
975
|
+
const logFiles = files.filter(f => f.startsWith('agent-log-') && f.endsWith('.json')).sort();
|
|
976
|
+
if (logFiles.length > 0) {
|
|
977
|
+
const lastLog = path.join(ctxDir, logFiles[logFiles.length - 1]);
|
|
978
|
+
const logData = JSON.parse(fs.readFileSync(lastLog, 'utf8'));
|
|
979
|
+
finalUsageData = logData.usage || finalUsageData;
|
|
980
|
+
if (missionResult === 'failed' && !finalFailReason) {
|
|
981
|
+
finalFailReason = logData.fail_reason || "timeout_or_crash";
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
} catch(e) {}
|
|
986
|
+
|
|
987
|
+
// ── Upload Evidence ──
|
|
988
|
+
try {
|
|
989
|
+
const { endMission, getEvidenceSasToken } = await import('../src/arcalityClient.mjs');
|
|
990
|
+
const { uploadEvidence } = await import('../src/EvidenceUploader.mjs');
|
|
991
|
+
|
|
992
|
+
const sasData = await getEvidenceSasToken(mission.mission_id, finalProjectId);
|
|
993
|
+
console.log(`[DEBUG] sasData received:`, sasData);
|
|
994
|
+
|
|
995
|
+
// Soporte para camelCase y snake_case debido al serializador JSON del backend
|
|
996
|
+
const actualSasUrl = sasData?.sasTokenUrl || sasData?.sasUrl || sasData?.sas_url;
|
|
997
|
+
const actualBasePath = sasData?.basePath || sasData?.base_path;
|
|
998
|
+
|
|
999
|
+
if (sasData && actualSasUrl && actualBasePath) {
|
|
1000
|
+
if (!process.argv.includes('--debug')) sRep.start('☁️ Uploading evidence to Azure...');
|
|
1001
|
+
|
|
1002
|
+
await uploadEvidence(reportDir, actualSasUrl, actualBasePath);
|
|
1003
|
+
|
|
1004
|
+
// Construct the public URL
|
|
1005
|
+
try {
|
|
1006
|
+
const parsedSas = new URL(actualSasUrl);
|
|
1007
|
+
finalReportUrl = `${parsedSas.origin}${parsedSas.pathname}/${actualBasePath}/index.html`;
|
|
1008
|
+
} catch(e) {}
|
|
1009
|
+
|
|
1010
|
+
if (!process.argv.includes('--debug')) sRep.stop(chalk.green('✅ Evidence uploaded successfully.'));
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// ── End Mission ──
|
|
1014
|
+
await endMission(mission.mission_id, missionResult || 'failed', finalUsageData, finalFailReason, finalReportUrl);
|
|
1015
|
+
} catch (e) {
|
|
1016
|
+
if (process.argv.includes('--debug')) console.log(chalk.yellow(`⚠️ Could not upload evidence or end mission: ${e.message}`));
|
|
1017
|
+
if (!process.argv.includes('--debug')) sRep.stop(chalk.yellow('⚠️ Evidence upload or end mission failed.'));
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1032
1020
|
if (globalReportProcess) {
|
|
1033
1021
|
try {
|
|
1034
1022
|
if (process.platform === "win32") {
|
package/src/EvidenceUploader.mjs
CHANGED
|
@@ -1,45 +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
|
-
}
|
|
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/arcalityClient.mjs
CHANGED
|
@@ -250,7 +250,7 @@ export async function startMission(prompt, targetUrl) {
|
|
|
250
250
|
* @param {'success'|'failed'|'cancelled'} result
|
|
251
251
|
* @param {object} usage - Optional usage statistics directly from the AI
|
|
252
252
|
*/
|
|
253
|
-
export async function endMission(missionId, result, usage = null, failReason = null) {
|
|
253
|
+
export async function endMission(missionId, result, usage = null, failReason = null, reportUrl = null) {
|
|
254
254
|
const apiBase = getEffectiveApiBase();
|
|
255
255
|
if (!apiBase || !missionId) return { ok: true };
|
|
256
256
|
|
|
@@ -262,7 +262,7 @@ export async function endMission(missionId, result, usage = null, failReason = n
|
|
|
262
262
|
'Content-Type': 'application/json',
|
|
263
263
|
'x-api-key': key
|
|
264
264
|
},
|
|
265
|
-
body: JSON.stringify({ result, usage, failReason })
|
|
265
|
+
body: JSON.stringify({ result, usage, failReason, reportUrl })
|
|
266
266
|
});
|
|
267
267
|
} catch { }
|
|
268
268
|
return { ok: true };
|
|
@@ -288,25 +288,32 @@ function simpleHash(str) {
|
|
|
288
288
|
*/
|
|
289
289
|
export async function getEvidenceSasToken(missionId, projectId) {
|
|
290
290
|
const apiBase = getEffectiveApiBase();
|
|
291
|
-
if (!apiBase || !missionId
|
|
291
|
+
if (!apiBase || !missionId) return null;
|
|
292
292
|
|
|
293
293
|
const key = getEffectiveApiKey();
|
|
294
294
|
if (!key) return null;
|
|
295
295
|
|
|
296
296
|
try {
|
|
297
|
+
const organization_id = process.env.ARCALITY_ORG_ID || '';
|
|
297
298
|
const res = await fetch(`${apiBase}/api/v1/missions/${missionId}/evidence/sas`, {
|
|
298
299
|
method: 'POST',
|
|
299
300
|
headers: {
|
|
300
301
|
'Content-Type': 'application/json',
|
|
301
|
-
'
|
|
302
|
+
'x-api-key': key
|
|
302
303
|
},
|
|
303
|
-
body: JSON.stringify({ projectId })
|
|
304
|
+
body: JSON.stringify({ organization_id, project_id: projectId })
|
|
304
305
|
});
|
|
305
306
|
|
|
306
|
-
if (!res.ok)
|
|
307
|
+
if (!res.ok) {
|
|
308
|
+
const txt = await res.text();
|
|
309
|
+
console.log(`[DEBUG] SAS Token API Failed: ${res.status} - ${txt}`);
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
307
312
|
|
|
308
|
-
|
|
309
|
-
|
|
313
|
+
const data = await res.json();
|
|
314
|
+
return data;
|
|
315
|
+
} catch (e) {
|
|
316
|
+
console.log(`[DEBUG] SAS Token API Error: ${e.message}`);
|
|
310
317
|
return null;
|
|
311
318
|
}
|
|
312
319
|
}
|