@aiscene/aiserver 2.0.6 → 2.0.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/capture/knowledge-organizer.js +2 -2
- package/dist/capture/knowledge-organizer.js.map +1 -1
- package/dist/config/cli.js +2 -2
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +29 -9
- package/dist/config/index.js.map +1 -1
- package/dist/debug/websocket-server.d.ts +9 -0
- package/dist/debug/websocket-server.d.ts.map +1 -1
- package/dist/debug/websocket-server.js +216 -78
- package/dist/debug/websocket-server.js.map +1 -1
- package/dist/task/scheduler.d.ts.map +1 -1
- package/dist/task/scheduler.js +21 -7
- package/dist/task/scheduler.js.map +1 -1
- package/package.json +1 -1
|
@@ -18,11 +18,20 @@ import { ExecutorFactory } from '../executor/executor-factory.js';
|
|
|
18
18
|
import { instrumentCode } from '../executor/code-instrument.js';
|
|
19
19
|
import { playwrightSwipe } from '../core/native-swipe.js';
|
|
20
20
|
import { applyAiMemory } from '../core/ai-memory.js';
|
|
21
|
+
import { FIXED_MODEL_API_KEY } from '../config/index.js';
|
|
21
22
|
import { whistleManager } from '../proxy/whistle-manager.js';
|
|
22
23
|
import { executeAndroidLogin, executeWebLogin, } from '../login/android-login-context.js';
|
|
23
24
|
import { webRecordingManager } from '../recorder/web-recording-manager.js';
|
|
24
25
|
import { resolveTestCaseReferences } from '../task/test-case-reference-resolver.js';
|
|
25
26
|
const logger = createLogger('DebugWebSocket');
|
|
27
|
+
const MODEL_API_KEY_ENV_KEYS = new Set([
|
|
28
|
+
'MIDSCENE_MODEL_API_KEY',
|
|
29
|
+
'MIDSCENE_PLANNING_MODEL_API_KEY',
|
|
30
|
+
'MIDSCENE_INSIGHT_MODEL_API_KEY',
|
|
31
|
+
]);
|
|
32
|
+
function isApiKeyConfigKey(key) {
|
|
33
|
+
return MODEL_API_KEY_ENV_KEYS.has(key) || /api[_-]?key/i.test(key);
|
|
34
|
+
}
|
|
26
35
|
/** 获取本机可用的非内部 IPv4 地址,供客户端连接投屏 WebSocket */
|
|
27
36
|
function getLocalIP() {
|
|
28
37
|
const interfaces = os.networkInterfaces();
|
|
@@ -532,6 +541,150 @@ export class DebugWebSocketServer {
|
|
|
532
541
|
creator: session?.captureDataPersistence?.creator || '',
|
|
533
542
|
};
|
|
534
543
|
}
|
|
544
|
+
getModelConfigString(modelConfig, key, fallback) {
|
|
545
|
+
const value = modelConfig?.[key];
|
|
546
|
+
if (typeof value === 'string' && value.trim())
|
|
547
|
+
return value.trim();
|
|
548
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
549
|
+
return String(value);
|
|
550
|
+
return fallback || process.env[key] || '';
|
|
551
|
+
}
|
|
552
|
+
resolveDebugFallbackModelConfig(primaryModelConfig) {
|
|
553
|
+
const ai = this.config.ai;
|
|
554
|
+
const hasFallback = [
|
|
555
|
+
ai.fallbackModelBaseUrl,
|
|
556
|
+
ai.fallbackModelApiKey,
|
|
557
|
+
ai.fallbackModelName,
|
|
558
|
+
ai.fallbackModelFamily,
|
|
559
|
+
ai.fallbackPlanningModelBaseUrl,
|
|
560
|
+
ai.fallbackPlanningModelApiKey,
|
|
561
|
+
ai.fallbackPlanningModelName,
|
|
562
|
+
ai.fallbackPlanningModelFamily,
|
|
563
|
+
ai.fallbackInsightModelBaseUrl,
|
|
564
|
+
ai.fallbackInsightModelApiKey,
|
|
565
|
+
ai.fallbackInsightModelName,
|
|
566
|
+
ai.fallbackInsightModelFamily,
|
|
567
|
+
].some(value => typeof value === 'string' && value.trim());
|
|
568
|
+
if (!hasFallback)
|
|
569
|
+
return undefined;
|
|
570
|
+
const primary = {
|
|
571
|
+
MIDSCENE_MODEL_BASE_URL: this.getModelConfigString(primaryModelConfig, 'MIDSCENE_MODEL_BASE_URL', ai.baseUrl),
|
|
572
|
+
MIDSCENE_MODEL_API_KEY: FIXED_MODEL_API_KEY,
|
|
573
|
+
MIDSCENE_MODEL_NAME: this.getModelConfigString(primaryModelConfig, 'MIDSCENE_MODEL_NAME', ai.modelName),
|
|
574
|
+
MIDSCENE_MODEL_FAMILY: this.getModelConfigString(primaryModelConfig, 'MIDSCENE_MODEL_FAMILY', ai.modelFamily),
|
|
575
|
+
MIDSCENE_PLANNING_MODEL_BASE_URL: this.getModelConfigString(primaryModelConfig, 'MIDSCENE_PLANNING_MODEL_BASE_URL', ai.planningModelBaseUrl || ai.baseUrl),
|
|
576
|
+
MIDSCENE_PLANNING_MODEL_API_KEY: FIXED_MODEL_API_KEY,
|
|
577
|
+
MIDSCENE_PLANNING_MODEL_NAME: this.getModelConfigString(primaryModelConfig, 'MIDSCENE_PLANNING_MODEL_NAME', ai.planningModelName || ai.modelName),
|
|
578
|
+
MIDSCENE_PLANNING_MODEL_FAMILY: this.getModelConfigString(primaryModelConfig, 'MIDSCENE_PLANNING_MODEL_FAMILY', ai.planningModelFamily || ai.modelFamily),
|
|
579
|
+
MIDSCENE_INSIGHT_MODEL_BASE_URL: this.getModelConfigString(primaryModelConfig, 'MIDSCENE_INSIGHT_MODEL_BASE_URL', ai.insightModelBaseUrl || ai.baseUrl),
|
|
580
|
+
MIDSCENE_INSIGHT_MODEL_API_KEY: FIXED_MODEL_API_KEY,
|
|
581
|
+
MIDSCENE_INSIGHT_MODEL_NAME: this.getModelConfigString(primaryModelConfig, 'MIDSCENE_INSIGHT_MODEL_NAME', ai.insightModelName || ai.modelName),
|
|
582
|
+
MIDSCENE_INSIGHT_MODEL_FAMILY: this.getModelConfigString(primaryModelConfig, 'MIDSCENE_INSIGHT_MODEL_FAMILY', ai.insightModelFamily || ai.modelFamily),
|
|
583
|
+
};
|
|
584
|
+
const fallback = {
|
|
585
|
+
MIDSCENE_MODEL_BASE_URL: ai.fallbackModelBaseUrl || primary.MIDSCENE_MODEL_BASE_URL,
|
|
586
|
+
MIDSCENE_MODEL_API_KEY: FIXED_MODEL_API_KEY,
|
|
587
|
+
MIDSCENE_MODEL_NAME: ai.fallbackModelName || primary.MIDSCENE_MODEL_NAME,
|
|
588
|
+
MIDSCENE_MODEL_FAMILY: ai.fallbackModelFamily || primary.MIDSCENE_MODEL_FAMILY,
|
|
589
|
+
MIDSCENE_PLANNING_MODEL_BASE_URL: ai.fallbackPlanningModelBaseUrl || ai.fallbackModelBaseUrl || primary.MIDSCENE_PLANNING_MODEL_BASE_URL,
|
|
590
|
+
MIDSCENE_PLANNING_MODEL_API_KEY: FIXED_MODEL_API_KEY,
|
|
591
|
+
MIDSCENE_PLANNING_MODEL_NAME: ai.fallbackPlanningModelName || ai.fallbackModelName || primary.MIDSCENE_PLANNING_MODEL_NAME,
|
|
592
|
+
MIDSCENE_PLANNING_MODEL_FAMILY: ai.fallbackPlanningModelFamily || ai.fallbackModelFamily || primary.MIDSCENE_PLANNING_MODEL_FAMILY,
|
|
593
|
+
MIDSCENE_INSIGHT_MODEL_BASE_URL: ai.fallbackInsightModelBaseUrl || ai.fallbackModelBaseUrl || primary.MIDSCENE_INSIGHT_MODEL_BASE_URL,
|
|
594
|
+
MIDSCENE_INSIGHT_MODEL_API_KEY: FIXED_MODEL_API_KEY,
|
|
595
|
+
MIDSCENE_INSIGHT_MODEL_NAME: ai.fallbackInsightModelName || ai.fallbackModelName || primary.MIDSCENE_INSIGHT_MODEL_NAME,
|
|
596
|
+
MIDSCENE_INSIGHT_MODEL_FAMILY: ai.fallbackInsightModelFamily || ai.fallbackModelFamily || primary.MIDSCENE_INSIGHT_MODEL_FAMILY,
|
|
597
|
+
};
|
|
598
|
+
const changed = Object.keys(primary).some(key => primary[key] !== fallback[key]);
|
|
599
|
+
return changed ? fallback : undefined;
|
|
600
|
+
}
|
|
601
|
+
applyResolvedModelConfigToEnv(modelConfig, label) {
|
|
602
|
+
for (const [key, rawValue] of Object.entries(modelConfig)) {
|
|
603
|
+
const value = MODEL_API_KEY_ENV_KEYS.has(key) || isApiKeyConfigKey(key) ? FIXED_MODEL_API_KEY : rawValue;
|
|
604
|
+
if (value) {
|
|
605
|
+
process.env[key] = value;
|
|
606
|
+
logger.info(`[ModelConfig] Applied ${key} = ${value} (${label})`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
isModelRateLimitError(message) {
|
|
611
|
+
if (!message)
|
|
612
|
+
return false;
|
|
613
|
+
return /\b429\b|too many requests|rate.?limit|quota exceeded|insufficient.?quota|requests per/i.test(message);
|
|
614
|
+
}
|
|
615
|
+
async executeExecutorWithAbort(executor, config, abortController) {
|
|
616
|
+
if (abortController.signal.aborted) {
|
|
617
|
+
return { success: false, errorMessage: 'Action stopped by user' };
|
|
618
|
+
}
|
|
619
|
+
let abortHandler;
|
|
620
|
+
const executePromise = executor.execute(config).catch((error) => ({
|
|
621
|
+
success: false,
|
|
622
|
+
errorMessage: error.message,
|
|
623
|
+
}));
|
|
624
|
+
const abortPromise = new Promise((resolve) => {
|
|
625
|
+
abortHandler = () => resolve({ success: false, errorMessage: 'Action stopped by user' });
|
|
626
|
+
abortController.signal.addEventListener('abort', abortHandler, { once: true });
|
|
627
|
+
});
|
|
628
|
+
try {
|
|
629
|
+
return await Promise.race([executePromise, abortPromise]);
|
|
630
|
+
}
|
|
631
|
+
finally {
|
|
632
|
+
if (abortHandler) {
|
|
633
|
+
abortController.signal.removeEventListener('abort', abortHandler);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
async destroyExecutorForRetry(session, executor, logPrefix) {
|
|
638
|
+
if (!executor || typeof executor.destroy !== 'function')
|
|
639
|
+
return;
|
|
640
|
+
try {
|
|
641
|
+
await executor.destroy();
|
|
642
|
+
}
|
|
643
|
+
catch (destroyErr) {
|
|
644
|
+
logger.warn(`[${logPrefix}] Executor destroy error: ${destroyErr.message}`);
|
|
645
|
+
}
|
|
646
|
+
finally {
|
|
647
|
+
if (session?.executor === executor) {
|
|
648
|
+
session.executor = undefined;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
async destroyCurrentExecutor(session, logPrefix) {
|
|
653
|
+
const executor = session?.executor;
|
|
654
|
+
await this.destroyExecutorForRetry(session, executor, logPrefix);
|
|
655
|
+
}
|
|
656
|
+
async executeWithDebugModelFallback(options) {
|
|
657
|
+
let executor = options.createExecutor();
|
|
658
|
+
if (options.session)
|
|
659
|
+
options.session.executor = executor;
|
|
660
|
+
let result = await this.executeExecutorWithAbort(executor, options.config, options.abortController);
|
|
661
|
+
if (options.abortController.signal.aborted)
|
|
662
|
+
return result;
|
|
663
|
+
const fallbackModelConfig = this.resolveDebugFallbackModelConfig(options.config.modelConfig);
|
|
664
|
+
if (!result.success && fallbackModelConfig && this.isModelRateLimitError(result.errorMessage)) {
|
|
665
|
+
const fallbackLog = '[model-fallback] 429/rate limit detected, retrying once with fallback model config';
|
|
666
|
+
logger.warn(`[${options.logPrefix}] Model rate limit detected, retrying once with fallback model config`);
|
|
667
|
+
sessionManager.addLog(options.sessionId, fallbackLog);
|
|
668
|
+
this.sendMessage(options.ws, {
|
|
669
|
+
type: 'log_output',
|
|
670
|
+
sessionId: options.sessionId,
|
|
671
|
+
content: fallbackLog,
|
|
672
|
+
}, options.deviceId);
|
|
673
|
+
await this.destroyExecutorForRetry(options.session, executor, options.logPrefix);
|
|
674
|
+
if (options.abortController.signal.aborted) {
|
|
675
|
+
return { success: false, errorMessage: 'Action stopped by user' };
|
|
676
|
+
}
|
|
677
|
+
this.applyResolvedModelConfigToEnv(fallbackModelConfig, 'debug fallback model');
|
|
678
|
+
executor = options.createExecutor();
|
|
679
|
+
if (options.session)
|
|
680
|
+
options.session.executor = executor;
|
|
681
|
+
result = await this.executeExecutorWithAbort(executor, { ...options.config, modelConfig: fallbackModelConfig }, options.abortController);
|
|
682
|
+
if (!options.abortController.signal.aborted && !result.success && result.errorMessage) {
|
|
683
|
+
result.errorMessage = `Fallback retry failed: ${result.errorMessage}`;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return result;
|
|
687
|
+
}
|
|
535
688
|
async handleStartDebug(ws, request) {
|
|
536
689
|
// Web 分支:支持 sessionId 复用 + 主进程内嵌执行 + 投屏
|
|
537
690
|
if (request.platform === 'web') {
|
|
@@ -878,7 +1031,7 @@ export class DebugWebSocketServer {
|
|
|
878
1031
|
session.abortController = abortController;
|
|
879
1032
|
// 将前端传来的 modelConfig 应用到环境变量,并获取补全后的 modelConfig(含 Planning/Insight)
|
|
880
1033
|
const fullModelConfig = this.applyModelConfig(request.modelConfig);
|
|
881
|
-
const
|
|
1034
|
+
const createExecutor = () => ExecutorFactory.create('action', {
|
|
882
1035
|
sessionId,
|
|
883
1036
|
onLog: (msg) => {
|
|
884
1037
|
sessionManager.addLog(sessionId, msg);
|
|
@@ -891,9 +1044,6 @@ export class DebugWebSocketServer {
|
|
|
891
1044
|
this.sendMessage(ws, { type: 'execution_line', sessionId, line }, request.deviceId);
|
|
892
1045
|
},
|
|
893
1046
|
});
|
|
894
|
-
// 保存 executor 引用到 session,以便 stop_debug 时可以中断
|
|
895
|
-
if (session)
|
|
896
|
-
session.executor = executor;
|
|
897
1047
|
const config = {
|
|
898
1048
|
type: (request.platform === 'ios' ? 'ios' : 'android'),
|
|
899
1049
|
testName: createDebugReportGroupName(request),
|
|
@@ -925,39 +1075,23 @@ export class DebugWebSocketServer {
|
|
|
925
1075
|
catch (captureErr) {
|
|
926
1076
|
logger.warn('[Debug] Failed to start request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
927
1077
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1078
|
+
const result = await this.executeWithDebugModelFallback({
|
|
1079
|
+
ws,
|
|
1080
|
+
session,
|
|
1081
|
+
sessionId,
|
|
1082
|
+
deviceId: request.deviceId,
|
|
1083
|
+
config,
|
|
1084
|
+
abortController,
|
|
1085
|
+
createExecutor,
|
|
1086
|
+
logPrefix: 'executeRunCodeInProcess',
|
|
934
1087
|
});
|
|
935
|
-
let result;
|
|
936
|
-
let aborted = false;
|
|
937
|
-
try {
|
|
938
|
-
result = await Promise.race([executePromise, abortPromise]);
|
|
939
|
-
// 检测是否由用户主动停止(handleStopDebug 调用 abortController.abort())
|
|
940
|
-
aborted = abortController.signal.aborted;
|
|
941
|
-
}
|
|
942
|
-
catch (error) {
|
|
943
|
-
result = { success: false, errorMessage: error.message };
|
|
944
|
-
}
|
|
945
1088
|
// 如果是用户主动停止,由 handleStopDebug 统一发送结果消息,此处不再重复发送
|
|
946
|
-
if (aborted) {
|
|
1089
|
+
if (abortController.signal.aborted) {
|
|
947
1090
|
logger.info(`[executeRunCodeInProcess] Execution aborted by user, skipping result messages (handleStopDebug will send them)`);
|
|
948
1091
|
return;
|
|
949
1092
|
}
|
|
950
1093
|
// 清理 executor(释放 device/agent 资源 + 清除设备代理)
|
|
951
|
-
|
|
952
|
-
try {
|
|
953
|
-
await executor.destroy();
|
|
954
|
-
}
|
|
955
|
-
catch (destroyErr) {
|
|
956
|
-
logger.warn(`[executeRunCodeInProcess] Executor destroy error: ${destroyErr.message}`);
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
if (session)
|
|
960
|
-
session.executor = undefined;
|
|
1094
|
+
await this.destroyCurrentExecutor(session, 'executeRunCodeInProcess');
|
|
961
1095
|
// 停止请求抓包,采集调试期间的所有请求
|
|
962
1096
|
let capturedRequests = [];
|
|
963
1097
|
let captureDataUrl;
|
|
@@ -1055,15 +1189,15 @@ export class DebugWebSocketServer {
|
|
|
1055
1189
|
...process.env,
|
|
1056
1190
|
WORKER_CONFIG: JSON.stringify(execConfig),
|
|
1057
1191
|
MIDSCENE_MODEL_BASE_URL: process.env.MIDSCENE_MODEL_BASE_URL || this.config.ai.baseUrl,
|
|
1058
|
-
MIDSCENE_MODEL_API_KEY:
|
|
1192
|
+
MIDSCENE_MODEL_API_KEY: FIXED_MODEL_API_KEY,
|
|
1059
1193
|
MIDSCENE_MODEL_NAME: process.env.MIDSCENE_MODEL_NAME || this.config.ai.modelName,
|
|
1060
1194
|
MIDSCENE_MODEL_FAMILY: process.env.MIDSCENE_MODEL_FAMILY || this.config.ai.modelFamily,
|
|
1061
1195
|
MIDSCENE_PLANNING_MODEL_BASE_URL: process.env.MIDSCENE_PLANNING_MODEL_BASE_URL || this.config.ai.planningModelBaseUrl,
|
|
1062
|
-
MIDSCENE_PLANNING_MODEL_API_KEY:
|
|
1196
|
+
MIDSCENE_PLANNING_MODEL_API_KEY: FIXED_MODEL_API_KEY,
|
|
1063
1197
|
MIDSCENE_PLANNING_MODEL_NAME: process.env.MIDSCENE_PLANNING_MODEL_NAME || this.config.ai.planningModelName,
|
|
1064
1198
|
MIDSCENE_PLANNING_MODEL_FAMILY: process.env.MIDSCENE_PLANNING_MODEL_FAMILY || this.config.ai.planningModelFamily,
|
|
1065
1199
|
MIDSCENE_INSIGHT_MODEL_BASE_URL: process.env.MIDSCENE_INSIGHT_MODEL_BASE_URL || this.config.ai.insightModelBaseUrl,
|
|
1066
|
-
MIDSCENE_INSIGHT_MODEL_API_KEY:
|
|
1200
|
+
MIDSCENE_INSIGHT_MODEL_API_KEY: FIXED_MODEL_API_KEY,
|
|
1067
1201
|
MIDSCENE_INSIGHT_MODEL_NAME: process.env.MIDSCENE_INSIGHT_MODEL_NAME || this.config.ai.insightModelName,
|
|
1068
1202
|
MIDSCENE_INSIGHT_MODEL_FAMILY: process.env.MIDSCENE_INSIGHT_MODEL_FAMILY || this.config.ai.insightModelFamily,
|
|
1069
1203
|
};
|
|
@@ -1176,7 +1310,7 @@ export class DebugWebSocketServer {
|
|
|
1176
1310
|
// 调试日志:打印收到的模型配置
|
|
1177
1311
|
logger.info(`>>> [WS] Received execute_action request:`);
|
|
1178
1312
|
logger.info(`>>> [WS] request.modelName = ${request.modelName}`);
|
|
1179
|
-
logger.info(`>>> [WS] request.modelConfig = ${JSON.stringify(request.modelConfig)}`);
|
|
1313
|
+
logger.info(`>>> [WS] request.modelConfig = ${JSON.stringify(this.forceFixedModelApiKeys(request.modelConfig))}`);
|
|
1180
1314
|
// 将前端传来的 modelConfig 应用到环境变量,并获取补全后的 modelConfig(含 Planning/Insight)
|
|
1181
1315
|
const fullModelConfig = this.applyModelConfig(request.modelConfig);
|
|
1182
1316
|
// 同时处理顶层的 modelName 字段
|
|
@@ -1194,7 +1328,7 @@ export class DebugWebSocketServer {
|
|
|
1194
1328
|
platform: request.platform,
|
|
1195
1329
|
logPrefix: `[Action ${sessionId}]`,
|
|
1196
1330
|
});
|
|
1197
|
-
const
|
|
1331
|
+
const createExecutor = () => ExecutorFactory.create('action', {
|
|
1198
1332
|
sessionId,
|
|
1199
1333
|
onLog: (msg) => {
|
|
1200
1334
|
sessionManager.addLog(sessionId, msg);
|
|
@@ -1207,8 +1341,6 @@ export class DebugWebSocketServer {
|
|
|
1207
1341
|
this.sendMessage(ws, { type: 'execution_line', sessionId, line }, request.deviceId);
|
|
1208
1342
|
},
|
|
1209
1343
|
});
|
|
1210
|
-
// 保存 executor 引用到 session,以便 stop_debug 时可以中断
|
|
1211
|
-
session.executor = executor;
|
|
1212
1344
|
const config = {
|
|
1213
1345
|
type: request.platform || 'android',
|
|
1214
1346
|
testName: createDebugReportGroupName(request),
|
|
@@ -1239,29 +1371,23 @@ export class DebugWebSocketServer {
|
|
|
1239
1371
|
catch (captureErr) {
|
|
1240
1372
|
logger.warn('[Action] Failed to start request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
1241
1373
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1374
|
+
const result = await this.executeWithDebugModelFallback({
|
|
1375
|
+
ws,
|
|
1376
|
+
session,
|
|
1377
|
+
sessionId,
|
|
1378
|
+
deviceId: request.deviceId,
|
|
1379
|
+
config,
|
|
1380
|
+
abortController,
|
|
1381
|
+
createExecutor,
|
|
1382
|
+
logPrefix: 'Action',
|
|
1248
1383
|
});
|
|
1249
|
-
const result = await Promise.race([executePromise, abortPromise]);
|
|
1250
1384
|
// 如果是用户主动停止,由 handleStopDebug 统一发送结果消息,此处不再重复发送
|
|
1251
1385
|
if (abortController.signal.aborted) {
|
|
1252
1386
|
logger.info(`[Action] Execution aborted by user, skipping result messages (handleStopDebug will send them)`);
|
|
1253
1387
|
return;
|
|
1254
1388
|
}
|
|
1255
1389
|
// 清理 executor(释放 device/agent 资源 + 清除设备代理)
|
|
1256
|
-
|
|
1257
|
-
try {
|
|
1258
|
-
await executor.destroy();
|
|
1259
|
-
}
|
|
1260
|
-
catch (destroyErr) {
|
|
1261
|
-
logger.warn(`[Action] Executor destroy error: ${destroyErr.message}`);
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
session.executor = undefined;
|
|
1390
|
+
await this.destroyCurrentExecutor(session, 'Action');
|
|
1265
1391
|
// 停止请求抓包,采集执行期间的所有请求
|
|
1266
1392
|
let capturedRequests = [];
|
|
1267
1393
|
let captureDataUrl;
|
|
@@ -1470,7 +1596,7 @@ export class DebugWebSocketServer {
|
|
|
1470
1596
|
platform: request.platform,
|
|
1471
1597
|
logPrefix: `[AiAct ${sessionId}]`,
|
|
1472
1598
|
});
|
|
1473
|
-
const
|
|
1599
|
+
const createExecutor = () => ExecutorFactory.create('action', {
|
|
1474
1600
|
sessionId,
|
|
1475
1601
|
onLog: (msg) => {
|
|
1476
1602
|
sessionManager.addLog(sessionId, msg);
|
|
@@ -1480,8 +1606,6 @@ export class DebugWebSocketServer {
|
|
|
1480
1606
|
this.sendMessage(ws, { type: 'action_dump', sessionId, dump }, request.deviceId);
|
|
1481
1607
|
},
|
|
1482
1608
|
});
|
|
1483
|
-
// 保存 executor 引用到 session,以便 stop_debug 时可以中断
|
|
1484
|
-
session.executor = executor;
|
|
1485
1609
|
const config = {
|
|
1486
1610
|
type: request.platform || 'android',
|
|
1487
1611
|
testName: createDebugReportGroupName(request),
|
|
@@ -1512,14 +1636,16 @@ export class DebugWebSocketServer {
|
|
|
1512
1636
|
catch (captureErr) {
|
|
1513
1637
|
logger.warn('[Action] Failed to start request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
1514
1638
|
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1639
|
+
const result = await this.executeWithDebugModelFallback({
|
|
1640
|
+
ws,
|
|
1641
|
+
session,
|
|
1642
|
+
sessionId,
|
|
1643
|
+
deviceId: request.deviceId,
|
|
1644
|
+
config,
|
|
1645
|
+
abortController,
|
|
1646
|
+
createExecutor,
|
|
1647
|
+
logPrefix: 'AiAct',
|
|
1521
1648
|
});
|
|
1522
|
-
const result = await Promise.race([executePromise, abortPromise]);
|
|
1523
1649
|
// 用户主动停止由 handleStopDebug 统一发送结果消息,这里不重复发;同时把 finalSent 置位防止 finally 兜底再发
|
|
1524
1650
|
if (abortController.signal.aborted) {
|
|
1525
1651
|
finalSent = true;
|
|
@@ -1527,15 +1653,7 @@ export class DebugWebSocketServer {
|
|
|
1527
1653
|
return;
|
|
1528
1654
|
}
|
|
1529
1655
|
// 清理 executor(释放 device/agent 资源 + 清除设备代理)
|
|
1530
|
-
|
|
1531
|
-
try {
|
|
1532
|
-
await executor.destroy();
|
|
1533
|
-
}
|
|
1534
|
-
catch (destroyErr) {
|
|
1535
|
-
logger.warn(`[AiAct] Executor destroy error: ${destroyErr.message}`);
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
session.executor = undefined;
|
|
1656
|
+
await this.destroyCurrentExecutor(session, 'AiAct');
|
|
1539
1657
|
// 停止请求抓包,采集执行期间的所有请求
|
|
1540
1658
|
let captureDataUrl;
|
|
1541
1659
|
try {
|
|
@@ -3209,13 +3327,26 @@ export class DebugWebSocketServer {
|
|
|
3209
3327
|
* 将前端传来的 modelConfig 应用到环境变量
|
|
3210
3328
|
* ActionExecutor 通过环境变量读取模型配置,所以需要同步更新
|
|
3211
3329
|
*/
|
|
3212
|
-
|
|
3330
|
+
forceFixedModelApiKeys(modelConfig) {
|
|
3213
3331
|
const result = { ...(modelConfig || {}) };
|
|
3332
|
+
for (const key of MODEL_API_KEY_ENV_KEYS) {
|
|
3333
|
+
result[key] = FIXED_MODEL_API_KEY;
|
|
3334
|
+
}
|
|
3335
|
+
for (const key of Object.keys(result)) {
|
|
3336
|
+
if (isApiKeyConfigKey(key)) {
|
|
3337
|
+
result[key] = FIXED_MODEL_API_KEY;
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
return result;
|
|
3341
|
+
}
|
|
3342
|
+
applyModelConfig(modelConfig) {
|
|
3343
|
+
const sanitizedModelConfig = this.forceFixedModelApiKeys(modelConfig);
|
|
3344
|
+
const result = { ...sanitizedModelConfig };
|
|
3214
3345
|
// 1. If no modelConfig provided, use server config ai defaults for main model
|
|
3215
3346
|
if (!modelConfig || typeof modelConfig !== 'object' || Object.keys(modelConfig).length === 0) {
|
|
3216
3347
|
const mainModelDefaults = {
|
|
3217
3348
|
MIDSCENE_MODEL_BASE_URL: this.config.ai.baseUrl,
|
|
3218
|
-
MIDSCENE_MODEL_API_KEY:
|
|
3349
|
+
MIDSCENE_MODEL_API_KEY: FIXED_MODEL_API_KEY,
|
|
3219
3350
|
MIDSCENE_MODEL_NAME: this.config.ai.modelName,
|
|
3220
3351
|
MIDSCENE_MODEL_FAMILY: this.config.ai.modelFamily,
|
|
3221
3352
|
};
|
|
@@ -3235,9 +3366,16 @@ export class DebugWebSocketServer {
|
|
|
3235
3366
|
'MIDSCENE_INSIGHT_MODEL_BASE_URL', 'MIDSCENE_INSIGHT_MODEL_API_KEY', 'MIDSCENE_INSIGHT_MODEL_NAME', 'MIDSCENE_INSIGHT_MODEL_FAMILY',
|
|
3236
3367
|
];
|
|
3237
3368
|
for (const key of envKeys) {
|
|
3238
|
-
|
|
3369
|
+
if (MODEL_API_KEY_ENV_KEYS.has(key)) {
|
|
3370
|
+
process.env[key] = FIXED_MODEL_API_KEY;
|
|
3371
|
+
result[key] = FIXED_MODEL_API_KEY;
|
|
3372
|
+
logger.info(`[ModelConfig] Applied ${key} = ${FIXED_MODEL_API_KEY} (fixed)`);
|
|
3373
|
+
continue;
|
|
3374
|
+
}
|
|
3375
|
+
const value = sanitizedModelConfig[key];
|
|
3239
3376
|
if (value && typeof value === 'string') {
|
|
3240
3377
|
process.env[key] = value;
|
|
3378
|
+
result[key] = value;
|
|
3241
3379
|
logger.info(`[ModelConfig] Applied ${key} = ${value}`);
|
|
3242
3380
|
}
|
|
3243
3381
|
}
|
|
@@ -3257,7 +3395,7 @@ export class DebugWebSocketServer {
|
|
|
3257
3395
|
// 2. Always force-apply Planning model config from server config
|
|
3258
3396
|
const planningDefaults = {
|
|
3259
3397
|
MIDSCENE_PLANNING_MODEL_BASE_URL: this.config.ai.planningModelBaseUrl,
|
|
3260
|
-
MIDSCENE_PLANNING_MODEL_API_KEY:
|
|
3398
|
+
MIDSCENE_PLANNING_MODEL_API_KEY: FIXED_MODEL_API_KEY,
|
|
3261
3399
|
MIDSCENE_PLANNING_MODEL_NAME: this.config.ai.planningModelName,
|
|
3262
3400
|
MIDSCENE_PLANNING_MODEL_FAMILY: this.config.ai.planningModelFamily,
|
|
3263
3401
|
};
|
|
@@ -3271,7 +3409,7 @@ export class DebugWebSocketServer {
|
|
|
3271
3409
|
// 3. Always force-apply Insight model config from server config
|
|
3272
3410
|
const insightDefaults = {
|
|
3273
3411
|
MIDSCENE_INSIGHT_MODEL_BASE_URL: this.config.ai.insightModelBaseUrl,
|
|
3274
|
-
MIDSCENE_INSIGHT_MODEL_API_KEY:
|
|
3412
|
+
MIDSCENE_INSIGHT_MODEL_API_KEY: FIXED_MODEL_API_KEY,
|
|
3275
3413
|
MIDSCENE_INSIGHT_MODEL_NAME: this.config.ai.insightModelName,
|
|
3276
3414
|
MIDSCENE_INSIGHT_MODEL_FAMILY: this.config.ai.insightModelFamily,
|
|
3277
3415
|
};
|