@aiscene/aiserver 1.7.1 → 1.7.2
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/core/adb.d.ts +10 -0
- package/dist/core/adb.d.ts.map +1 -0
- package/dist/core/adb.js +25 -0
- package/dist/core/adb.js.map +1 -0
- package/dist/core/android-identifiers.d.ts +3 -0
- package/dist/core/android-identifiers.d.ts.map +1 -0
- package/dist/core/android-identifiers.js +21 -0
- package/dist/core/android-identifiers.js.map +1 -0
- package/dist/core/native-swipe.d.ts.map +1 -1
- package/dist/core/native-swipe.js +3 -6
- package/dist/core/native-swipe.js.map +1 -1
- package/dist/core/types.d.ts +13 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/debug/report-group-name.d.ts +14 -0
- package/dist/debug/report-group-name.d.ts.map +1 -0
- package/dist/debug/report-group-name.js +30 -0
- package/dist/debug/report-group-name.js.map +1 -0
- package/dist/debug/types.d.ts +55 -31
- package/dist/debug/types.d.ts.map +1 -1
- package/dist/debug/websocket-server.d.ts +1 -0
- package/dist/debug/websocket-server.d.ts.map +1 -1
- package/dist/debug/websocket-server.js +142 -9
- package/dist/debug/websocket-server.js.map +1 -1
- package/dist/device/detector.d.ts.map +1 -1
- package/dist/device/detector.js +2 -1
- package/dist/device/detector.js.map +1 -1
- package/dist/device/heartbeat.d.ts.map +1 -1
- package/dist/device/heartbeat.js +2 -4
- package/dist/device/heartbeat.js.map +1 -1
- package/dist/executor/action-executor.d.ts.map +1 -1
- package/dist/executor/action-executor.js +22 -22
- 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 +22 -19
- 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 +6 -0
- package/dist/executor/cli-executor.js.map +1 -1
- package/dist/login/android-login-context.d.ts +29 -0
- package/dist/login/android-login-context.d.ts.map +1 -0
- package/dist/login/android-login-context.js +165 -0
- package/dist/login/android-login-context.js.map +1 -0
- package/dist/recorder/android-login-replay.d.ts +11 -0
- package/dist/recorder/android-login-replay.d.ts.map +1 -0
- package/dist/recorder/android-login-replay.js +70 -0
- package/dist/recorder/android-login-replay.js.map +1 -0
- package/dist/recorder/android-login-script.d.ts +50 -0
- package/dist/recorder/android-login-script.d.ts.map +1 -0
- package/dist/recorder/android-login-script.js +2 -0
- package/dist/recorder/android-login-script.js.map +1 -0
- package/dist/recorder/android-recording-manager.d.ts +23 -0
- package/dist/recorder/android-recording-manager.d.ts.map +1 -0
- package/dist/recorder/android-recording-manager.js +180 -0
- package/dist/recorder/android-recording-manager.js.map +1 -0
- package/dist/recorder/android-ui-hierarchy.d.ts +45 -0
- package/dist/recorder/android-ui-hierarchy.d.ts.map +1 -0
- package/dist/recorder/android-ui-hierarchy.js +183 -0
- package/dist/recorder/android-ui-hierarchy.js.map +1 -0
- package/dist/recorder/web-login-replay.d.ts +9 -0
- package/dist/recorder/web-login-replay.d.ts.map +1 -0
- package/dist/recorder/web-login-replay.js +39 -0
- package/dist/recorder/web-login-replay.js.map +1 -0
- package/dist/recorder/web-login-script.d.ts +36 -0
- package/dist/recorder/web-login-script.d.ts.map +1 -0
- package/dist/recorder/web-login-script.js +2 -0
- package/dist/recorder/web-login-script.js.map +1 -0
- package/dist/recorder/web-recording-manager.d.ts +41 -0
- package/dist/recorder/web-recording-manager.d.ts.map +1 -0
- package/dist/recorder/web-recording-manager.js +226 -0
- package/dist/recorder/web-recording-manager.js.map +1 -0
- package/dist/scrcpy/server.d.ts.map +1 -1
- package/dist/scrcpy/server.js +213 -11
- package/dist/scrcpy/server.js.map +1 -1
- package/dist/task/poller.d.ts.map +1 -1
- package/dist/task/poller.js +8 -1
- package/dist/task/poller.js.map +1 -1
- package/dist/task/scheduler.d.ts.map +1 -1
- package/dist/task/scheduler.js +49 -0
- package/dist/task/scheduler.js.map +1 -1
- package/dist/task/types.d.ts.map +1 -1
- package/dist/task/types.js +35 -1
- package/dist/task/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ import { WebSocketServer } from 'ws';
|
|
|
2
2
|
import { fork } from 'child_process';
|
|
3
3
|
import { URL } from 'url';
|
|
4
4
|
import os from 'os';
|
|
5
|
+
import { randomUUID } from 'crypto';
|
|
5
6
|
import { uploadReportToServer, uploadCapturePackageFile } from '../api/callback.js';
|
|
6
7
|
import fs from 'fs';
|
|
7
8
|
import path from 'path';
|
|
@@ -9,6 +10,7 @@ import { createLogger } from '../core/logger.js';
|
|
|
9
10
|
import { eventBus } from '../core/event-bus.js';
|
|
10
11
|
import { sessionManager } from './session-manager.js';
|
|
11
12
|
import { dumpManager } from './dump-manager.js';
|
|
13
|
+
import { createDebugReportGroupName } from './report-group-name.js';
|
|
12
14
|
import { screencastManager } from './screencast.js';
|
|
13
15
|
import { webScreencastManager } from './web-screencast.js';
|
|
14
16
|
import { webBrowserPool } from './web-browser-pool.js';
|
|
@@ -16,6 +18,8 @@ import { ExecutorFactory } from '../executor/executor-factory.js';
|
|
|
16
18
|
import { instrumentCode } from '../executor/code-instrument.js';
|
|
17
19
|
import { playwrightSwipe } from '../core/native-swipe.js';
|
|
18
20
|
import { whistleManager } from '../proxy/whistle-manager.js';
|
|
21
|
+
import { executeAndroidLogin, executeWebLogin, } from '../login/android-login-context.js';
|
|
22
|
+
import { webRecordingManager } from '../recorder/web-recording-manager.js';
|
|
19
23
|
const logger = createLogger('DebugWebSocket');
|
|
20
24
|
/** 获取本机可用的非内部 IPv4 地址,供客户端连接投屏 WebSocket */
|
|
21
25
|
function getLocalIP() {
|
|
@@ -248,6 +252,10 @@ export class DebugWebSocketServer {
|
|
|
248
252
|
});
|
|
249
253
|
ws.on('close', () => {
|
|
250
254
|
logger.info('[Control] Connection closed');
|
|
255
|
+
const recordingClientId = ws.recordingClientId;
|
|
256
|
+
if (recordingClientId) {
|
|
257
|
+
void webRecordingManager.cancel(recordingClientId);
|
|
258
|
+
}
|
|
251
259
|
for (const sessionId of sessionManager.findByClient(ws)) {
|
|
252
260
|
const s = sessionManager.get(sessionId);
|
|
253
261
|
// Web 调试会话:客户端断开不销毁浏览器/投屏,仅解绑 client,由 TTL 在 30 分钟无活动时自动回收
|
|
@@ -302,10 +310,86 @@ export class DebugWebSocketServer {
|
|
|
302
310
|
case 'unsubscribe_task_logs':
|
|
303
311
|
this.handleUnsubscribeTaskLogs(ws, message);
|
|
304
312
|
break;
|
|
313
|
+
case 'web_recording_start':
|
|
314
|
+
case 'web_recording_snapshot':
|
|
315
|
+
case 'web_recording_select':
|
|
316
|
+
case 'web_recording_input':
|
|
317
|
+
case 'web_recording_stop':
|
|
318
|
+
case 'web_recording_cancel':
|
|
319
|
+
case 'web_recording_replay':
|
|
320
|
+
await this.handleWebRecording(ws, message);
|
|
321
|
+
break;
|
|
305
322
|
default:
|
|
306
323
|
this.sendError(ws, `Unknown message type: ${message.type}`);
|
|
307
324
|
}
|
|
308
325
|
}
|
|
326
|
+
async handleWebRecording(ws, request) {
|
|
327
|
+
const clientId = (ws.recordingClientId ||= randomUUID());
|
|
328
|
+
try {
|
|
329
|
+
if (request.type === 'web_recording_start') {
|
|
330
|
+
if (!request.targetUrl)
|
|
331
|
+
throw new Error('targetUrl is required');
|
|
332
|
+
const script = await webRecordingManager.start(clientId, {
|
|
333
|
+
name: request.name,
|
|
334
|
+
targetUrl: request.targetUrl,
|
|
335
|
+
});
|
|
336
|
+
this.sendMessage(ws, { type: 'recording-started', script });
|
|
337
|
+
this.sendMessage(ws, {
|
|
338
|
+
type: 'recording-snapshot',
|
|
339
|
+
...(await webRecordingManager.snapshot(clientId)),
|
|
340
|
+
});
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (request.type === 'web_recording_snapshot') {
|
|
344
|
+
this.sendMessage(ws, {
|
|
345
|
+
type: 'recording-snapshot',
|
|
346
|
+
...(await webRecordingManager.snapshot(clientId)),
|
|
347
|
+
});
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (request.type === 'web_recording_select') {
|
|
351
|
+
const result = await webRecordingManager.select(clientId, Number(request.x), Number(request.y));
|
|
352
|
+
if (result.step) {
|
|
353
|
+
this.sendMessage(ws, { type: 'recording-step-added', step: result.step });
|
|
354
|
+
}
|
|
355
|
+
this.sendMessage(ws, {
|
|
356
|
+
type: 'recording-action-done',
|
|
357
|
+
action: 'tap',
|
|
358
|
+
passwordField: result.passwordField,
|
|
359
|
+
});
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (request.type === 'web_recording_input') {
|
|
363
|
+
const step = await webRecordingManager.input(clientId, String(request.text || ''), String(request.recordingValue || request.text || ''), request.secret === true);
|
|
364
|
+
this.sendMessage(ws, { type: 'recording-step-added', step });
|
|
365
|
+
this.sendMessage(ws, { type: 'recording-input-done', secret: request.secret === true });
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (request.type === 'web_recording_stop') {
|
|
369
|
+
const script = await webRecordingManager.stop(clientId);
|
|
370
|
+
this.sendMessage(ws, { type: 'recording-stopped', script });
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (request.type === 'web_recording_cancel') {
|
|
374
|
+
await webRecordingManager.cancel(clientId);
|
|
375
|
+
this.sendMessage(ws, { type: 'recording-cancelled' });
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (request.type === 'web_recording_replay') {
|
|
379
|
+
await webRecordingManager.replay({
|
|
380
|
+
script: request.script,
|
|
381
|
+
variables: request.variables,
|
|
382
|
+
});
|
|
383
|
+
this.sendMessage(ws, { type: 'recording-replay-completed' });
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
this.sendMessage(ws, {
|
|
388
|
+
type: 'recording-error',
|
|
389
|
+
message: error.message,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
309
393
|
// ==================== Task Log Subscription ====================
|
|
310
394
|
handleSubscribeTaskLogs(ws, request) {
|
|
311
395
|
this.subscribeTaskLog(ws, request.taskId);
|
|
@@ -344,6 +428,12 @@ export class DebugWebSocketServer {
|
|
|
344
428
|
try {
|
|
345
429
|
sessionManager.updateStatus(sessionId, 'running');
|
|
346
430
|
this.sendMessage(ws, { type: 'debug_started', sessionId }, request.deviceId);
|
|
431
|
+
await executeAndroidLogin({
|
|
432
|
+
source: request,
|
|
433
|
+
deviceId: request.deviceId,
|
|
434
|
+
platform: request.platform,
|
|
435
|
+
logPrefix: `[Debug ${sessionId}]`,
|
|
436
|
+
});
|
|
347
437
|
// 判断 naturalLanguage 是代码还是自然语言
|
|
348
438
|
const isCode = this.isCodeScript(request.naturalLanguage || '');
|
|
349
439
|
logger.info(`[Debug] naturalLanguage mode: ${isCode ? 'runCode' : 'aiAction'}, preview: ${(request.naturalLanguage || '').substring(0, 50)}...`);
|
|
@@ -377,6 +467,7 @@ export class DebugWebSocketServer {
|
|
|
377
467
|
async handleStartDebugWeb(ws, request) {
|
|
378
468
|
// 1) 决定 sessionId(客户端传了就用以复用)
|
|
379
469
|
const sessionId = request.sessionId || `debug-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
470
|
+
const reportGroupName = createDebugReportGroupName(request);
|
|
380
471
|
// 2) 判断是否复用:session 已存在且浏览器/agent 完整
|
|
381
472
|
let session = sessionManager.get(sessionId);
|
|
382
473
|
const isReuse = !!(session && session.browser && session.webAgent && session.page);
|
|
@@ -503,6 +594,7 @@ export class DebugWebSocketServer {
|
|
|
503
594
|
// modelConfig 走构造参数(与 handleExecuteWebAction 一致,避免多会话相互覆盖 env)
|
|
504
595
|
session.webAgent = new midscene.PlaywrightAgent(page, {
|
|
505
596
|
outputFormat: 'html-and-external-assets',
|
|
597
|
+
groupName: reportGroupName,
|
|
506
598
|
modelConfig: this.applyModelConfig(request.modelConfig),
|
|
507
599
|
});
|
|
508
600
|
// 注册并发池 + 启动 TTL 计时
|
|
@@ -520,7 +612,12 @@ export class DebugWebSocketServer {
|
|
|
520
612
|
wsUrl: `ws://${localIp}:${this.port}/screencast?sessionId=${encodeURIComponent(sessionId)}`,
|
|
521
613
|
});
|
|
522
614
|
}
|
|
523
|
-
|
|
615
|
+
await executeWebLogin({
|
|
616
|
+
source: request,
|
|
617
|
+
page,
|
|
618
|
+
logPrefix: `[WebDebug ${sessionId}]`,
|
|
619
|
+
});
|
|
620
|
+
// 兼容旧版表单登录配置(仅首次启动时执行)
|
|
524
621
|
if (request.loginConfig && request.loginConfig.loginUrl
|
|
525
622
|
&& request.loginConfig.loginUsername && request.loginConfig.loginPassword) {
|
|
526
623
|
const loginConfig = { ...request.loginConfig, mobileMode: request.mobileMode || false };
|
|
@@ -529,7 +626,13 @@ export class DebugWebSocketServer {
|
|
|
529
626
|
}
|
|
530
627
|
else {
|
|
531
628
|
logger.info(`[WebDebug] Reusing existing session: ${sessionId}`);
|
|
532
|
-
//
|
|
629
|
+
// 浏览器和页面继续复用,但每次请求使用新的 Agent,避免报告内容和名称串到上一次调试。
|
|
630
|
+
const midscene = await import('@aiscene/web/playwright');
|
|
631
|
+
session.webAgent = new midscene.PlaywrightAgent(session.page, {
|
|
632
|
+
outputFormat: 'html-and-external-assets',
|
|
633
|
+
groupName: reportGroupName,
|
|
634
|
+
modelConfig: this.applyModelConfig(request.modelConfig),
|
|
635
|
+
});
|
|
533
636
|
}
|
|
534
637
|
// 4) 复用/首启都要:刷新 TTL
|
|
535
638
|
this.touchWebSession(sessionId);
|
|
@@ -658,7 +761,7 @@ export class DebugWebSocketServer {
|
|
|
658
761
|
session.executor = executor;
|
|
659
762
|
const config = {
|
|
660
763
|
type: (request.platform === 'ios' ? 'ios' : 'android'),
|
|
661
|
-
testName:
|
|
764
|
+
testName: createDebugReportGroupName(request),
|
|
662
765
|
actionType: 'runCode',
|
|
663
766
|
actionParams: { code: request.naturalLanguage },
|
|
664
767
|
udid: request.deviceId,
|
|
@@ -667,10 +770,11 @@ export class DebugWebSocketServer {
|
|
|
667
770
|
url: request.url,
|
|
668
771
|
modelConfig: fullModelConfig,
|
|
669
772
|
executionId: sessionId,
|
|
773
|
+
skipAppRestart: request.skipAppRestart,
|
|
670
774
|
mobileMode: request.mobileMode,
|
|
671
775
|
deviceName: request.deviceName,
|
|
672
776
|
// 环境代理配置
|
|
673
|
-
proxyPort: request.proxyPort
|
|
777
|
+
proxyPort: request.proxyPort,
|
|
674
778
|
// 公司代理平台账号
|
|
675
779
|
proxyAccount: request.proxyAccount,
|
|
676
780
|
// 添加登录配置
|
|
@@ -783,7 +887,7 @@ export class DebugWebSocketServer {
|
|
|
783
887
|
const fullModelConfig = this.applyModelConfig(request.modelConfig);
|
|
784
888
|
const execConfig = {
|
|
785
889
|
type: (request.platform === 'ios' ? 'ios' : 'android'),
|
|
786
|
-
testName:
|
|
890
|
+
testName: createDebugReportGroupName(request),
|
|
787
891
|
url: request.url,
|
|
788
892
|
udid: request.deviceId,
|
|
789
893
|
packageName: request.packageName,
|
|
@@ -795,8 +899,9 @@ export class DebugWebSocketServer {
|
|
|
795
899
|
taskId: sessionId,
|
|
796
900
|
nodeId: this.config.task.nodeId,
|
|
797
901
|
modelConfig: fullModelConfig,
|
|
902
|
+
skipAppRestart: request.skipAppRestart,
|
|
798
903
|
// 环境代理配置
|
|
799
|
-
proxyPort: request.proxyPort
|
|
904
|
+
proxyPort: request.proxyPort,
|
|
800
905
|
// 公司代理平台账号
|
|
801
906
|
proxyAccount: request.proxyAccount,
|
|
802
907
|
// 添加登录配置
|
|
@@ -959,6 +1064,12 @@ export class DebugWebSocketServer {
|
|
|
959
1064
|
try {
|
|
960
1065
|
sessionManager.updateStatus(sessionId, 'running');
|
|
961
1066
|
this.sendMessage(ws, { type: 'action_started', sessionId, actionType: request.actionType }, request.deviceId);
|
|
1067
|
+
await executeAndroidLogin({
|
|
1068
|
+
source: request,
|
|
1069
|
+
deviceId: request.deviceId,
|
|
1070
|
+
platform: request.platform,
|
|
1071
|
+
logPrefix: `[Action ${sessionId}]`,
|
|
1072
|
+
});
|
|
962
1073
|
const executor = ExecutorFactory.create('action', {
|
|
963
1074
|
sessionId,
|
|
964
1075
|
onLog: (msg) => {
|
|
@@ -976,7 +1087,7 @@ export class DebugWebSocketServer {
|
|
|
976
1087
|
session.executor = executor;
|
|
977
1088
|
const config = {
|
|
978
1089
|
type: request.platform || 'android',
|
|
979
|
-
testName:
|
|
1090
|
+
testName: createDebugReportGroupName(request),
|
|
980
1091
|
actionType: request.actionType,
|
|
981
1092
|
locatePrompt: request.locatePrompt,
|
|
982
1093
|
actionParams: request.actionParams,
|
|
@@ -1223,6 +1334,12 @@ export class DebugWebSocketServer {
|
|
|
1223
1334
|
try {
|
|
1224
1335
|
sessionManager.updateStatus(sessionId, 'running');
|
|
1225
1336
|
this.sendMessage(ws, { type: 'ai_act_started', sessionId }, request.deviceId);
|
|
1337
|
+
await executeAndroidLogin({
|
|
1338
|
+
source: request,
|
|
1339
|
+
deviceId: request.deviceId,
|
|
1340
|
+
platform: request.platform,
|
|
1341
|
+
logPrefix: `[AiAct ${sessionId}]`,
|
|
1342
|
+
});
|
|
1226
1343
|
const executor = ExecutorFactory.create('action', {
|
|
1227
1344
|
sessionId,
|
|
1228
1345
|
onLog: (msg) => {
|
|
@@ -1237,7 +1354,7 @@ export class DebugWebSocketServer {
|
|
|
1237
1354
|
session.executor = executor;
|
|
1238
1355
|
const config = {
|
|
1239
1356
|
type: request.platform || 'android',
|
|
1240
|
-
testName:
|
|
1357
|
+
testName: createDebugReportGroupName(request),
|
|
1241
1358
|
actionType: 'aiAct',
|
|
1242
1359
|
naturalLanguage: request.naturalLanguage,
|
|
1243
1360
|
udid: request.deviceId,
|
|
@@ -1434,6 +1551,7 @@ export class DebugWebSocketServer {
|
|
|
1434
1551
|
}
|
|
1435
1552
|
async handleExecuteWebAction(ws, request) {
|
|
1436
1553
|
const sessionId = request.sessionId || `web-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
1554
|
+
const reportGroupName = createDebugReportGroupName(request);
|
|
1437
1555
|
let session = sessionManager.get(sessionId);
|
|
1438
1556
|
const isNewSession = !session;
|
|
1439
1557
|
if (isNewSession) {
|
|
@@ -1546,6 +1664,7 @@ export class DebugWebSocketServer {
|
|
|
1546
1664
|
// 因为 fork 子进程必须通过 env 透传配置,不影响这里。
|
|
1547
1665
|
session.webAgent = new midscene.PlaywrightAgent(page, {
|
|
1548
1666
|
outputFormat: 'html-and-external-assets',
|
|
1667
|
+
groupName: reportGroupName,
|
|
1549
1668
|
modelConfig: this.applyModelConfig(request.modelConfig),
|
|
1550
1669
|
});
|
|
1551
1670
|
// 注册到 web session 并发池,开始计入并发数 + TTL
|
|
@@ -1564,12 +1683,26 @@ export class DebugWebSocketServer {
|
|
|
1564
1683
|
});
|
|
1565
1684
|
}
|
|
1566
1685
|
}
|
|
1686
|
+
else {
|
|
1687
|
+
// 允许复用已有浏览器页面,但本次操作必须生成独立报告。
|
|
1688
|
+
const midscene = await import('@aiscene/web/playwright');
|
|
1689
|
+
session.webAgent = new midscene.PlaywrightAgent(session.page, {
|
|
1690
|
+
outputFormat: 'html-and-external-assets',
|
|
1691
|
+
groupName: reportGroupName,
|
|
1692
|
+
modelConfig: this.applyModelConfig(request.modelConfig),
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1567
1695
|
// 取出 agent / page
|
|
1568
1696
|
const agent = session.webAgent;
|
|
1569
1697
|
const page = session.page;
|
|
1570
1698
|
// 刷新 web session 活跃时间,避免 TTL 误杀
|
|
1571
1699
|
this.touchWebSession(sessionId);
|
|
1572
|
-
|
|
1700
|
+
await executeWebLogin({
|
|
1701
|
+
source: request,
|
|
1702
|
+
page,
|
|
1703
|
+
logPrefix: `[WebAction ${sessionId}]`,
|
|
1704
|
+
});
|
|
1705
|
+
// 兼容旧版表单登录配置
|
|
1573
1706
|
if (request.loginConfig && request.loginConfig.loginUrl && request.loginConfig.loginUsername && request.loginConfig.loginPassword) {
|
|
1574
1707
|
const loginConfig = {
|
|
1575
1708
|
...request.loginConfig,
|