@aiscene/aiserver 1.4.3 → 1.4.5
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/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +2 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +1 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/core/types.d.ts +0 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/debug/websocket-server.d.ts +4 -0
- package/dist/debug/websocket-server.d.ts.map +1 -1
- package/dist/debug/websocket-server.js +190 -159
- package/dist/debug/websocket-server.js.map +1 -1
- package/dist/executor/action-executor.d.ts.map +1 -1
- package/dist/executor/action-executor.js +4 -19
- package/dist/executor/action-executor.js.map +1 -1
- package/dist/executor/android-executor.d.ts.map +1 -1
- package/dist/executor/android-executor.js +1 -2
- package/dist/executor/android-executor.js.map +1 -1
- package/dist/executor/cli-executor.d.ts.map +1 -1
- package/dist/executor/cli-executor.js +1 -2
- package/dist/executor/cli-executor.js.map +1 -1
- package/dist/executor/ios-executor.d.ts.map +1 -1
- package/dist/executor/ios-executor.js +1 -2
- package/dist/executor/ios-executor.js.map +1 -1
- package/package.json +3 -3
|
@@ -30,41 +30,71 @@ function getLocalIP() {
|
|
|
30
30
|
}
|
|
31
31
|
return '127.0.0.1';
|
|
32
32
|
}
|
|
33
|
-
/**
|
|
34
|
-
async function uploadReportToServer(
|
|
35
|
-
if (!
|
|
36
|
-
logger.
|
|
33
|
+
/** 上传报告文件到服务器(流式上传,不将整个文件读入内存) */
|
|
34
|
+
async function uploadReportToServer(reportFilePath, sessionId, uploadUrl) {
|
|
35
|
+
if (!reportFilePath) {
|
|
36
|
+
logger.info('[UploadReport] No report file path provided, skip upload');
|
|
37
37
|
return null;
|
|
38
38
|
}
|
|
39
|
+
logger.info(`[UploadReport] === Start upload === sessionId=${sessionId}, filePath=${reportFilePath}`);
|
|
39
40
|
try {
|
|
40
|
-
|
|
41
|
+
if (!fs.existsSync(reportFilePath)) {
|
|
42
|
+
logger.warn(`[UploadReport] Report file not found: ${reportFilePath}, skip upload`);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const stat = fs.statSync(reportFilePath);
|
|
46
|
+
const fileSizeMB = (stat.size / 1024 / 1024).toFixed(2);
|
|
47
|
+
logger.info(`[UploadReport] File exists: ${reportFilePath}, size=${stat.size} bytes (${fileSizeMB}MB)`);
|
|
48
|
+
logger.info(`[UploadReport] Uploading to: POST ${uploadUrl}, sessionId=${sessionId}`);
|
|
49
|
+
const uploadStart = Date.now();
|
|
50
|
+
// 读取文件内容(与 callback.ts uploadReportFile 保持一致的写法,确保后端能正确解析 multipart)
|
|
51
|
+
let fileBuffer;
|
|
52
|
+
if (stat.size > 50 * 1024 * 1024) {
|
|
53
|
+
fileBuffer = await new Promise((resolve, reject) => {
|
|
54
|
+
const chunks = [];
|
|
55
|
+
const readStream = fs.createReadStream(reportFilePath);
|
|
56
|
+
readStream.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
|
57
|
+
readStream.on('end', () => resolve(Buffer.concat(chunks)));
|
|
58
|
+
readStream.on('error', reject);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
fileBuffer = fs.readFileSync(reportFilePath);
|
|
63
|
+
}
|
|
64
|
+
const fileName = `report_${sessionId}.html`;
|
|
41
65
|
const formData = new FormData();
|
|
42
|
-
formData.append('file',
|
|
43
|
-
filename: `report_${sessionId}.html`,
|
|
44
|
-
contentType: 'text/html',
|
|
45
|
-
});
|
|
66
|
+
formData.append('file', fileBuffer, fileName);
|
|
46
67
|
formData.append('sessionId', sessionId);
|
|
47
|
-
logger.info(`[UploadReport] Request: POST ${uploadUrl}`);
|
|
48
|
-
logger.info(`[UploadReport] Request body: sessionId=${sessionId}, fileSize=${buffer.length} bytes`);
|
|
49
68
|
const response = await axios.post(uploadUrl, formData, {
|
|
50
69
|
headers: {
|
|
51
70
|
'User-Agent': 'aiserver/1.0',
|
|
52
|
-
'Accept': '
|
|
71
|
+
'Accept': '*/*',
|
|
72
|
+
'Connection': 'close',
|
|
73
|
+
'Expect': '',
|
|
74
|
+
// form-data 库会自动设置 Content-Type(含 boundary),必须带上
|
|
75
|
+
...formData.getHeaders(),
|
|
53
76
|
},
|
|
54
|
-
timeout:
|
|
77
|
+
timeout: 120000,
|
|
78
|
+
maxRedirects: 0,
|
|
55
79
|
});
|
|
56
|
-
|
|
80
|
+
const uploadCost = Date.now() - uploadStart;
|
|
81
|
+
logger.info(`[UploadReport] Response: status=${response.status}, cost=${uploadCost}ms`);
|
|
57
82
|
logger.info(`[UploadReport] Response data: ${JSON.stringify(response.data)}`);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
83
|
+
// 后端返回 { code: 200, data: { sessionId, reportUrl } } 或 { success: true, data: { reportUrl } }
|
|
84
|
+
const reportUrl = response.data?.data?.reportUrl || response.data?.reportUrl;
|
|
85
|
+
if (reportUrl) {
|
|
86
|
+
logger.info(`[UploadReport] === Upload successful === reportUrl=${reportUrl}, cost=${uploadCost}ms, size=${fileSizeMB}MB`);
|
|
87
|
+
return reportUrl;
|
|
61
88
|
}
|
|
62
|
-
logger.warn(`[UploadReport] Upload response missing reportUrl`);
|
|
89
|
+
logger.warn(`[UploadReport] Upload response missing reportUrl, response=${JSON.stringify(response.data)}`);
|
|
63
90
|
return null;
|
|
64
91
|
}
|
|
65
92
|
catch (error) {
|
|
66
93
|
const err = error;
|
|
67
|
-
logger.error(`[UploadReport] Upload failed
|
|
94
|
+
logger.error(`[UploadReport] === Upload failed === error=${err.message}`);
|
|
95
|
+
if (err.code) {
|
|
96
|
+
logger.error(`[UploadReport] Error code: ${err.code}`);
|
|
97
|
+
}
|
|
68
98
|
if (err.response) {
|
|
69
99
|
logger.error(`[UploadReport] Response status: ${err.response.status}`);
|
|
70
100
|
logger.error(`[UploadReport] Response data: ${JSON.stringify(err.response.data)}`);
|
|
@@ -467,20 +497,18 @@ export class DebugWebSocketServer {
|
|
|
467
497
|
session.executor = undefined;
|
|
468
498
|
sessionManager.updateStatus(sessionId, result.success ? 'completed' : 'failed');
|
|
469
499
|
// 调试:打印 result 内容
|
|
470
|
-
logger.info(`[executeRunCodeInProcess] result: success=${result.success},
|
|
471
|
-
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
logger.info(`[executeRunCodeInProcess] Skip server upload (debug mode), reportHTML size=${reportHTML.length}`);
|
|
500
|
+
logger.info(`[executeRunCodeInProcess] result: success=${result.success}, reportFile=${result.reportFile || ''}, errorMessage=${result.errorMessage}`);
|
|
501
|
+
const reportFile = result.reportFile || '';
|
|
502
|
+
const reportUrl = await this.safeUploadReport(reportFile, sessionId);
|
|
503
|
+
logger.info(`[executeRunCodeInProcess] report uploaded: reportUrl=${reportUrl || 'null'}, reportFile=${reportFile}`);
|
|
475
504
|
this.sendMessage(ws, {
|
|
476
505
|
type: 'action_result',
|
|
477
506
|
sessionId,
|
|
478
507
|
actionType: 'runCode',
|
|
479
508
|
success: result.success,
|
|
480
509
|
result: result.result,
|
|
481
|
-
dump:
|
|
482
|
-
|
|
483
|
-
reportUrl: reportUrl, // 上传后的报告 URL
|
|
510
|
+
dump: '',
|
|
511
|
+
reportUrl,
|
|
484
512
|
error: result.errorMessage,
|
|
485
513
|
platform: request.platform || 'android',
|
|
486
514
|
}, request.deviceId);
|
|
@@ -489,7 +517,7 @@ export class DebugWebSocketServer {
|
|
|
489
517
|
type: 'debug_completed',
|
|
490
518
|
sessionId,
|
|
491
519
|
success: result.success,
|
|
492
|
-
reportUrl
|
|
520
|
+
reportUrl,
|
|
493
521
|
}, request.deviceId);
|
|
494
522
|
}
|
|
495
523
|
/** 自然语言模式:Fork worker 进程执行 aiAction */
|
|
@@ -560,37 +588,16 @@ export class DebugWebSocketServer {
|
|
|
560
588
|
resultHandled = true;
|
|
561
589
|
const success = workerResult.success !== false;
|
|
562
590
|
sessionManager.updateStatus(sessionId, success ? 'completed' : 'failed');
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const reportFile = workerResult.reportFile;
|
|
567
|
-
if (reportFile) {
|
|
568
|
-
try {
|
|
569
|
-
if (fs.existsSync(reportFile)) {
|
|
570
|
-
reportHTML = fs.readFileSync(reportFile, 'utf-8');
|
|
571
|
-
logger.info(`[Debug] Read reportFile: ${reportFile}, size=${reportHTML.length}`);
|
|
572
|
-
}
|
|
573
|
-
else {
|
|
574
|
-
logger.warn(`[Debug] reportFile does not exist: ${reportFile}`);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
catch (e) {
|
|
578
|
-
logger.warn(`[Debug] Read reportFile failed: ${e.message}`);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
if (reportHTML && reportHTML.length > 0) {
|
|
582
|
-
// 调试场景不再上传报告到服务端:reportHTML 直接通过长链接回前端
|
|
583
|
-
logger.info(`[Debug] Skip server upload (debug mode), reportHTML size=${reportHTML.length}`);
|
|
584
|
-
}
|
|
591
|
+
const reportFile = workerResult.reportFile || '';
|
|
592
|
+
const reportUrl = await this.safeUploadReport(reportFile, sessionId);
|
|
593
|
+
logger.info(`[Debug] report uploaded: reportUrl=${reportUrl || 'null'}, reportFile=${reportFile}`);
|
|
585
594
|
this.sendMessage(ws, {
|
|
586
595
|
type: 'debug_completed',
|
|
587
596
|
sessionId,
|
|
588
597
|
success,
|
|
589
|
-
dump:
|
|
590
|
-
reportFile,
|
|
591
|
-
reportHTML,
|
|
598
|
+
dump: '',
|
|
592
599
|
reportUrl,
|
|
593
|
-
|
|
600
|
+
error: workerResult.errorMessage,
|
|
594
601
|
}, request.deviceId);
|
|
595
602
|
};
|
|
596
603
|
childProcess.on('message', (message) => {
|
|
@@ -692,25 +699,23 @@ export class DebugWebSocketServer {
|
|
|
692
699
|
const status = result.success ? 'completed' : 'failed';
|
|
693
700
|
sessionManager.updateStatus(sessionId, status);
|
|
694
701
|
logger.info(`[Action] Execution finished: sessionId=${sessionId}, status=${status}, ws.readyState=${ws.readyState}`);
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
logger.info(`[Action] Skip server upload (debug mode), reportHTML size=${reportHTML.length}`);
|
|
702
|
+
const reportFile = result.reportFile || '';
|
|
703
|
+
const reportUrl = await this.safeUploadReport(reportFile, sessionId);
|
|
704
|
+
logger.info(`[Action] report uploaded: reportUrl=${reportUrl || 'null'}, reportFile=${reportFile}`);
|
|
699
705
|
this.sendMessage(ws, {
|
|
700
706
|
type: 'action_result',
|
|
701
707
|
sessionId,
|
|
702
708
|
success: result.success,
|
|
703
709
|
result: result.result,
|
|
704
|
-
dump:
|
|
705
|
-
|
|
706
|
-
reportUrl: null,
|
|
710
|
+
dump: '',
|
|
711
|
+
reportUrl,
|
|
707
712
|
error: result.errorMessage,
|
|
708
713
|
}, request.deviceId);
|
|
709
714
|
this.sendMessage(ws, {
|
|
710
715
|
type: 'debug_completed',
|
|
711
716
|
sessionId,
|
|
712
717
|
success: result.success,
|
|
713
|
-
reportUrl
|
|
718
|
+
reportUrl,
|
|
714
719
|
}, request.deviceId);
|
|
715
720
|
logger.info(`[Action] Sent action_result + debug_completed for sessionId=${sessionId}`);
|
|
716
721
|
}
|
|
@@ -718,30 +723,32 @@ export class DebugWebSocketServer {
|
|
|
718
723
|
session.executor = undefined;
|
|
719
724
|
sessionManager.updateStatus(sessionId, 'failed');
|
|
720
725
|
logger.error(`[Action] Execution error: sessionId=${sessionId}, error=${error.message}, ws.readyState=${ws.readyState}`);
|
|
721
|
-
// 异常时也尝试提取
|
|
722
|
-
let
|
|
726
|
+
// 异常时也尝试提取 reportFile 并上传
|
|
727
|
+
let reportFile = '';
|
|
723
728
|
try {
|
|
724
729
|
const agent = session.executor?.getAgent?.() || session.agent;
|
|
725
|
-
if (agent && typeof agent.
|
|
726
|
-
|
|
730
|
+
if (agent && typeof agent.reportFile === 'string') {
|
|
731
|
+
reportFile = agent.reportFile || '';
|
|
727
732
|
}
|
|
728
733
|
}
|
|
729
734
|
catch (e) {
|
|
730
|
-
logger.warn(`[Action] Failed to extract
|
|
735
|
+
logger.warn(`[Action] Failed to extract reportFile on error: ${e.message}`);
|
|
731
736
|
}
|
|
737
|
+
const reportUrl = await this.safeUploadReport(reportFile, sessionId);
|
|
732
738
|
this.sendMessage(ws, {
|
|
733
739
|
type: 'action_result',
|
|
734
740
|
sessionId,
|
|
735
741
|
success: false,
|
|
736
742
|
error: error.message,
|
|
737
|
-
reportHTML,
|
|
738
743
|
dump: '',
|
|
744
|
+
reportUrl,
|
|
739
745
|
}, request.deviceId);
|
|
740
746
|
// 异常时也发送 debug_completed,确保前端能退出执行中状态
|
|
741
747
|
this.sendMessage(ws, {
|
|
742
748
|
type: 'debug_completed',
|
|
743
749
|
sessionId,
|
|
744
750
|
success: false,
|
|
751
|
+
reportUrl,
|
|
745
752
|
}, request.deviceId);
|
|
746
753
|
}
|
|
747
754
|
}
|
|
@@ -759,8 +766,8 @@ export class DebugWebSocketServer {
|
|
|
759
766
|
if (finalSent)
|
|
760
767
|
return;
|
|
761
768
|
finalSent = true;
|
|
762
|
-
const
|
|
763
|
-
//
|
|
769
|
+
const reportUrl = payload.reportUrl || null;
|
|
770
|
+
// 第一次尝试
|
|
764
771
|
try {
|
|
765
772
|
this.sendMessage(ws, {
|
|
766
773
|
type: 'ai_act_result',
|
|
@@ -769,15 +776,13 @@ export class DebugWebSocketServer {
|
|
|
769
776
|
success: payload.success,
|
|
770
777
|
result: payload.result,
|
|
771
778
|
dump: '',
|
|
772
|
-
|
|
773
|
-
reportUrl: null,
|
|
779
|
+
reportUrl,
|
|
774
780
|
error: payload.error,
|
|
775
781
|
platform: request.platform || 'android',
|
|
776
782
|
}, request.deviceId);
|
|
777
783
|
}
|
|
778
784
|
catch (e1) {
|
|
779
|
-
logger.error(`[AiAct] sendFinal ai_act_result failed: ${e1.message}, retry
|
|
780
|
-
// 二次降级:去掉 reportHTML 再发一次,最大限度保证前端拿到结束信号
|
|
785
|
+
logger.error(`[AiAct] sendFinal ai_act_result failed: ${e1.message}, retry`);
|
|
781
786
|
try {
|
|
782
787
|
this.sendMessage(ws, {
|
|
783
788
|
type: 'ai_act_result',
|
|
@@ -786,14 +791,13 @@ export class DebugWebSocketServer {
|
|
|
786
791
|
success: payload.success,
|
|
787
792
|
result: payload.result,
|
|
788
793
|
dump: '',
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
error: (payload.error || '') + ' [reportHTML dropped: send failed]',
|
|
794
|
+
reportUrl,
|
|
795
|
+
error: (payload.error || '') + ' [send failed]',
|
|
792
796
|
platform: request.platform || 'android',
|
|
793
797
|
}, request.deviceId);
|
|
794
798
|
}
|
|
795
799
|
catch (e2) {
|
|
796
|
-
logger.error(`[AiAct] sendFinal
|
|
800
|
+
logger.error(`[AiAct] sendFinal retry also failed: ${e2.message}`);
|
|
797
801
|
}
|
|
798
802
|
}
|
|
799
803
|
// 不论上面成败,debug_completed 必须发
|
|
@@ -802,23 +806,24 @@ export class DebugWebSocketServer {
|
|
|
802
806
|
type: 'debug_completed',
|
|
803
807
|
sessionId,
|
|
804
808
|
success: payload.success,
|
|
809
|
+
reportUrl,
|
|
805
810
|
}, request.deviceId);
|
|
806
|
-
logger.info(`[AiAct] Sent ai_act_result + debug_completed for sessionId=${sessionId}, success=${payload.success}`);
|
|
811
|
+
logger.info(`[AiAct] Sent ai_act_result + debug_completed for sessionId=${sessionId}, success=${payload.success}, reportUrl=${reportUrl || 'null'}`);
|
|
807
812
|
}
|
|
808
813
|
catch (e3) {
|
|
809
814
|
logger.error(`[AiAct] Failed to send debug_completed: ${e3.message}`);
|
|
810
815
|
}
|
|
811
816
|
};
|
|
812
|
-
// 安全提取
|
|
813
|
-
const
|
|
817
|
+
// 安全提取 reportFile 路径,单独 try/catch,绝不向上抛
|
|
818
|
+
const safeExtractReportFile = () => {
|
|
814
819
|
try {
|
|
815
820
|
const agent = session.executor?.getAgent?.() || session.agent;
|
|
816
|
-
if (agent && typeof agent.
|
|
817
|
-
return agent.
|
|
821
|
+
if (agent && typeof agent.reportFile === 'string') {
|
|
822
|
+
return agent.reportFile || '';
|
|
818
823
|
}
|
|
819
824
|
}
|
|
820
825
|
catch (e) {
|
|
821
|
-
logger.warn(`[AiAct]
|
|
826
|
+
logger.warn(`[AiAct] safeExtractReportFile failed: ${e.message}`);
|
|
822
827
|
}
|
|
823
828
|
return '';
|
|
824
829
|
};
|
|
@@ -868,10 +873,12 @@ export class DebugWebSocketServer {
|
|
|
868
873
|
const status = result.success ? 'completed' : 'failed';
|
|
869
874
|
sessionManager.updateStatus(sessionId, status);
|
|
870
875
|
logger.info(`[AiAct] Execution finished: sessionId=${sessionId}, status=${status}, ws.readyState=${ws.readyState}`);
|
|
876
|
+
const reportFile = result.reportFile || safeExtractReportFile();
|
|
877
|
+
const reportUrl = await this.safeUploadReport(reportFile, sessionId);
|
|
871
878
|
sendFinal({
|
|
872
879
|
success: result.success,
|
|
873
880
|
result: result.result,
|
|
874
|
-
|
|
881
|
+
reportUrl,
|
|
875
882
|
error: result.errorMessage,
|
|
876
883
|
});
|
|
877
884
|
}
|
|
@@ -879,20 +886,30 @@ export class DebugWebSocketServer {
|
|
|
879
886
|
session.executor = undefined;
|
|
880
887
|
sessionManager.updateStatus(sessionId, 'failed');
|
|
881
888
|
logger.error(`[AiAct] Execution error: sessionId=${sessionId}, error=${error.message}, ws.readyState=${ws.readyState}`);
|
|
889
|
+
const reportFile = safeExtractReportFile();
|
|
890
|
+
const reportUrl = await this.safeUploadReport(reportFile, sessionId);
|
|
882
891
|
sendFinal({
|
|
883
892
|
success: false,
|
|
884
893
|
error: error.message,
|
|
885
|
-
|
|
894
|
+
reportUrl,
|
|
886
895
|
});
|
|
887
896
|
}
|
|
888
897
|
finally {
|
|
889
898
|
// 兜底:上面任何分支因任何原因没发结束消息,这里最后补一刀
|
|
890
899
|
if (!finalSent) {
|
|
891
900
|
logger.warn(`[AiAct] finally fallback: no final message sent yet for sessionId=${sessionId}, sending now`);
|
|
901
|
+
const reportFile = safeExtractReportFile();
|
|
902
|
+
let reportUrl = null;
|
|
903
|
+
try {
|
|
904
|
+
reportUrl = await this.safeUploadReport(reportFile, sessionId);
|
|
905
|
+
}
|
|
906
|
+
catch (e) {
|
|
907
|
+
logger.error(`[AiAct] finally fallback upload error: ${e.message}`);
|
|
908
|
+
}
|
|
892
909
|
sendFinal({
|
|
893
910
|
success: false,
|
|
894
911
|
error: 'aiAct ended without explicit result (fallback in finally)',
|
|
895
|
-
|
|
912
|
+
reportUrl,
|
|
896
913
|
});
|
|
897
914
|
}
|
|
898
915
|
}
|
|
@@ -1202,31 +1219,24 @@ export class DebugWebSocketServer {
|
|
|
1202
1219
|
default:
|
|
1203
1220
|
throw new Error(`Unknown web action type: ${request.actionType}`);
|
|
1204
1221
|
}
|
|
1205
|
-
//
|
|
1206
|
-
let
|
|
1207
|
-
let reportHTML = '';
|
|
1222
|
+
// 拉取报告文件路径
|
|
1223
|
+
let reportFile = '';
|
|
1208
1224
|
try {
|
|
1209
|
-
if (typeof agent.
|
|
1210
|
-
|
|
1211
|
-
}
|
|
1212
|
-
if (typeof agent.reportHTMLString === 'function') {
|
|
1213
|
-
reportHTML = agent.reportHTMLString({ inlineScreenshots: true });
|
|
1225
|
+
if (typeof agent.reportFile === 'string') {
|
|
1226
|
+
reportFile = agent.reportFile || '';
|
|
1214
1227
|
}
|
|
1215
1228
|
}
|
|
1216
1229
|
catch (e) {
|
|
1217
|
-
logger.warn(`[WebAction]
|
|
1230
|
+
logger.warn(`[WebAction] reportFile extract failed: ${e.message}`);
|
|
1218
1231
|
}
|
|
1219
1232
|
// runCode 执行中有错误(如断言失败),标记失败但正常返回报告
|
|
1220
1233
|
const runCodeError = request.__runCodeError;
|
|
1221
1234
|
const actionSuccess = !runCodeError;
|
|
1222
1235
|
sessionManager.updateStatus(sessionId, actionSuccess ? 'completed' : 'failed');
|
|
1223
1236
|
stopWebDumpInterval();
|
|
1224
|
-
//
|
|
1225
|
-
const reportUrl =
|
|
1226
|
-
|
|
1227
|
-
// 调试场景不再上传报告到服务端:reportHTML 直接通过长链接回前端
|
|
1228
|
-
logger.info(`[WebAction] Skip server upload (debug mode), reportHTML size=${reportHTML.length}`);
|
|
1229
|
-
}
|
|
1237
|
+
// 上传报告文件到服务端,拿到 reportUrl
|
|
1238
|
+
const reportUrl = await this.safeUploadReport(reportFile, sessionId);
|
|
1239
|
+
logger.info(`[WebAction] report uploaded: reportUrl=${reportUrl || 'null'}, reportFile=${reportFile}`);
|
|
1230
1240
|
// 发送 web_action_result
|
|
1231
1241
|
this.sendMessage(ws, {
|
|
1232
1242
|
type: 'web_action_result',
|
|
@@ -1234,8 +1244,7 @@ export class DebugWebSocketServer {
|
|
|
1234
1244
|
actionType: request.actionType,
|
|
1235
1245
|
success: actionSuccess,
|
|
1236
1246
|
result,
|
|
1237
|
-
dump:
|
|
1238
|
-
reportHTML: reportHTML || '',
|
|
1247
|
+
dump: '',
|
|
1239
1248
|
reportUrl,
|
|
1240
1249
|
error: runCodeError || undefined,
|
|
1241
1250
|
});
|
|
@@ -1246,8 +1255,7 @@ export class DebugWebSocketServer {
|
|
|
1246
1255
|
actionType: request.actionType,
|
|
1247
1256
|
success: actionSuccess,
|
|
1248
1257
|
result,
|
|
1249
|
-
dump:
|
|
1250
|
-
reportHTML: reportHTML || '',
|
|
1258
|
+
dump: '',
|
|
1251
1259
|
reportUrl,
|
|
1252
1260
|
error: runCodeError || undefined,
|
|
1253
1261
|
platform: 'web',
|
|
@@ -1269,41 +1277,28 @@ export class DebugWebSocketServer {
|
|
|
1269
1277
|
clearInterval(session.dumpIntervalId);
|
|
1270
1278
|
session.dumpIntervalId = undefined;
|
|
1271
1279
|
}
|
|
1272
|
-
// 失败时也尝试提取
|
|
1273
|
-
let
|
|
1274
|
-
let reportHTML = '';
|
|
1280
|
+
// 失败时也尝试提取 reportFile 并上传
|
|
1281
|
+
let reportFile = '';
|
|
1275
1282
|
const agentForReport = session?.webAgent;
|
|
1276
1283
|
try {
|
|
1277
|
-
if (agentForReport && typeof agentForReport.
|
|
1278
|
-
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
catch (dumpErr) {
|
|
1282
|
-
logger.warn(`[WebAction] (error case) dump extract failed: ${dumpErr.message}`);
|
|
1283
|
-
}
|
|
1284
|
-
try {
|
|
1285
|
-
if (agentForReport && typeof agentForReport.reportHTMLString === 'function') {
|
|
1286
|
-
reportHTML = agentForReport.reportHTMLString({ inlineScreenshots: true });
|
|
1284
|
+
if (agentForReport && typeof agentForReport.reportFile === 'string') {
|
|
1285
|
+
reportFile = agentForReport.reportFile || '';
|
|
1287
1286
|
}
|
|
1288
1287
|
}
|
|
1289
1288
|
catch (reportErr) {
|
|
1290
|
-
logger.warn(`[WebAction] (error case)
|
|
1291
|
-
}
|
|
1292
|
-
// 异常路径:调试场景不再上传报告到服务端
|
|
1293
|
-
const reportUrl = null;
|
|
1294
|
-
if (reportHTML && reportHTML.length > 0) {
|
|
1295
|
-
logger.info(`[WebAction] (error case) Skip server upload (debug mode), reportHTML size=${reportHTML.length}`);
|
|
1289
|
+
logger.warn(`[WebAction] (error case) reportFile extract failed: ${reportErr.message}`);
|
|
1296
1290
|
}
|
|
1291
|
+
// 上传报告文件到服务端,拿到 reportUrl
|
|
1292
|
+
const reportUrl = await this.safeUploadReport(reportFile, sessionId);
|
|
1293
|
+
logger.info(`[WebAction] (error case) report uploaded: reportUrl=${reportUrl || 'null'}, reportFile=${reportFile}`);
|
|
1297
1294
|
this.sendMessage(ws, {
|
|
1298
1295
|
type: 'web_action_result',
|
|
1299
1296
|
sessionId,
|
|
1300
1297
|
actionType: request.actionType,
|
|
1301
1298
|
success: false,
|
|
1302
1299
|
error: error.message,
|
|
1303
|
-
dump:
|
|
1304
|
-
reportHTML: reportHTML || '',
|
|
1300
|
+
dump: '',
|
|
1305
1301
|
reportUrl,
|
|
1306
|
-
agentStatus: session?.webAgent ? 'initialized' : 'not_initialized',
|
|
1307
1302
|
});
|
|
1308
1303
|
// 与移动端对齐:同步发一份 action_result
|
|
1309
1304
|
this.sendMessage(ws, {
|
|
@@ -1312,10 +1307,8 @@ export class DebugWebSocketServer {
|
|
|
1312
1307
|
actionType: request.actionType,
|
|
1313
1308
|
success: false,
|
|
1314
1309
|
error: error.message,
|
|
1315
|
-
dump:
|
|
1316
|
-
reportHTML: reportHTML || '',
|
|
1310
|
+
dump: '',
|
|
1317
1311
|
reportUrl,
|
|
1318
|
-
agentStatus: session?.webAgent ? 'initialized' : 'not_initialized',
|
|
1319
1312
|
platform: 'web',
|
|
1320
1313
|
});
|
|
1321
1314
|
// 必须发 debug_completed,前端依赖它退出执行中状态
|
|
@@ -1444,26 +1437,16 @@ export class DebugWebSocketServer {
|
|
|
1444
1437
|
return;
|
|
1445
1438
|
}
|
|
1446
1439
|
// 仿照 playground cancelTask:先获取当前执行数据,再 destroy agent
|
|
1447
|
-
let
|
|
1448
|
-
let reportHTML = '';
|
|
1440
|
+
let reportFile = '';
|
|
1449
1441
|
const agent = session.executor?.getAgent?.() || session.agent;
|
|
1450
|
-
// 获取
|
|
1442
|
+
// 获取 reportFile 路径
|
|
1451
1443
|
try {
|
|
1452
|
-
if (agent && typeof agent.
|
|
1453
|
-
|
|
1444
|
+
if (agent && typeof agent.reportFile === 'string') {
|
|
1445
|
+
reportFile = agent.reportFile || '';
|
|
1454
1446
|
}
|
|
1455
1447
|
}
|
|
1456
1448
|
catch (error) {
|
|
1457
|
-
logger.warn(`Failed to get
|
|
1458
|
-
}
|
|
1459
|
-
// 获取 reportHTML
|
|
1460
|
-
try {
|
|
1461
|
-
if (agent && typeof agent.reportHTMLString === 'function') {
|
|
1462
|
-
reportHTML = agent.reportHTMLString({ inlineScreenshots: true }) || '';
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
catch (error) {
|
|
1466
|
-
logger.warn(`Failed to get reportHTML before cancel: ${error.message}`);
|
|
1449
|
+
logger.warn(`Failed to get reportFile before cancel: ${error.message}`);
|
|
1467
1450
|
}
|
|
1468
1451
|
// 中断主进程内的执行(execute_action / execute_ai_act)
|
|
1469
1452
|
if (session.abortController && !session.abortController.signal.aborted) {
|
|
@@ -1506,12 +1489,14 @@ export class DebugWebSocketServer {
|
|
|
1506
1489
|
// 关闭 web 浏览器(如果存在)
|
|
1507
1490
|
await this.closeWebSession(request.sessionId);
|
|
1508
1491
|
sessionManager.updateStatus(request.sessionId, 'stopped');
|
|
1509
|
-
//
|
|
1492
|
+
// 上传报告文件到服务端,拿到 reportUrl
|
|
1493
|
+
const reportUrl = await this.safeUploadReport(reportFile, request.sessionId);
|
|
1494
|
+
logger.info(`[StopDebug] report uploaded: reportUrl=${reportUrl || 'null'}, reportFile=${reportFile}`);
|
|
1495
|
+
// 发送带执行数据的停止消息
|
|
1510
1496
|
this.sendMessage(ws, {
|
|
1511
1497
|
type: 'debug_stopped',
|
|
1512
1498
|
sessionId: request.sessionId,
|
|
1513
|
-
|
|
1514
|
-
reportHTML,
|
|
1499
|
+
reportUrl,
|
|
1515
1500
|
});
|
|
1516
1501
|
// 同时发送 action_result,让前端能获取到停止时的执行数据
|
|
1517
1502
|
this.sendMessage(ws, {
|
|
@@ -1519,14 +1504,15 @@ export class DebugWebSocketServer {
|
|
|
1519
1504
|
sessionId: request.sessionId,
|
|
1520
1505
|
success: false,
|
|
1521
1506
|
error: 'Action stopped by user',
|
|
1522
|
-
dump,
|
|
1523
|
-
|
|
1507
|
+
dump: '',
|
|
1508
|
+
reportUrl,
|
|
1524
1509
|
});
|
|
1525
1510
|
// 发送 debug_completed
|
|
1526
1511
|
this.sendMessage(ws, {
|
|
1527
1512
|
type: 'debug_completed',
|
|
1528
1513
|
sessionId: request.sessionId,
|
|
1529
1514
|
success: false,
|
|
1515
|
+
reportUrl,
|
|
1530
1516
|
});
|
|
1531
1517
|
}
|
|
1532
1518
|
// ==================== Get Logs ====================
|
|
@@ -1539,6 +1525,39 @@ export class DebugWebSocketServer {
|
|
|
1539
1525
|
this.sendMessage(ws, { type: 'all_logs', sessionId: request.sessionId, logs: session.logs });
|
|
1540
1526
|
}
|
|
1541
1527
|
// ==================== Utilities ====================
|
|
1528
|
+
/**
|
|
1529
|
+
* 安全上传报告到服务端,返回 reportUrl;任何内部异常都 catch 后返回 null,绝不抛
|
|
1530
|
+
*/
|
|
1531
|
+
async safeUploadReport(reportFile, sessionId) {
|
|
1532
|
+
if (!reportFile)
|
|
1533
|
+
return null;
|
|
1534
|
+
const uploadUrl = this.config?.callback?.debugReportUploadUrl;
|
|
1535
|
+
if (!uploadUrl) {
|
|
1536
|
+
logger.warn(`[UploadReport] debugReportUploadUrl not configured, skip upload (sessionId=${sessionId})`);
|
|
1537
|
+
return null;
|
|
1538
|
+
}
|
|
1539
|
+
const maxAttempts = 2;
|
|
1540
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1541
|
+
try {
|
|
1542
|
+
const url = await uploadReportToServer(reportFile, sessionId, uploadUrl);
|
|
1543
|
+
if (url)
|
|
1544
|
+
return url;
|
|
1545
|
+
// uploadReportToServer 返回 null 说明响应缺少 reportUrl,也算失败,重试一次
|
|
1546
|
+
if (attempt < maxAttempts) {
|
|
1547
|
+
logger.warn(`[UploadReport] Upload returned no reportUrl (attempt ${attempt}/${maxAttempts}), retrying...`);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
catch (e) {
|
|
1551
|
+
if (attempt < maxAttempts) {
|
|
1552
|
+
logger.warn(`[UploadReport] Upload failed (attempt ${attempt}/${maxAttempts}): ${e.message}, retrying...`);
|
|
1553
|
+
}
|
|
1554
|
+
else {
|
|
1555
|
+
logger.error(`[UploadReport] Upload failed after ${maxAttempts} attempts: ${e.message}`);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
return null;
|
|
1560
|
+
}
|
|
1542
1561
|
/**
|
|
1543
1562
|
* 将前端传来的 modelConfig 应用到环境变量
|
|
1544
1563
|
* ActionExecutor 通过环境变量读取模型配置,所以需要同步更新
|
|
@@ -1572,22 +1591,34 @@ export class DebugWebSocketServer {
|
|
|
1572
1591
|
timestamp: new Date().toISOString(),
|
|
1573
1592
|
};
|
|
1574
1593
|
if (ws.readyState === ws.OPEN) {
|
|
1575
|
-
|
|
1576
|
-
//
|
|
1594
|
+
let payload = JSON.stringify(msg);
|
|
1595
|
+
// WSS 自身 maxPayload=10MB(接收端),发送端我们保留 2MB 余量以兼容反代/网关
|
|
1577
1596
|
const MAX_PAYLOAD_SIZE = 2 * 1024 * 1024; // 2MB
|
|
1578
1597
|
if (payload.length > MAX_PAYLOAD_SIZE) {
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
if (msg.reportHTML && typeof msg.reportHTML === 'string' && msg.reportHTML.length > 512 * 1024) {
|
|
1582
|
-
msg.reportHTML = msg.reportHTML.substring(0, 512 * 1024) + '\n...[truncated]';
|
|
1583
|
-
}
|
|
1598
|
+
const originalSize = payload.length;
|
|
1599
|
+
let truncated = false;
|
|
1584
1600
|
if (msg.dump && typeof msg.dump === 'string' && msg.dump.length > 512 * 1024) {
|
|
1585
1601
|
msg.dump = msg.dump.substring(0, 512 * 1024) + '\n...[truncated]';
|
|
1602
|
+
truncated = true;
|
|
1603
|
+
}
|
|
1604
|
+
// 关键修复:截断后必须重新序列化,否则 ws.send 发的还是旧的大 payload
|
|
1605
|
+
if (truncated) {
|
|
1606
|
+
payload = JSON.stringify(msg);
|
|
1607
|
+
}
|
|
1608
|
+
if (payload.length > MAX_PAYLOAD_SIZE) {
|
|
1609
|
+
if (msg.dump)
|
|
1610
|
+
msg.dump = '';
|
|
1611
|
+
const prevError = typeof msg.error === 'string' ? msg.error : '';
|
|
1612
|
+
msg.error = (prevError ? prevError + ' | ' : '') + `report dropped (oversize: ${(originalSize / 1024 / 1024).toFixed(2)}MB > ${(MAX_PAYLOAD_SIZE / 1024 / 1024)}MB)`;
|
|
1613
|
+
msg.reportOversize = true;
|
|
1614
|
+
msg.reportOversizeBytes = originalSize;
|
|
1615
|
+
payload = JSON.stringify(msg);
|
|
1586
1616
|
}
|
|
1617
|
+
logger.warn(`[WS] Message too large (orig=${(originalSize / 1024 / 1024).toFixed(2)}MB, sent=${(payload.length / 1024 / 1024).toFixed(2)}MB), type=${message.type}, sessionId=${message.sessionId}`);
|
|
1587
1618
|
}
|
|
1588
1619
|
ws.send(payload, (err) => {
|
|
1589
1620
|
if (err) {
|
|
1590
|
-
logger.error(`[WS] Send failed: type=${message.type}, error=${err.message}`);
|
|
1621
|
+
logger.error(`[WS] Send failed: type=${message.type}, sessionId=${message.sessionId}, error=${err.message}`);
|
|
1591
1622
|
}
|
|
1592
1623
|
});
|
|
1593
1624
|
}
|