@aiscene/aiserver 1.6.6 → 1.6.8
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/api/callback.d.ts +1 -1
- package/dist/api/callback.d.ts.map +1 -1
- package/dist/api/callback.js +168 -100
- package/dist/api/callback.js.map +1 -1
- package/dist/config/index.js +3 -3
- package/dist/config/index.js.map +1 -1
- package/dist/core/types.d.ts +2 -6
- package/dist/core/types.d.ts.map +1 -1
- package/dist/debug/dump-manager.d.ts.map +1 -1
- package/dist/debug/dump-manager.js +4 -1
- package/dist/debug/dump-manager.js.map +1 -1
- package/dist/debug/session-manager.d.ts +1 -0
- package/dist/debug/session-manager.d.ts.map +1 -1
- package/dist/debug/session-manager.js +4 -0
- package/dist/debug/session-manager.js.map +1 -1
- package/dist/debug/types.d.ts +0 -21
- package/dist/debug/types.d.ts.map +1 -1
- package/dist/debug/websocket-server.d.ts +0 -1
- package/dist/debug/websocket-server.d.ts.map +1 -1
- package/dist/debug/websocket-server.js +271 -190
- package/dist/debug/websocket-server.js.map +1 -1
- package/dist/executor/action-executor.d.ts +14 -0
- package/dist/executor/action-executor.d.ts.map +1 -1
- package/dist/executor/action-executor.js +77 -8
- package/dist/executor/action-executor.js.map +1 -1
- package/dist/executor/android-executor.d.ts +1 -0
- package/dist/executor/android-executor.d.ts.map +1 -1
- package/dist/executor/android-executor.js +26 -9
- 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 +3 -1
- 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 +3 -1
- package/dist/executor/ios-executor.js.map +1 -1
- package/dist/executor/worker-entry.js +32 -3
- package/dist/executor/worker-entry.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/proxy/whistle-manager.d.ts +0 -8
- package/dist/proxy/whistle-manager.d.ts.map +1 -1
- package/dist/proxy/whistle-manager.js +0 -76
- package/dist/proxy/whistle-manager.js.map +1 -1
- package/dist/report/report-package.d.ts +7 -0
- package/dist/report/report-package.d.ts.map +1 -0
- package/dist/report/report-package.js +46 -0
- package/dist/report/report-package.js.map +1 -0
- package/dist/task/poller.d.ts +1 -0
- package/dist/task/poller.d.ts.map +1 -1
- package/dist/task/poller.js +14 -0
- package/dist/task/poller.js.map +1 -1
- package/dist/task/scheduler.d.ts +5 -1
- package/dist/task/scheduler.d.ts.map +1 -1
- package/dist/task/scheduler.js +82 -45
- package/dist/task/scheduler.js.map +1 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +27 -0
- package/dist/web/server.js.map +1 -1
- package/package.json +2 -1
|
@@ -229,6 +229,8 @@ export class DebugWebSocketServer {
|
|
|
229
229
|
}
|
|
230
230
|
handleControlConnection(ws) {
|
|
231
231
|
logger.info('[Control] New connection');
|
|
232
|
+
// 连接建立后发送 proxy_connected 确认,供客户端(android_device_run.js)确认握手
|
|
233
|
+
this.sendMessage(ws, { type: 'proxy_connected', timestamp: new Date().toISOString() });
|
|
232
234
|
// 心跳:收到 pong 标记为存活
|
|
233
235
|
ws.isAlive = true;
|
|
234
236
|
ws.on('pong', () => {
|
|
@@ -254,7 +256,9 @@ export class DebugWebSocketServer {
|
|
|
254
256
|
logger.info(`[WebSession] Client disconnected, keep browser alive for reuse: ${sessionId}`);
|
|
255
257
|
continue;
|
|
256
258
|
}
|
|
257
|
-
this.cleanupSession(sessionId)
|
|
259
|
+
this.cleanupSession(sessionId).catch((error) => {
|
|
260
|
+
logger.warn(`[Control] Session cleanup failed: sessionId=${sessionId}, error=${error.message}`);
|
|
261
|
+
});
|
|
258
262
|
}
|
|
259
263
|
// 清理任务日志订阅
|
|
260
264
|
this.cleanupClientSubscriptions(ws);
|
|
@@ -498,6 +502,7 @@ export class DebugWebSocketServer {
|
|
|
498
502
|
session.webDeviceName = request.mobileMode ? (request.deviceName || 'iPhone 13') : undefined;
|
|
499
503
|
// modelConfig 走构造参数(与 handleExecuteWebAction 一致,避免多会话相互覆盖 env)
|
|
500
504
|
session.webAgent = new midscene.PlaywrightAgent(page, {
|
|
505
|
+
outputFormat: 'html-and-external-assets',
|
|
501
506
|
modelConfig: this.applyModelConfig(request.modelConfig),
|
|
502
507
|
});
|
|
503
508
|
// 注册并发池 + 启动 TTL 计时
|
|
@@ -665,9 +670,7 @@ export class DebugWebSocketServer {
|
|
|
665
670
|
mobileMode: request.mobileMode,
|
|
666
671
|
deviceName: request.deviceName,
|
|
667
672
|
// 环境代理配置
|
|
668
|
-
|
|
669
|
-
proxyPort: request.proxyPort || (request.hostMappings && request.hostMappings.length > 0 ? 8899 : undefined),
|
|
670
|
-
hostMappings: request.hostMappings,
|
|
673
|
+
proxyPort: request.proxyPort || 8899,
|
|
671
674
|
// 公司代理平台账号
|
|
672
675
|
proxyAccount: request.proxyAccount,
|
|
673
676
|
// 添加登录配置
|
|
@@ -682,38 +685,13 @@ export class DebugWebSocketServer {
|
|
|
682
685
|
agreementSelector: request.loginConfig.agreementSelector,
|
|
683
686
|
}),
|
|
684
687
|
};
|
|
685
|
-
//
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
if (success) {
|
|
693
|
-
const sess = sessionManager.get(sessionId);
|
|
694
|
-
if (sess) {
|
|
695
|
-
sess.whistleRuleGroupName = ruleGroupName;
|
|
696
|
-
sess.whistleApiBase = whistleApiBase;
|
|
697
|
-
}
|
|
698
|
-
if (!config.proxyAccount && !config.proxyPort)
|
|
699
|
-
config.proxyPort = 8899;
|
|
700
|
-
logger.info('[Debug] Whistle proxy setup: ruleGroup=' + ruleGroupName + ', proxyPort=' + config.proxyPort + ', proxyAccount=' + (config.proxyAccount || 'N/A'));
|
|
701
|
-
// 开始请求抓包(传入 proxyAccount,自动选择正确的 Whistle 实例)
|
|
702
|
-
try {
|
|
703
|
-
await whistleManager.startCapture(sessionId, undefined, 3000, config.proxyAccount);
|
|
704
|
-
logger.info('[Debug] Whistle request capture started: sessionId=' + sessionId);
|
|
705
|
-
}
|
|
706
|
-
catch (captureErr) {
|
|
707
|
-
logger.warn('[Debug] Failed to start request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
else {
|
|
711
|
-
logger.warn('[Debug] Whistle proxy setup failed, continuing without proxy');
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
catch (err) {
|
|
715
|
-
logger.warn('[Debug] Whistle proxy setup error: ' + (err instanceof Error ? err.message : String(err)));
|
|
716
|
-
}
|
|
688
|
+
// 注意:proxy-service.jd.com 的规则由前端(浏览器端)设置,后端只负责设备代理 + 抓包
|
|
689
|
+
try {
|
|
690
|
+
await whistleManager.startCapture(sessionId, undefined, 3000, config.proxyAccount);
|
|
691
|
+
logger.info('[Debug] Whistle request capture started: sessionId=' + sessionId);
|
|
692
|
+
}
|
|
693
|
+
catch (captureErr) {
|
|
694
|
+
logger.warn('[Debug] Failed to start request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
717
695
|
}
|
|
718
696
|
// 使用 AbortSignal 监听停止请求
|
|
719
697
|
const executePromise = executor.execute(config);
|
|
@@ -737,7 +715,15 @@ export class DebugWebSocketServer {
|
|
|
737
715
|
logger.info(`[executeRunCodeInProcess] Execution aborted by user, skipping result messages (handleStopDebug will send them)`);
|
|
738
716
|
return;
|
|
739
717
|
}
|
|
740
|
-
// 清理 executor
|
|
718
|
+
// 清理 executor(释放 device/agent 资源 + 清除设备代理)
|
|
719
|
+
if (executor && typeof executor.destroy === 'function') {
|
|
720
|
+
try {
|
|
721
|
+
await executor.destroy();
|
|
722
|
+
}
|
|
723
|
+
catch (destroyErr) {
|
|
724
|
+
logger.warn(`[executeRunCodeInProcess] Executor destroy error: ${destroyErr.message}`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
741
727
|
if (session)
|
|
742
728
|
session.executor = undefined;
|
|
743
729
|
// 停止请求抓包,采集调试期间的所有请求
|
|
@@ -763,19 +749,6 @@ export class DebugWebSocketServer {
|
|
|
763
749
|
catch (captureErr) {
|
|
764
750
|
logger.warn('[Debug] Failed to stop request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
765
751
|
}
|
|
766
|
-
// Teardown Whistle proxy rules if they were set
|
|
767
|
-
const currentSess = sessionManager.get(sessionId);
|
|
768
|
-
const whistleRuleName = currentSess ? currentSess.whistleRuleGroupName : undefined;
|
|
769
|
-
if (whistleRuleName) {
|
|
770
|
-
whistleManager.removeRuleGroup(whistleRuleName, whistleApiBase).catch((err) => {
|
|
771
|
-
logger.warn('[Debug] Failed to teardown Whistle proxy: ' + (err instanceof Error ? err.message : String(err)));
|
|
772
|
-
});
|
|
773
|
-
if (currentSess) {
|
|
774
|
-
currentSess.whistleRuleGroupName = undefined;
|
|
775
|
-
currentSess.whistleApiBase = undefined;
|
|
776
|
-
}
|
|
777
|
-
logger.info('[Debug] Whistle proxy teardown: ruleGroup=' + whistleRuleName);
|
|
778
|
-
}
|
|
779
752
|
sessionManager.updateStatus(sessionId, result.success ? 'completed' : 'failed');
|
|
780
753
|
// 调试:打印 result 内容
|
|
781
754
|
logger.info(`[executeRunCodeInProcess] result: success=${result.success}, reportFile=${result.reportFile || ''}, errorMessage=${result.errorMessage}`);
|
|
@@ -801,7 +774,6 @@ export class DebugWebSocketServer {
|
|
|
801
774
|
sessionId,
|
|
802
775
|
success: result.success,
|
|
803
776
|
reportUrl,
|
|
804
|
-
capturedRequests: capturedRequests.length > 0 ? this.simplifyCapturedRequests(capturedRequests) : undefined,
|
|
805
777
|
captureDataUrl,
|
|
806
778
|
}, request.deviceId);
|
|
807
779
|
}
|
|
@@ -824,9 +796,7 @@ export class DebugWebSocketServer {
|
|
|
824
796
|
nodeId: this.config.task.nodeId,
|
|
825
797
|
modelConfig: fullModelConfig,
|
|
826
798
|
// 环境代理配置
|
|
827
|
-
|
|
828
|
-
proxyPort: request.proxyPort || (request.hostMappings && request.hostMappings.length > 0 ? 8899 : undefined),
|
|
829
|
-
hostMappings: request.hostMappings,
|
|
799
|
+
proxyPort: request.proxyPort || 8899,
|
|
830
800
|
// 公司代理平台账号
|
|
831
801
|
proxyAccount: request.proxyAccount,
|
|
832
802
|
// 添加登录配置
|
|
@@ -841,38 +811,14 @@ export class DebugWebSocketServer {
|
|
|
841
811
|
agreementSelector: request.loginConfig.agreementSelector,
|
|
842
812
|
}),
|
|
843
813
|
};
|
|
844
|
-
//
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
const sess = sessionManager.get(sessionId);
|
|
853
|
-
if (sess) {
|
|
854
|
-
sess.whistleRuleGroupName = ruleGroupName;
|
|
855
|
-
sess.whistleApiBase = workerWhistleApiBase;
|
|
856
|
-
}
|
|
857
|
-
if (!execConfig.proxyAccount && !execConfig.proxyPort)
|
|
858
|
-
execConfig.proxyPort = 8899;
|
|
859
|
-
logger.info('[Debug-Worker] Whistle proxy setup: ruleGroup=' + ruleGroupName + ', proxyPort=' + execConfig.proxyPort + ', proxyAccount=' + (execConfig.proxyAccount || 'N/A'));
|
|
860
|
-
// 开始请求抓包(传入 proxyAccount,自动选择正确的 Whistle 实例)
|
|
861
|
-
try {
|
|
862
|
-
await whistleManager.startCapture(sessionId, undefined, 3000, execConfig.proxyAccount);
|
|
863
|
-
logger.info('[Debug-Worker] Whistle request capture started: sessionId=' + sessionId);
|
|
864
|
-
}
|
|
865
|
-
catch (captureErr) {
|
|
866
|
-
logger.warn('[Debug-Worker] Failed to start request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
else {
|
|
870
|
-
logger.warn('[Debug-Worker] Whistle proxy setup failed, continuing without proxy');
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
catch (err) {
|
|
874
|
-
logger.warn('[Debug-Worker] Whistle proxy setup error: ' + (err instanceof Error ? err.message : String(err)));
|
|
875
|
-
}
|
|
814
|
+
// 注意:proxy-service.jd.com 的规则由前端(浏览器端)设置,后端只负责设备代理 + 抓包
|
|
815
|
+
// 始终启动请求抓包
|
|
816
|
+
try {
|
|
817
|
+
await whistleManager.startCapture(sessionId, undefined, 3000, execConfig.proxyAccount);
|
|
818
|
+
logger.info('[Debug-Worker] Whistle request capture started: sessionId=' + sessionId);
|
|
819
|
+
}
|
|
820
|
+
catch (captureErr) {
|
|
821
|
+
logger.warn('[Debug-Worker] Failed to start request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
876
822
|
}
|
|
877
823
|
// Fork worker process for long-running debug
|
|
878
824
|
const workerPath = new URL('../executor/worker-entry.js', import.meta.url).pathname;
|
|
@@ -931,22 +877,6 @@ export class DebugWebSocketServer {
|
|
|
931
877
|
catch (captureErr) {
|
|
932
878
|
logger.warn('[Debug-Worker] Failed to stop request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
933
879
|
}
|
|
934
|
-
// Teardown Whistle proxy rules if they were set
|
|
935
|
-
const curSess = sessionManager.get(sessionId);
|
|
936
|
-
const wRuleName = curSess ? curSess.whistleRuleGroupName : undefined;
|
|
937
|
-
if (wRuleName) {
|
|
938
|
-
try {
|
|
939
|
-
await whistleManager.removeRuleGroup(wRuleName, workerWhistleApiBase);
|
|
940
|
-
if (curSess) {
|
|
941
|
-
curSess.whistleRuleGroupName = undefined;
|
|
942
|
-
curSess.whistleApiBase = undefined;
|
|
943
|
-
}
|
|
944
|
-
logger.info('[Debug-Worker] Whistle proxy teardown: ruleGroup=' + wRuleName);
|
|
945
|
-
}
|
|
946
|
-
catch (err) {
|
|
947
|
-
logger.warn('[Debug-Worker] Failed to teardown Whistle proxy: ' + (err instanceof Error ? err.message : String(err)));
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
880
|
sessionManager.updateStatus(sessionId, success ? 'completed' : 'failed');
|
|
951
881
|
const reportFile = workerResult.reportFile || '';
|
|
952
882
|
const reportUrl = await this.safeUploadReport(reportFile, sessionId);
|
|
@@ -972,7 +902,6 @@ export class DebugWebSocketServer {
|
|
|
972
902
|
dump: '',
|
|
973
903
|
reportUrl,
|
|
974
904
|
error: workerResult.errorMessage,
|
|
975
|
-
capturedRequests: workerCapturedRequests.length > 0 ? this.simplifyCapturedRequests(workerCapturedRequests) : undefined,
|
|
976
905
|
captureDataUrl: workerCaptureDataUrl,
|
|
977
906
|
}, request.deviceId);
|
|
978
907
|
};
|
|
@@ -1058,12 +987,19 @@ export class DebugWebSocketServer {
|
|
|
1058
987
|
executionId: sessionId,
|
|
1059
988
|
skipAppRestart: request.skipAppRestart,
|
|
1060
989
|
// 环境代理配置
|
|
1061
|
-
environmentId: request.environmentId,
|
|
1062
990
|
proxyPort: request.proxyPort,
|
|
1063
|
-
hostMappings: request.hostMappings,
|
|
1064
991
|
// 公司代理平台账号
|
|
1065
992
|
proxyAccount: request.proxyAccount,
|
|
1066
993
|
};
|
|
994
|
+
// 注意:proxy-service.jd.com 的规则由前端(浏览器端)设置,后端只负责设备代理 + 抓包
|
|
995
|
+
// 始终启动请求抓包
|
|
996
|
+
try {
|
|
997
|
+
await whistleManager.startCapture(sessionId, undefined, 3000, config.proxyAccount);
|
|
998
|
+
logger.info('[Action] Whistle request capture started: sessionId=' + sessionId);
|
|
999
|
+
}
|
|
1000
|
+
catch (captureErr) {
|
|
1001
|
+
logger.warn('[Action] Failed to start request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
1002
|
+
}
|
|
1067
1003
|
// 使用 AbortSignal 监听停止请求
|
|
1068
1004
|
const executePromise = executor.execute(config);
|
|
1069
1005
|
const abortPromise = new Promise((resolve) => {
|
|
@@ -1077,8 +1013,38 @@ export class DebugWebSocketServer {
|
|
|
1077
1013
|
logger.info(`[Action] Execution aborted by user, skipping result messages (handleStopDebug will send them)`);
|
|
1078
1014
|
return;
|
|
1079
1015
|
}
|
|
1080
|
-
// 清理 executor
|
|
1016
|
+
// 清理 executor(释放 device/agent 资源 + 清除设备代理)
|
|
1017
|
+
if (executor && typeof executor.destroy === 'function') {
|
|
1018
|
+
try {
|
|
1019
|
+
await executor.destroy();
|
|
1020
|
+
}
|
|
1021
|
+
catch (destroyErr) {
|
|
1022
|
+
logger.warn(`[Action] Executor destroy error: ${destroyErr.message}`);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1081
1025
|
session.executor = undefined;
|
|
1026
|
+
// 停止请求抓包,采集执行期间的所有请求
|
|
1027
|
+
let capturedRequests = [];
|
|
1028
|
+
let captureDataUrl;
|
|
1029
|
+
try {
|
|
1030
|
+
capturedRequests = await whistleManager.stopCapture(sessionId);
|
|
1031
|
+
if (capturedRequests.length > 0) {
|
|
1032
|
+
logger.info('[Action] Captured ' + capturedRequests.length + ' requests during session');
|
|
1033
|
+
try {
|
|
1034
|
+
const uploadResult = await this.saveAndUploadCaptureData(capturedRequests, sessionId);
|
|
1035
|
+
if (uploadResult) {
|
|
1036
|
+
captureDataUrl = uploadResult;
|
|
1037
|
+
logger.info('[Action] Capture data uploaded: ' + captureDataUrl);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
catch (uploadErr) {
|
|
1041
|
+
logger.warn('[Action] Failed to upload capture data: ' + (uploadErr instanceof Error ? uploadErr.message : String(uploadErr)));
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
catch (captureErr) {
|
|
1046
|
+
logger.warn('[Action] Failed to stop request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
1047
|
+
}
|
|
1082
1048
|
const status = result.success ? 'completed' : 'failed';
|
|
1083
1049
|
sessionManager.updateStatus(sessionId, status);
|
|
1084
1050
|
logger.info(`[Action] Execution finished: sessionId=${sessionId}, status=${status}, ws.readyState=${ws.readyState}`);
|
|
@@ -1093,19 +1059,49 @@ export class DebugWebSocketServer {
|
|
|
1093
1059
|
dump: '',
|
|
1094
1060
|
reportUrl,
|
|
1095
1061
|
error: result.errorMessage,
|
|
1062
|
+
captureDataUrl,
|
|
1096
1063
|
}, request.deviceId);
|
|
1097
1064
|
this.sendMessage(ws, {
|
|
1098
1065
|
type: 'debug_completed',
|
|
1099
1066
|
sessionId,
|
|
1100
1067
|
success: result.success,
|
|
1101
1068
|
reportUrl,
|
|
1069
|
+
captureDataUrl,
|
|
1102
1070
|
}, request.deviceId);
|
|
1103
1071
|
logger.info(`[Action] Sent action_result + debug_completed for sessionId=${sessionId}`);
|
|
1104
1072
|
}
|
|
1105
1073
|
catch (error) {
|
|
1074
|
+
// 清理 executor(释放 device/agent 资源 + 清除设备代理)
|
|
1075
|
+
if (session.executor && typeof session.executor.destroy === 'function') {
|
|
1076
|
+
try {
|
|
1077
|
+
await session.executor.destroy();
|
|
1078
|
+
}
|
|
1079
|
+
catch (destroyErr) {
|
|
1080
|
+
logger.warn(`[AiAct] Executor destroy error (catch): ${destroyErr.message}`);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1106
1083
|
session.executor = undefined;
|
|
1107
1084
|
sessionManager.updateStatus(sessionId, 'failed');
|
|
1108
1085
|
logger.error(`[Action] Execution error: sessionId=${sessionId}, error=${error.message}, ws.readyState=${ws.readyState}`);
|
|
1086
|
+
// 异常时也停止抓包并上传
|
|
1087
|
+
let captureDataUrl;
|
|
1088
|
+
try {
|
|
1089
|
+
const capturedRequests = await whistleManager.stopCapture(sessionId);
|
|
1090
|
+
if (capturedRequests.length > 0) {
|
|
1091
|
+
logger.info('[Action] Captured ' + capturedRequests.length + ' requests (error path)');
|
|
1092
|
+
try {
|
|
1093
|
+
const uploadResult = await this.saveAndUploadCaptureData(capturedRequests, sessionId);
|
|
1094
|
+
if (uploadResult)
|
|
1095
|
+
captureDataUrl = uploadResult;
|
|
1096
|
+
}
|
|
1097
|
+
catch (uploadErr) {
|
|
1098
|
+
logger.warn('[Action] Failed to upload capture data (error path): ' + (uploadErr instanceof Error ? uploadErr.message : String(uploadErr)));
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
catch (captureErr) {
|
|
1103
|
+
logger.warn('[Action] Failed to stop capture (error path): ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
1104
|
+
}
|
|
1109
1105
|
// 异常时也尝试提取 reportFile 并上传
|
|
1110
1106
|
let reportFile = '';
|
|
1111
1107
|
try {
|
|
@@ -1125,6 +1121,7 @@ export class DebugWebSocketServer {
|
|
|
1125
1121
|
error: error.message,
|
|
1126
1122
|
dump: '',
|
|
1127
1123
|
reportUrl,
|
|
1124
|
+
captureDataUrl,
|
|
1128
1125
|
}, request.deviceId);
|
|
1129
1126
|
// 异常时也发送 debug_completed,确保前端能退出执行中状态
|
|
1130
1127
|
this.sendMessage(ws, {
|
|
@@ -1132,6 +1129,7 @@ export class DebugWebSocketServer {
|
|
|
1132
1129
|
sessionId,
|
|
1133
1130
|
success: false,
|
|
1134
1131
|
reportUrl,
|
|
1132
|
+
captureDataUrl,
|
|
1135
1133
|
}, request.deviceId);
|
|
1136
1134
|
}
|
|
1137
1135
|
}
|
|
@@ -1143,6 +1141,14 @@ export class DebugWebSocketServer {
|
|
|
1143
1141
|
session.abortController = abortController;
|
|
1144
1142
|
// 将前端传来的 modelConfig 应用到环境变量,并获取补全后的 modelConfig(含 Planning/Insight)
|
|
1145
1143
|
const fullModelConfig = this.applyModelConfig(request.modelConfig);
|
|
1144
|
+
// 始终启动请求抓包
|
|
1145
|
+
try {
|
|
1146
|
+
await whistleManager.startCapture(sessionId);
|
|
1147
|
+
logger.info('[AiAct] Whistle request capture started: sessionId=' + sessionId);
|
|
1148
|
+
}
|
|
1149
|
+
catch (captureErr) {
|
|
1150
|
+
logger.warn('[AiAct] Failed to start request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
1151
|
+
}
|
|
1146
1152
|
// 哨兵:保证 ai_act_result + debug_completed 一定且只发一次(即便发送时再次抛错)
|
|
1147
1153
|
let finalSent = false;
|
|
1148
1154
|
const sendFinal = (payload) => {
|
|
@@ -1150,6 +1156,7 @@ export class DebugWebSocketServer {
|
|
|
1150
1156
|
return;
|
|
1151
1157
|
finalSent = true;
|
|
1152
1158
|
const reportUrl = payload.reportUrl || null;
|
|
1159
|
+
const captureDataUrl = payload.captureDataUrl;
|
|
1153
1160
|
// 第一次尝试
|
|
1154
1161
|
try {
|
|
1155
1162
|
this.sendMessage(ws, {
|
|
@@ -1162,6 +1169,7 @@ export class DebugWebSocketServer {
|
|
|
1162
1169
|
reportUrl,
|
|
1163
1170
|
error: payload.error,
|
|
1164
1171
|
platform: request.platform || 'android',
|
|
1172
|
+
captureDataUrl,
|
|
1165
1173
|
}, request.deviceId);
|
|
1166
1174
|
}
|
|
1167
1175
|
catch (e1) {
|
|
@@ -1177,6 +1185,7 @@ export class DebugWebSocketServer {
|
|
|
1177
1185
|
reportUrl,
|
|
1178
1186
|
error: (payload.error || '') + ' [send failed]',
|
|
1179
1187
|
platform: request.platform || 'android',
|
|
1188
|
+
captureDataUrl,
|
|
1180
1189
|
}, request.deviceId);
|
|
1181
1190
|
}
|
|
1182
1191
|
catch (e2) {
|
|
@@ -1190,6 +1199,7 @@ export class DebugWebSocketServer {
|
|
|
1190
1199
|
sessionId,
|
|
1191
1200
|
success: payload.success,
|
|
1192
1201
|
reportUrl,
|
|
1202
|
+
captureDataUrl,
|
|
1193
1203
|
}, request.deviceId);
|
|
1194
1204
|
logger.info(`[AiAct] Sent ai_act_result + debug_completed for sessionId=${sessionId}, success=${payload.success}, reportUrl=${reportUrl || 'null'}`);
|
|
1195
1205
|
}
|
|
@@ -1239,6 +1249,15 @@ export class DebugWebSocketServer {
|
|
|
1239
1249
|
deepThink: request.deepThink,
|
|
1240
1250
|
},
|
|
1241
1251
|
};
|
|
1252
|
+
// 注意:proxy-service.jd.com 的规则由前端(浏览器端)设置,后端只负责设备代理 + 抓包
|
|
1253
|
+
// 始终启动请求抓包
|
|
1254
|
+
try {
|
|
1255
|
+
await whistleManager.startCapture(sessionId, undefined, 3000, config.proxyAccount);
|
|
1256
|
+
logger.info('[Action] Whistle request capture started: sessionId=' + sessionId);
|
|
1257
|
+
}
|
|
1258
|
+
catch (captureErr) {
|
|
1259
|
+
logger.warn('[Action] Failed to start request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
1260
|
+
}
|
|
1242
1261
|
// 使用 AbortSignal 监听停止请求
|
|
1243
1262
|
const executePromise = executor.execute(config);
|
|
1244
1263
|
const abortPromise = new Promise((resolve) => {
|
|
@@ -1253,7 +1272,37 @@ export class DebugWebSocketServer {
|
|
|
1253
1272
|
logger.info(`[AiAct] Execution aborted by user, skipping result messages (handleStopDebug will send them)`);
|
|
1254
1273
|
return;
|
|
1255
1274
|
}
|
|
1275
|
+
// 清理 executor(释放 device/agent 资源 + 清除设备代理)
|
|
1276
|
+
if (executor && typeof executor.destroy === 'function') {
|
|
1277
|
+
try {
|
|
1278
|
+
await executor.destroy();
|
|
1279
|
+
}
|
|
1280
|
+
catch (destroyErr) {
|
|
1281
|
+
logger.warn(`[AiAct] Executor destroy error: ${destroyErr.message}`);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1256
1284
|
session.executor = undefined;
|
|
1285
|
+
// 停止请求抓包,采集执行期间的所有请求
|
|
1286
|
+
let captureDataUrl;
|
|
1287
|
+
try {
|
|
1288
|
+
const capturedRequests = await whistleManager.stopCapture(sessionId);
|
|
1289
|
+
if (capturedRequests.length > 0) {
|
|
1290
|
+
logger.info('[AiAct] Captured ' + capturedRequests.length + ' requests during session');
|
|
1291
|
+
try {
|
|
1292
|
+
const uploadResult = await this.saveAndUploadCaptureData(capturedRequests, sessionId);
|
|
1293
|
+
if (uploadResult) {
|
|
1294
|
+
captureDataUrl = uploadResult;
|
|
1295
|
+
logger.info('[AiAct] Capture data uploaded: ' + captureDataUrl);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
catch (uploadErr) {
|
|
1299
|
+
logger.warn('[AiAct] Failed to upload capture data: ' + (uploadErr instanceof Error ? uploadErr.message : String(uploadErr)));
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
catch (captureErr) {
|
|
1304
|
+
logger.warn('[AiAct] Failed to stop request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
1305
|
+
}
|
|
1257
1306
|
const status = result.success ? 'completed' : 'failed';
|
|
1258
1307
|
sessionManager.updateStatus(sessionId, status);
|
|
1259
1308
|
logger.info(`[AiAct] Execution finished: sessionId=${sessionId}, status=${status}, ws.readyState=${ws.readyState}`);
|
|
@@ -1264,18 +1313,48 @@ export class DebugWebSocketServer {
|
|
|
1264
1313
|
result: result.result,
|
|
1265
1314
|
reportUrl,
|
|
1266
1315
|
error: result.errorMessage,
|
|
1316
|
+
captureDataUrl,
|
|
1267
1317
|
});
|
|
1268
1318
|
}
|
|
1269
1319
|
catch (error) {
|
|
1320
|
+
// 清理 executor(释放 device/agent 资源 + 清除设备代理)
|
|
1321
|
+
if (session.executor && typeof session.executor.destroy === 'function') {
|
|
1322
|
+
try {
|
|
1323
|
+
await session.executor.destroy();
|
|
1324
|
+
}
|
|
1325
|
+
catch (destroyErr) {
|
|
1326
|
+
logger.warn(`[AiAct] Executor destroy error (catch): ${destroyErr.message}`);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1270
1329
|
session.executor = undefined;
|
|
1271
1330
|
sessionManager.updateStatus(sessionId, 'failed');
|
|
1272
1331
|
logger.error(`[AiAct] Execution error: sessionId=${sessionId}, error=${error.message}, ws.readyState=${ws.readyState}`);
|
|
1332
|
+
// 异常时也停止抓包并上传
|
|
1333
|
+
let captureDataUrl;
|
|
1334
|
+
try {
|
|
1335
|
+
const capturedRequests = await whistleManager.stopCapture(sessionId);
|
|
1336
|
+
if (capturedRequests.length > 0) {
|
|
1337
|
+
logger.info('[AiAct] Captured ' + capturedRequests.length + ' requests (error path)');
|
|
1338
|
+
try {
|
|
1339
|
+
const uploadResult = await this.saveAndUploadCaptureData(capturedRequests, sessionId);
|
|
1340
|
+
if (uploadResult)
|
|
1341
|
+
captureDataUrl = uploadResult;
|
|
1342
|
+
}
|
|
1343
|
+
catch (uploadErr) {
|
|
1344
|
+
logger.warn('[AiAct] Failed to upload capture data (error path): ' + (uploadErr instanceof Error ? uploadErr.message : String(uploadErr)));
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
catch (captureErr) {
|
|
1349
|
+
logger.warn('[AiAct] Failed to stop capture (error path): ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
1350
|
+
}
|
|
1273
1351
|
const reportFile = safeExtractReportFile();
|
|
1274
1352
|
const reportUrl = await this.safeUploadReport(reportFile, sessionId);
|
|
1275
1353
|
sendFinal({
|
|
1276
1354
|
success: false,
|
|
1277
1355
|
error: error.message,
|
|
1278
1356
|
reportUrl,
|
|
1357
|
+
captureDataUrl,
|
|
1279
1358
|
});
|
|
1280
1359
|
}
|
|
1281
1360
|
finally {
|
|
@@ -1363,6 +1442,14 @@ export class DebugWebSocketServer {
|
|
|
1363
1442
|
}
|
|
1364
1443
|
try {
|
|
1365
1444
|
this.sendMessage(ws, { type: 'web_action_started', sessionId, actionType: request.actionType });
|
|
1445
|
+
// 始终启动请求抓包
|
|
1446
|
+
try {
|
|
1447
|
+
await whistleManager.startCapture(sessionId);
|
|
1448
|
+
logger.info('[WebAction] Whistle request capture started: sessionId=' + sessionId);
|
|
1449
|
+
}
|
|
1450
|
+
catch (captureErr) {
|
|
1451
|
+
logger.warn('[WebAction] Failed to start request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
1452
|
+
}
|
|
1366
1453
|
// 懒启动浏览器(每个 session 只启一次)
|
|
1367
1454
|
if (!session.browser) {
|
|
1368
1455
|
// 并发上限保护:超过 webSessionMaxConcurrency 直接拒绝,防止 OOM / launch 失败
|
|
@@ -1458,6 +1545,7 @@ export class DebugWebSocketServer {
|
|
|
1458
1545
|
// 注意:worker 子进程路径(handleStartDebug 等)仍需经过 applyModelConfig 写 env,
|
|
1459
1546
|
// 因为 fork 子进程必须通过 env 透传配置,不影响这里。
|
|
1460
1547
|
session.webAgent = new midscene.PlaywrightAgent(page, {
|
|
1548
|
+
outputFormat: 'html-and-external-assets',
|
|
1461
1549
|
modelConfig: this.applyModelConfig(request.modelConfig),
|
|
1462
1550
|
});
|
|
1463
1551
|
// 注册到 web session 并发池,开始计入并发数 + TTL
|
|
@@ -1663,6 +1751,27 @@ export class DebugWebSocketServer {
|
|
|
1663
1751
|
const actionSuccess = !runCodeError;
|
|
1664
1752
|
sessionManager.updateStatus(sessionId, actionSuccess ? 'completed' : 'failed');
|
|
1665
1753
|
stopWebDumpInterval();
|
|
1754
|
+
// 停止请求抓包,采集执行期间的所有请求
|
|
1755
|
+
let captureDataUrl;
|
|
1756
|
+
try {
|
|
1757
|
+
const capturedRequests = await whistleManager.stopCapture(sessionId);
|
|
1758
|
+
if (capturedRequests.length > 0) {
|
|
1759
|
+
logger.info('[WebAction] Captured ' + capturedRequests.length + ' requests during session');
|
|
1760
|
+
try {
|
|
1761
|
+
const uploadResult = await this.saveAndUploadCaptureData(capturedRequests, sessionId);
|
|
1762
|
+
if (uploadResult) {
|
|
1763
|
+
captureDataUrl = uploadResult;
|
|
1764
|
+
logger.info('[WebAction] Capture data uploaded: ' + captureDataUrl);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
catch (uploadErr) {
|
|
1768
|
+
logger.warn('[WebAction] Failed to upload capture data: ' + (uploadErr instanceof Error ? uploadErr.message : String(uploadErr)));
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
catch (captureErr) {
|
|
1773
|
+
logger.warn('[WebAction] Failed to stop request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
1774
|
+
}
|
|
1666
1775
|
// 上传报告文件到服务端,拿到 reportUrl
|
|
1667
1776
|
const reportUrl = await this.safeUploadReport(reportFile, sessionId);
|
|
1668
1777
|
logger.info(`[WebAction] report uploaded: reportUrl=${reportUrl || 'null'}, reportFile=${reportFile}`);
|
|
@@ -1676,6 +1785,7 @@ export class DebugWebSocketServer {
|
|
|
1676
1785
|
dump: '',
|
|
1677
1786
|
reportUrl,
|
|
1678
1787
|
error: runCodeError || undefined,
|
|
1788
|
+
captureDataUrl,
|
|
1679
1789
|
});
|
|
1680
1790
|
// 与移动端对齐:同步发一份 action_result,供 debugLogManager 走报告链路
|
|
1681
1791
|
this.sendMessage(ws, {
|
|
@@ -1688,6 +1798,7 @@ export class DebugWebSocketServer {
|
|
|
1688
1798
|
reportUrl,
|
|
1689
1799
|
error: runCodeError || undefined,
|
|
1690
1800
|
platform: 'web',
|
|
1801
|
+
captureDataUrl,
|
|
1691
1802
|
});
|
|
1692
1803
|
// 必须发 debug_completed,前端依赖它退出执行中状态
|
|
1693
1804
|
this.sendMessage(ws, {
|
|
@@ -1696,6 +1807,7 @@ export class DebugWebSocketServer {
|
|
|
1696
1807
|
success: actionSuccess,
|
|
1697
1808
|
reportUrl,
|
|
1698
1809
|
errorMessage: runCodeError || undefined,
|
|
1810
|
+
captureDataUrl,
|
|
1699
1811
|
});
|
|
1700
1812
|
}
|
|
1701
1813
|
catch (error) {
|
|
@@ -1706,6 +1818,25 @@ export class DebugWebSocketServer {
|
|
|
1706
1818
|
clearInterval(session.dumpIntervalId);
|
|
1707
1819
|
session.dumpIntervalId = undefined;
|
|
1708
1820
|
}
|
|
1821
|
+
// 异常时也停止抓包并上传
|
|
1822
|
+
let captureDataUrl;
|
|
1823
|
+
try {
|
|
1824
|
+
const capturedRequests = await whistleManager.stopCapture(sessionId);
|
|
1825
|
+
if (capturedRequests.length > 0) {
|
|
1826
|
+
logger.info('[WebAction] Captured ' + capturedRequests.length + ' requests (error path)');
|
|
1827
|
+
try {
|
|
1828
|
+
const uploadResult = await this.saveAndUploadCaptureData(capturedRequests, sessionId);
|
|
1829
|
+
if (uploadResult)
|
|
1830
|
+
captureDataUrl = uploadResult;
|
|
1831
|
+
}
|
|
1832
|
+
catch (uploadErr) {
|
|
1833
|
+
logger.warn('[WebAction] Failed to upload capture data (error path): ' + (uploadErr instanceof Error ? uploadErr.message : String(uploadErr)));
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
catch (captureErr) {
|
|
1838
|
+
logger.warn('[WebAction] Failed to stop capture (error path): ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
1839
|
+
}
|
|
1709
1840
|
// 失败时也尝试提取 reportFile 并上传
|
|
1710
1841
|
let reportFile = '';
|
|
1711
1842
|
const agentForReport = session?.webAgent;
|
|
@@ -1728,6 +1859,7 @@ export class DebugWebSocketServer {
|
|
|
1728
1859
|
error: error.message,
|
|
1729
1860
|
dump: '',
|
|
1730
1861
|
reportUrl,
|
|
1862
|
+
captureDataUrl,
|
|
1731
1863
|
});
|
|
1732
1864
|
// 与移动端对齐:同步发一份 action_result
|
|
1733
1865
|
this.sendMessage(ws, {
|
|
@@ -1739,6 +1871,7 @@ export class DebugWebSocketServer {
|
|
|
1739
1871
|
dump: '',
|
|
1740
1872
|
reportUrl,
|
|
1741
1873
|
platform: 'web',
|
|
1874
|
+
captureDataUrl,
|
|
1742
1875
|
});
|
|
1743
1876
|
// 必须发 debug_completed,前端依赖它退出执行中状态
|
|
1744
1877
|
this.sendMessage(ws, {
|
|
@@ -1746,6 +1879,7 @@ export class DebugWebSocketServer {
|
|
|
1746
1879
|
sessionId,
|
|
1747
1880
|
success: false,
|
|
1748
1881
|
reportUrl,
|
|
1882
|
+
captureDataUrl,
|
|
1749
1883
|
errorMessage: error.message,
|
|
1750
1884
|
});
|
|
1751
1885
|
}
|
|
@@ -1862,23 +1996,6 @@ export class DebugWebSocketServer {
|
|
|
1862
1996
|
catch (captureErr) {
|
|
1863
1997
|
logger.warn('[CloseWebSession] Failed to stop request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
1864
1998
|
}
|
|
1865
|
-
// Teardown Whistle proxy rules if they were set
|
|
1866
|
-
const closeSess = sessionManager.get(request.sessionId);
|
|
1867
|
-
const closeWhistleRule = closeSess ? closeSess.whistleRuleGroupName : undefined;
|
|
1868
|
-
if (closeWhistleRule) {
|
|
1869
|
-
try {
|
|
1870
|
-
const closeWhistleApiBase = closeSess ? closeSess.whistleApiBase : undefined;
|
|
1871
|
-
await whistleManager.removeRuleGroup(closeWhistleRule, closeWhistleApiBase);
|
|
1872
|
-
if (closeSess) {
|
|
1873
|
-
closeSess.whistleRuleGroupName = undefined;
|
|
1874
|
-
closeSess.whistleApiBase = undefined;
|
|
1875
|
-
}
|
|
1876
|
-
logger.info('[CloseWebSession] Whistle proxy teardown: ruleGroup=' + closeWhistleRule);
|
|
1877
|
-
}
|
|
1878
|
-
catch (err) {
|
|
1879
|
-
logger.warn('[CloseWebSession] Failed to teardown Whistle proxy: ' + (err instanceof Error ? err.message : String(err)));
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
1999
|
sessionManager.updateStatus(request.sessionId, 'stopped');
|
|
1883
2000
|
this.sendMessage(ws, { type: 'web_session_closed', sessionId: request.sessionId });
|
|
1884
2001
|
}
|
|
@@ -1954,23 +2071,6 @@ export class DebugWebSocketServer {
|
|
|
1954
2071
|
catch (captureErr) {
|
|
1955
2072
|
logger.warn('[StopDebug] Failed to stop request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
1956
2073
|
}
|
|
1957
|
-
// Teardown Whistle proxy rules if they were set
|
|
1958
|
-
const stopSess = sessionManager.get(request.sessionId);
|
|
1959
|
-
const stopWhistleRule = stopSess ? stopSess.whistleRuleGroupName : undefined;
|
|
1960
|
-
if (stopWhistleRule) {
|
|
1961
|
-
try {
|
|
1962
|
-
const stopWhistleApiBase = stopSess ? stopSess.whistleApiBase : undefined;
|
|
1963
|
-
await whistleManager.removeRuleGroup(stopWhistleRule, stopWhistleApiBase);
|
|
1964
|
-
if (stopSess) {
|
|
1965
|
-
stopSess.whistleRuleGroupName = undefined;
|
|
1966
|
-
stopSess.whistleApiBase = undefined;
|
|
1967
|
-
}
|
|
1968
|
-
logger.info('[StopDebug] Whistle proxy teardown: ruleGroup=' + stopWhistleRule);
|
|
1969
|
-
}
|
|
1970
|
-
catch (err) {
|
|
1971
|
-
logger.warn('[StopDebug] Failed to teardown Whistle proxy: ' + (err instanceof Error ? err.message : String(err)));
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
2074
|
sessionManager.updateStatus(request.sessionId, 'stopped');
|
|
1975
2075
|
// 上传报告文件到服务端,拿到 reportUrl
|
|
1976
2076
|
const reportUrl = await this.safeUploadReport(reportFile, request.sessionId);
|
|
@@ -1994,7 +2094,6 @@ export class DebugWebSocketServer {
|
|
|
1994
2094
|
type: 'debug_stopped',
|
|
1995
2095
|
sessionId: request.sessionId,
|
|
1996
2096
|
reportUrl,
|
|
1997
|
-
capturedRequests: stopCapturedRequests.length > 0 ? this.simplifyCapturedRequests(stopCapturedRequests) : undefined,
|
|
1998
2097
|
captureDataUrl: stopCaptureDataUrl,
|
|
1999
2098
|
});
|
|
2000
2099
|
// 同时发送 action_result,让前端能获取到停止时的执行数据
|
|
@@ -2005,6 +2104,7 @@ export class DebugWebSocketServer {
|
|
|
2005
2104
|
error: 'Action stopped by user',
|
|
2006
2105
|
dump: '',
|
|
2007
2106
|
reportUrl,
|
|
2107
|
+
captureDataUrl: stopCaptureDataUrl,
|
|
2008
2108
|
});
|
|
2009
2109
|
// 发送 debug_completed
|
|
2010
2110
|
this.sendMessage(ws, {
|
|
@@ -2012,7 +2112,6 @@ export class DebugWebSocketServer {
|
|
|
2012
2112
|
sessionId: request.sessionId,
|
|
2013
2113
|
success: false,
|
|
2014
2114
|
reportUrl,
|
|
2015
|
-
capturedRequests: stopCapturedRequests.length > 0 ? this.simplifyCapturedRequests(stopCapturedRequests) : undefined,
|
|
2016
2115
|
captureDataUrl: stopCaptureDataUrl,
|
|
2017
2116
|
});
|
|
2018
2117
|
}
|
|
@@ -2135,6 +2234,14 @@ export class DebugWebSocketServer {
|
|
|
2135
2234
|
timestamp: new Date().toISOString(),
|
|
2136
2235
|
};
|
|
2137
2236
|
if (ws.readyState === ws.OPEN) {
|
|
2237
|
+
const messageType = String(message.type || '');
|
|
2238
|
+
const canDropUnderBackpressure = messageType === 'action_dump' ||
|
|
2239
|
+
messageType === 'execution_line' ||
|
|
2240
|
+
messageType === 'task_log';
|
|
2241
|
+
if (canDropUnderBackpressure && ws.bufferedAmount > 2 * 1024 * 1024) {
|
|
2242
|
+
logger.warn(`[WS] Dropping ${messageType} due to backpressure: buffered=${ws.bufferedAmount}, sessionId=${message.sessionId || ''}`);
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2138
2245
|
let payload = JSON.stringify(msg);
|
|
2139
2246
|
// WSS 自身 maxPayload=10MB(接收端),发送端我们保留 2MB 余量以兼容反代/网关
|
|
2140
2247
|
const MAX_PAYLOAD_SIZE = 2 * 1024 * 1024; // 2MB
|
|
@@ -2219,56 +2326,29 @@ export class DebugWebSocketServer {
|
|
|
2219
2326
|
return undefined;
|
|
2220
2327
|
}
|
|
2221
2328
|
}
|
|
2222
|
-
|
|
2223
|
-
const
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
clientIp: req.clientIp,
|
|
2231
|
-
startTime: req.startTime,
|
|
2232
|
-
endTime: req.endTime,
|
|
2233
|
-
duration: req.duration,
|
|
2234
|
-
dnsTime: req.dnsTime,
|
|
2235
|
-
requestTime: req.requestTime,
|
|
2236
|
-
responseTime: req.responseTime,
|
|
2237
|
-
};
|
|
2238
|
-
// 请求头只保留 content-type 和 content-length
|
|
2239
|
-
if (req.reqHeaders) {
|
|
2240
|
-
const filteredReqHeaders = {};
|
|
2241
|
-
for (const [k, v] of Object.entries(req.reqHeaders)) {
|
|
2242
|
-
if (k.toLowerCase() === 'content-type' || k.toLowerCase() === 'content-length') {
|
|
2243
|
-
filteredReqHeaders[k] = v;
|
|
2244
|
-
}
|
|
2245
|
-
}
|
|
2246
|
-
simplified.reqHeaders = filteredReqHeaders;
|
|
2247
|
-
}
|
|
2248
|
-
if (req.resHeaders) {
|
|
2249
|
-
const filteredResHeaders = {};
|
|
2250
|
-
for (const [k, v] of Object.entries(req.resHeaders)) {
|
|
2251
|
-
if (k.toLowerCase() === 'content-type' || k.toLowerCase() === 'content-length') {
|
|
2252
|
-
filteredResHeaders[k] = v;
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
simplified.resHeaders = filteredResHeaders;
|
|
2256
|
-
}
|
|
2257
|
-
// body 截断
|
|
2258
|
-
if (req.reqBody) {
|
|
2259
|
-
simplified.reqBody = req.reqBody.length > MAX_BODY_LENGTH
|
|
2260
|
-
? req.reqBody.substring(0, MAX_BODY_LENGTH) + '...[truncated]'
|
|
2261
|
-
: req.reqBody;
|
|
2329
|
+
async cleanupSession(sessionId) {
|
|
2330
|
+
const session = sessionManager.get(sessionId);
|
|
2331
|
+
if (session?.abortController && !session.abortController.signal.aborted) {
|
|
2332
|
+
session.abortController.abort();
|
|
2333
|
+
}
|
|
2334
|
+
if (session?.executor && typeof session.executor.destroy === 'function') {
|
|
2335
|
+
try {
|
|
2336
|
+
await session.executor.destroy();
|
|
2262
2337
|
}
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
? req.resBody.substring(0, MAX_BODY_LENGTH) + '...[truncated]'
|
|
2266
|
-
: req.resBody;
|
|
2338
|
+
catch (error) {
|
|
2339
|
+
logger.warn(`[Cleanup] Executor destroy failed: sessionId=${sessionId}, error=${error.message}`);
|
|
2267
2340
|
}
|
|
2268
|
-
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
|
-
|
|
2341
|
+
session.executor = undefined;
|
|
2342
|
+
}
|
|
2343
|
+
if (session?.process && !session.process.killed) {
|
|
2344
|
+
session.process.kill('SIGTERM');
|
|
2345
|
+
}
|
|
2346
|
+
try {
|
|
2347
|
+
await whistleManager.stopCapture(sessionId);
|
|
2348
|
+
}
|
|
2349
|
+
catch (error) {
|
|
2350
|
+
logger.warn(`[Cleanup] Capture stop failed: sessionId=${sessionId}, error=${error.message}`);
|
|
2351
|
+
}
|
|
2272
2352
|
// 关闭 web 浏览器(如果存在)
|
|
2273
2353
|
await this.closeWebSession(sessionId);
|
|
2274
2354
|
dumpManager.stopPeriodicDump(sessionId);
|
|
@@ -2279,6 +2359,7 @@ export class DebugWebSocketServer {
|
|
|
2279
2359
|
return sessionManager.getAll();
|
|
2280
2360
|
}
|
|
2281
2361
|
async close() {
|
|
2362
|
+
await Promise.all(sessionManager.getAll().map((session) => this.cleanupSession(session.sessionId)));
|
|
2282
2363
|
await screencastManager.closeAll();
|
|
2283
2364
|
await webScreencastManager.closeAll();
|
|
2284
2365
|
await webBrowserPool.closeAll();
|