@aiscene/aiserver 1.9.6 → 1.9.7

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.
@@ -1,4 +1,5 @@
1
1
  import type { DebugSession } from '../core/types.js';
2
+ import type { BatchActionRequest } from './types.js';
2
3
  import type { AIServerConfig } from '../config/schema.js';
3
4
  export declare class DebugWebSocketServer {
4
5
  private wss;
@@ -6,6 +7,8 @@ export declare class DebugWebSocketServer {
6
7
  private port;
7
8
  private isStarted;
8
9
  private activeBatchSessionIds;
10
+ private createHeadlessBatchClient;
11
+ runBatchActionInBackground(request: BatchActionRequest): void;
9
12
  private taskLogSubscribers;
10
13
  private clientTaskSubscriptions;
11
14
  /** 当前已启动浏览器的 web session 集合(用于并发计数与 TTL 扫描) */
@@ -53,6 +56,8 @@ export declare class DebugWebSocketServer {
53
56
  private handleUnsubscribeTaskLogs;
54
57
  /** 判断 naturalLanguage 是否为可执行代码(而非自然语言描述) */
55
58
  private isCodeScript;
59
+ private resolveScriptReferences;
60
+ private resolveActionParamsReferences;
56
61
  private handleStartDebug;
57
62
  /**
58
63
  * Web 调试主进程内嵌执行
@@ -1 +1 @@
1
- {"version":3,"file":"websocket-server.d.ts","sourceRoot":"","sources":["../../src/debug/websocket-server.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAsBrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA0B1D,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,GAAG,CAAgC;IAC3C,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,qBAAqB,CAA0B;IAGvD,OAAO,CAAC,kBAAkB,CAA0C;IAEpE,OAAO,CAAC,uBAAuB,CAA0C;IAGzE,+CAA+C;IAC/C,OAAO,CAAC,mBAAmB,CAAkC;IAC7D,6DAA6D;IAC7D,OAAO,CAAC,wBAAwB,CAAgE;IAChG,6CAA6C;IAC7C,OAAO,CAAC,uBAAuB,CAA6E;IAC5G,gBAAgB;IAChB,OAAO,CAAC,iBAAiB,CAA+B;gBAE5C,IAAI,GAAE,MAAa;IAI/B,8CAA8C;IAC9C,OAAO,CAAC,eAAe;IAMvB,2CAA2C;IAC3C,OAAO,CAAC,kBAAkB;IAI1B,8BAA8B;IAC9B,OAAO,CAAC,oBAAoB;IAI5B,qCAAqC;IACrC,OAAO,CAAC,iBAAiB;IAenB,KAAK,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IA4ElD;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAmB5B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAexB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAcxB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAiB1B;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAUlC,OAAO,CAAC,uBAAuB;YAuDjB,aAAa;YAuDb,kBAAkB;IAwGhC,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,yBAAyB;IAWjC,4CAA4C;IAC5C,OAAO,CAAC,YAAY;YAMN,gBAAgB;IAiD9B;;;;;OAKG;YACW,mBAAmB;IAiQjC;;OAEG;YACW,gBAAgB;IAyC9B,6CAA6C;YAC/B,uBAAuB;IAkJrC,uCAAuC;YACzB,wBAAwB;YAwKxB,mBAAmB;YAuNnB,kBAAkB;IAgQhC,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,uBAAuB;IAY/B,OAAO,CAAC,gBAAgB;YAQV,oBAAoB;YAKpB,WAAW;YAmDX,sBAAsB;IAwfpC,OAAO,CAAC,qBAAqB,CAAsC;YAErD,iBAAiB;IAwM/B,OAAO,CAAC,oBAAoB;YAiBd,uBAAuB;IAqCrC,uBAAuB;YACT,mBAAmB;IAiPjC,qCAAqC;YACvB,mBAAmB;YA+InB,eAAe;YAiBf,qBAAqB;YA4BrB,oBAAoB;YAuBpB,eAAe;YAqDf,qBAAqB;YAqBrB,eAAe;YAgIf,aAAa;IAU3B;;OAEG;YACW,gBAAgB;IA2B9B;;;OAGG;IACH,OAAO,CAAC,gBAAgB;YAgFV,iBAAiB;IAiE/B,OAAO,CAAC,WAAW;IA2DnB,OAAO,CAAC,SAAS;IAIjB;;;OAGG;IAEH;;;OAGG;YACW,wBAAwB;YAqCxB,cAAc;IA4B5B,WAAW,IAAI,YAAY,EAAE;IAIvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAa7B"}
1
+ {"version":3,"file":"websocket-server.d.ts","sourceRoot":"","sources":["../../src/debug/websocket-server.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,KAAK,EAQV,kBAAkB,EAUnB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA2B1D,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,GAAG,CAAgC;IAC3C,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,qBAAqB,CAA0B;IAEvD,OAAO,CAAC,yBAAyB;IAajC,0BAA0B,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAY7D,OAAO,CAAC,kBAAkB,CAA0C;IAEpE,OAAO,CAAC,uBAAuB,CAA0C;IAGzE,+CAA+C;IAC/C,OAAO,CAAC,mBAAmB,CAAkC;IAC7D,6DAA6D;IAC7D,OAAO,CAAC,wBAAwB,CAAgE;IAChG,6CAA6C;IAC7C,OAAO,CAAC,uBAAuB,CAA6E;IAC5G,gBAAgB;IAChB,OAAO,CAAC,iBAAiB,CAA+B;gBAE5C,IAAI,GAAE,MAAa;IAI/B,8CAA8C;IAC9C,OAAO,CAAC,eAAe;IAMvB,2CAA2C;IAC3C,OAAO,CAAC,kBAAkB;IAI1B,8BAA8B;IAC9B,OAAO,CAAC,oBAAoB;IAI5B,qCAAqC;IACrC,OAAO,CAAC,iBAAiB;IAenB,KAAK,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IA4ElD;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAmB5B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAexB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAcxB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAiB1B;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAUlC,OAAO,CAAC,uBAAuB;YAuDjB,aAAa;YAuDb,kBAAkB;IAwGhC,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,yBAAyB;IAWjC,4CAA4C;IAC5C,OAAO,CAAC,YAAY;YAON,uBAAuB;YAQvB,6BAA6B;YAY7B,gBAAgB;IAkD9B;;;;;OAKG;YACW,mBAAmB;IAkQjC;;OAEG;YACW,gBAAgB;IAyC9B,6CAA6C;YAC/B,uBAAuB;IAkJrC,uCAAuC;YACzB,wBAAwB;YAwKxB,mBAAmB;YAyNnB,kBAAkB;IAkQhC,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,uBAAuB;IAY/B,OAAO,CAAC,gBAAgB;YAQV,oBAAoB;YAKpB,WAAW;YAmDX,sBAAsB;IA2fpC,OAAO,CAAC,qBAAqB,CAAsC;YAErD,iBAAiB;IA8M/B,OAAO,CAAC,oBAAoB;YAiBd,uBAAuB;IAqCrC,uBAAuB;YACT,mBAAmB;IAoPjC,qCAAqC;YACvB,mBAAmB;YAkJnB,eAAe;YAiBf,qBAAqB;YA4BrB,oBAAoB;YAuBpB,eAAe;YAqDf,qBAAqB;YAqBrB,eAAe;YAgIf,aAAa;IAU3B;;OAEG;YACW,gBAAgB;IA2B9B;;;OAGG;IACH,OAAO,CAAC,gBAAgB;YAgFV,iBAAiB;IAiE/B,OAAO,CAAC,WAAW;IA2DnB,OAAO,CAAC,SAAS;IAIjB;;;OAGG;IAEH;;;OAGG;YACW,wBAAwB;YAqCxB,cAAc;IA4B5B,WAAW,IAAI,YAAY,EAAE;IAIvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAa7B"}
@@ -21,6 +21,7 @@ import { applyAiMemory } from '../core/ai-memory.js';
21
21
  import { whistleManager } from '../proxy/whistle-manager.js';
22
22
  import { executeAndroidLogin, executeWebLogin, } from '../login/android-login-context.js';
23
23
  import { webRecordingManager } from '../recorder/web-recording-manager.js';
24
+ import { resolveTestCaseReferences } from '../task/test-case-reference-resolver.js';
24
25
  const logger = createLogger('DebugWebSocket');
25
26
  /** 获取本机可用的非内部 IPv4 地址,供客户端连接投屏 WebSocket */
26
27
  function getLocalIP() {
@@ -43,6 +44,29 @@ export class DebugWebSocketServer {
43
44
  port;
44
45
  isStarted = false;
45
46
  activeBatchSessionIds = new Set();
47
+ createHeadlessBatchClient(batchId) {
48
+ return {
49
+ OPEN: 1,
50
+ readyState: 1,
51
+ bufferedAmount: 0,
52
+ send: (_payload, callback) => {
53
+ if (typeof callback === 'function')
54
+ callback();
55
+ },
56
+ close: () => undefined,
57
+ terminate: () => undefined,
58
+ };
59
+ }
60
+ runBatchActionInBackground(request) {
61
+ if (!this.isStarted) {
62
+ throw new Error('Debug WebSocket server is not started');
63
+ }
64
+ const batchId = request?.batchId || 'unknown';
65
+ const ws = this.createHeadlessBatchClient(batchId);
66
+ void this.handleBatchAction(ws, request).catch((error) => {
67
+ logger.error(`[BatchAction] Background batch failed: batchId=${batchId}, error=${error.message}`);
68
+ });
69
+ }
46
70
  // 任务日志订阅: taskId -> Set of WebSocket clients
47
71
  taskLogSubscribers = new Map();
48
72
  // 记录客户端订阅的所有任务(用于断开时清理)
@@ -449,6 +473,24 @@ export class DebugWebSocketServer {
449
473
  // 包含 await/this.agent/this.sleep 等 JS 代码特征则视为代码
450
474
  return /\bawait\b/.test(text) || /\bthis\.(agent|sleep|page)\b/.test(text);
451
475
  }
476
+ async resolveScriptReferences(script) {
477
+ if (!script)
478
+ return script;
479
+ return resolveTestCaseReferences(script, {
480
+ apiUrl: this.config.task.apiUrl,
481
+ logger,
482
+ });
483
+ }
484
+ async resolveActionParamsReferences(actionParams) {
485
+ if (!actionParams || typeof actionParams.code !== 'string') {
486
+ return actionParams;
487
+ }
488
+ const resolvedCode = await this.resolveScriptReferences(actionParams.code);
489
+ return {
490
+ ...actionParams,
491
+ code: resolvedCode || actionParams.code,
492
+ };
493
+ }
452
494
  async handleStartDebug(ws, request) {
453
495
  // Web 分支:支持 sessionId 复用 + 主进程内嵌执行 + 投屏
454
496
  if (request.platform === 'web') {
@@ -460,6 +502,7 @@ export class DebugWebSocketServer {
460
502
  const session = sessionManager.create(sessionId, ws, request.deviceId);
461
503
  this.sendMessage(ws, { type: 'session_created', sessionId }, request.deviceId);
462
504
  try {
505
+ request.naturalLanguage = await this.resolveScriptReferences(request.naturalLanguage) || request.naturalLanguage;
463
506
  sessionManager.updateStatus(sessionId, 'running');
464
507
  this.sendMessage(ws, { type: 'debug_started', sessionId }, request.deviceId);
465
508
  await executeAndroidLogin({
@@ -536,6 +579,7 @@ export class DebugWebSocketServer {
536
579
  }
537
580
  this.sendMessage(ws, { type: 'session_created', sessionId });
538
581
  try {
582
+ request.naturalLanguage = await this.resolveScriptReferences(request.naturalLanguage) || request.naturalLanguage;
539
583
  sessionManager.updateStatus(sessionId, 'running');
540
584
  this.sendMessage(ws, { type: 'debug_started', sessionId, reused: isReuse });
541
585
  // 3) 首次启动:申请浏览器 + 创建 agent + 自动投屏
@@ -1098,6 +1142,7 @@ export class DebugWebSocketServer {
1098
1142
  try {
1099
1143
  sessionManager.updateStatus(sessionId, 'running');
1100
1144
  this.sendMessage(ws, { type: 'action_started', sessionId, actionType: request.actionType }, request.deviceId);
1145
+ const resolvedActionParams = await this.resolveActionParamsReferences(request.actionParams);
1101
1146
  await executeAndroidLogin({
1102
1147
  source: request,
1103
1148
  deviceId: request.deviceId,
@@ -1124,7 +1169,7 @@ export class DebugWebSocketServer {
1124
1169
  testName: createDebugReportGroupName(request),
1125
1170
  actionType: request.actionType,
1126
1171
  locatePrompt: request.locatePrompt,
1127
- actionParams: request.actionParams,
1172
+ actionParams: resolvedActionParams,
1128
1173
  udid: request.deviceId,
1129
1174
  packageName: request.packageName,
1130
1175
  testUrl: request.testUrl,
@@ -1371,6 +1416,7 @@ export class DebugWebSocketServer {
1371
1416
  try {
1372
1417
  sessionManager.updateStatus(sessionId, 'running');
1373
1418
  this.sendMessage(ws, { type: 'ai_act_started', sessionId }, request.deviceId);
1419
+ const resolvedNaturalLanguage = await this.resolveScriptReferences(request.naturalLanguage);
1374
1420
  await executeAndroidLogin({
1375
1421
  source: request,
1376
1422
  deviceId: request.deviceId,
@@ -1393,7 +1439,7 @@ export class DebugWebSocketServer {
1393
1439
  type: request.platform || 'android',
1394
1440
  testName: createDebugReportGroupName(request),
1395
1441
  actionType: 'aiAct',
1396
- naturalLanguage: request.naturalLanguage,
1442
+ naturalLanguage: resolvedNaturalLanguage,
1397
1443
  udid: request.deviceId,
1398
1444
  packageName: request.packageName,
1399
1445
  modelConfig: fullModelConfig,
@@ -1633,6 +1679,8 @@ export class DebugWebSocketServer {
1633
1679
  }
1634
1680
  try {
1635
1681
  this.sendMessage(ws, { type: 'web_action_started', sessionId, actionType: request.actionType });
1682
+ const resolvedNaturalLanguage = await this.resolveScriptReferences(request.naturalLanguage);
1683
+ const resolvedActionParams = await this.resolveActionParamsReferences(request.actionParams);
1636
1684
  // 始终启动请求抓包
1637
1685
  try {
1638
1686
  await whistleManager.startCapture(sessionId);
@@ -1860,7 +1908,7 @@ export class DebugWebSocketServer {
1860
1908
  break;
1861
1909
  }
1862
1910
  case 'aiAction': {
1863
- const text = request.naturalLanguage || request.locatePrompt || '';
1911
+ const text = resolvedNaturalLanguage || request.locatePrompt || '';
1864
1912
  if (!text)
1865
1913
  throw new Error('aiAction requires naturalLanguage or locatePrompt');
1866
1914
  result = await agent.aiAct(text);
@@ -1899,7 +1947,7 @@ export class DebugWebSocketServer {
1899
1947
  }
1900
1948
  case 'runCode': {
1901
1949
  // 支持两种方式传递代码:request.code 或 request.actionParams.code
1902
- const code = request.actionParams?.code || request.code || '';
1950
+ const code = resolvedActionParams?.code || request.code || '';
1903
1951
  if (!code)
1904
1952
  throw new Error('runCode requires actionParams.code');
1905
1953
  // 行号探针:与真机 runCode 一致,通过 instrumentCode 在每条语句前插入 __lineCallback(line),
@@ -2286,7 +2334,12 @@ export class DebugWebSocketServer {
2286
2334
  callbackOk: callbackFailures.length === 0,
2287
2335
  callbackFailures: callbackFailures.slice(-10),
2288
2336
  });
2289
- await this.continueNextLoopBatches(ws, request, finalizeCallback.data);
2337
+ try {
2338
+ await this.continueNextLoopBatches(ws, request, finalizeCallback.data);
2339
+ }
2340
+ catch (error) {
2341
+ logger.error(`[BatchAction] Continue next loop batch failed after finalize: batchId=${batchId}, error=${error.message}`);
2342
+ }
2290
2343
  }
2291
2344
  }
2292
2345
  getNextBatchRequests(callbackData) {
@@ -2342,14 +2395,16 @@ export class DebugWebSocketServer {
2342
2395
  if (!targetUrl) {
2343
2396
  throw new Error('Web 用例缺少测试地址,请先在用例中补充测试地址后再执行');
2344
2397
  }
2398
+ const resolvedNaturalLanguage = await this.resolveScriptReferences(tc.naturalLanguage);
2399
+ const resolvedActionParams = await this.resolveActionParamsReferences(tc.actionParams);
2345
2400
  // 构造单条 WebActionExecuteRequest
2346
2401
  const webActionRequest = {
2347
2402
  type: 'execute_web_action',
2348
2403
  actionType: tc.actionType || 'aiAction',
2349
2404
  url: targetUrl,
2350
2405
  testUrl: targetUrl,
2351
- naturalLanguage: tc.naturalLanguage,
2352
- actionParams: tc.actionParams,
2406
+ naturalLanguage: resolvedNaturalLanguage,
2407
+ actionParams: resolvedActionParams,
2353
2408
  sessionId: sharedSessionId || undefined,
2354
2409
  mobileMode: request.mobileMode,
2355
2410
  deviceName: request.deviceName,
@@ -2525,7 +2580,7 @@ export class DebugWebSocketServer {
2525
2580
  // 执行动作
2526
2581
  const actionType = tc.actionType || 'aiAction';
2527
2582
  if (actionType === 'aiAction' || actionType === 'aiAct') {
2528
- const text = tc.naturalLanguage || '';
2583
+ const text = resolvedNaturalLanguage || '';
2529
2584
  if (!text)
2530
2585
  throw new Error('aiAction requires naturalLanguage');
2531
2586
  await agent.aiAct(text);
@@ -2536,7 +2591,7 @@ export class DebugWebSocketServer {
2536
2591
  await this.safeWebGoto(page, targetUrl, `${caseLabel} goto`);
2537
2592
  }
2538
2593
  else if (actionType === 'runCode') {
2539
- const code = tc.actionParams?.code || '';
2594
+ const code = resolvedActionParams?.code || '';
2540
2595
  if (!code)
2541
2596
  throw new Error('runCode requires actionParams.code');
2542
2597
  const AsyncFunctionCtor = async function () { }.constructor;
@@ -2631,15 +2686,17 @@ export class DebugWebSocketServer {
2631
2686
  catch (captureErr) {
2632
2687
  logger.warn(`[BatchAction] Failed to start capture: ${captureErr.message}`);
2633
2688
  }
2689
+ const resolvedNaturalLanguage = await this.resolveScriptReferences(tc.naturalLanguage);
2690
+ const resolvedActionParams = await this.resolveActionParamsReferences(tc.actionParams);
2634
2691
  // 构造 ExecuteConfig
2635
2692
  const actionType = tc.actionType || 'aiAct';
2636
2693
  const config = {
2637
2694
  type: platform,
2638
2695
  testName: tc.title || tc.id || `batch-${batchId}-case-${caseIndex + 1}`,
2639
2696
  actionType,
2640
- naturalLanguage: tc.naturalLanguage,
2641
- locatePrompt: tc.actionParams?.locatePrompt,
2642
- actionParams: tc.actionParams,
2697
+ naturalLanguage: resolvedNaturalLanguage,
2698
+ locatePrompt: resolvedActionParams?.locatePrompt,
2699
+ actionParams: resolvedActionParams,
2643
2700
  udid: effectiveDeviceId,
2644
2701
  packageName: effectivePackageName,
2645
2702
  bundleId: effectiveBundleId,
@@ -3083,7 +3140,7 @@ export class DebugWebSocketServer {
3083
3140
  const token = request.callback?.token?.trim();
3084
3141
  if (token)
3085
3142
  headers.Authorization = token.startsWith('Bearer ') ? token : `Bearer ${token}`;
3086
- const timeoutMs = Math.max(1000, Math.min(Number(request.callback?.timeoutMs || 10000) || 10000, 60000));
3143
+ const timeoutMs = Math.max(1000, Math.min(Number(request.callback?.timeoutMs || 10000) || 10000, 600000));
3087
3144
  const maxAttempts = 2;
3088
3145
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
3089
3146
  const controller = new AbortController();