@aiscene/aiserver 1.8.2 → 1.8.3

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.
Files changed (79) hide show
  1. package/dist/api/callback.d.ts.map +1 -1
  2. package/dist/api/callback.js +5 -0
  3. package/dist/api/callback.js.map +1 -1
  4. package/dist/api/device-api.d.ts.map +1 -1
  5. package/dist/api/device-api.js +9 -2
  6. package/dist/api/device-api.js.map +1 -1
  7. package/dist/api/task-api.d.ts.map +1 -1
  8. package/dist/api/task-api.js +6 -1
  9. package/dist/api/task-api.js.map +1 -1
  10. package/dist/auth/browser-auth.d.ts +4 -0
  11. package/dist/auth/browser-auth.d.ts.map +1 -0
  12. package/dist/auth/browser-auth.js +59 -0
  13. package/dist/auth/browser-auth.js.map +1 -0
  14. package/dist/auth/node-auth.d.ts +27 -0
  15. package/dist/auth/node-auth.d.ts.map +1 -0
  16. package/dist/auth/node-auth.js +132 -0
  17. package/dist/auth/node-auth.js.map +1 -0
  18. package/dist/config/cli.d.ts +12 -0
  19. package/dist/config/cli.d.ts.map +1 -1
  20. package/dist/config/cli.js +59 -2
  21. package/dist/config/cli.js.map +1 -1
  22. package/dist/config/index.d.ts.map +1 -1
  23. package/dist/config/index.js +32 -10
  24. package/dist/config/index.js.map +1 -1
  25. package/dist/config/schema.d.ts +3 -0
  26. package/dist/config/schema.d.ts.map +1 -1
  27. package/dist/core/types.d.ts +15 -0
  28. package/dist/core/types.d.ts.map +1 -1
  29. package/dist/debug/types.d.ts +2 -0
  30. package/dist/debug/types.d.ts.map +1 -1
  31. package/dist/debug/web-screencast.d.ts.map +1 -1
  32. package/dist/debug/web-screencast.js +0 -6
  33. package/dist/debug/web-screencast.js.map +1 -1
  34. package/dist/debug/websocket-server.d.ts +6 -3
  35. package/dist/debug/websocket-server.d.ts.map +1 -1
  36. package/dist/debug/websocket-server.js +137 -103
  37. package/dist/debug/websocket-server.js.map +1 -1
  38. package/dist/device/heartbeat.d.ts.map +1 -1
  39. package/dist/device/heartbeat.js +9 -2
  40. package/dist/device/heartbeat.js.map +1 -1
  41. package/dist/executor/action-executor.d.ts.map +1 -1
  42. package/dist/executor/action-executor.js +6 -0
  43. package/dist/executor/action-executor.js.map +1 -1
  44. package/dist/executor/android-executor.d.ts +3 -0
  45. package/dist/executor/android-executor.d.ts.map +1 -1
  46. package/dist/executor/android-executor.js +51 -0
  47. package/dist/executor/android-executor.js.map +1 -1
  48. package/dist/executor/cli-executor.d.ts +3 -1
  49. package/dist/executor/cli-executor.d.ts.map +1 -1
  50. package/dist/executor/cli-executor.js +59 -77
  51. package/dist/executor/cli-executor.js.map +1 -1
  52. package/dist/executor/ios-executor.d.ts.map +1 -1
  53. package/dist/executor/ios-executor.js +4 -0
  54. package/dist/executor/ios-executor.js.map +1 -1
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +79 -27
  57. package/dist/index.js.map +1 -1
  58. package/dist/login/android-login-context.d.ts +1 -0
  59. package/dist/login/android-login-context.d.ts.map +1 -1
  60. package/dist/login/android-login-context.js +79 -14
  61. package/dist/login/android-login-context.js.map +1 -1
  62. package/dist/node/service.d.ts.map +1 -1
  63. package/dist/node/service.js +13 -3
  64. package/dist/node/service.js.map +1 -1
  65. package/dist/task/execution-profile.d.ts +32 -0
  66. package/dist/task/execution-profile.d.ts.map +1 -0
  67. package/dist/task/execution-profile.js +227 -0
  68. package/dist/task/execution-profile.js.map +1 -0
  69. package/dist/task/scheduler.d.ts +1 -0
  70. package/dist/task/scheduler.d.ts.map +1 -1
  71. package/dist/task/scheduler.js +28 -27
  72. package/dist/task/scheduler.js.map +1 -1
  73. package/dist/task/types.js +1 -0
  74. package/dist/task/types.js.map +1 -1
  75. package/dist/web/server.d.ts +6 -1
  76. package/dist/web/server.d.ts.map +1 -1
  77. package/dist/web/server.js +141 -9
  78. package/dist/web/server.js.map +1 -1
  79. package/package.json +2 -2
@@ -478,10 +478,10 @@ export class DebugWebSocketServer {
478
478
  }
479
479
  }
480
480
  /**
481
- * Web 调试主进程内嵌执行(支持 sessionId 复用 + 浏览器/投屏保留 + 30 分钟 TTL)
481
+ * Web 调试主进程内嵌执行
482
482
  * - 首次请求:懒启动浏览器 + PlaywrightAgent + 自动投屏
483
483
  * - 复用请求:跳过初始化,直接执行 naturalLanguage
484
- * - 执行完不关浏览器,由 startWebSessionGc 按 TTL 自动回收
484
+ * - 执行完成后自动关闭浏览器,释放资源
485
485
  */
486
486
  async handleStartDebugWeb(ws, request) {
487
487
  // 1) 决定 sessionId(客户端传了就用以复用)
@@ -597,11 +597,19 @@ export class DebugWebSocketServer {
597
597
  const contextOptions = {
598
598
  viewport: request.viewport || { width: 1280, height: 800 },
599
599
  };
600
- const acquired = await webBrowserPool.acquireContext(contextOptions);
601
- browser = acquired.browser;
602
- context = acquired.context;
600
+ browser = await playwright.chromium.launch({ headless: false });
601
+ context = await browser.newContext(contextOptions);
603
602
  page = await context.newPage();
604
- releaseBrowser = acquired.release;
603
+ releaseBrowser = async () => {
604
+ try {
605
+ await context.close();
606
+ }
607
+ catch { /* ignore */ }
608
+ try {
609
+ await browser.close();
610
+ }
611
+ catch { /* ignore */ }
612
+ };
605
613
  }
606
614
  session.browser = browser;
607
615
  session.context = context;
@@ -636,12 +644,6 @@ export class DebugWebSocketServer {
636
644
  page,
637
645
  logPrefix: `[WebDebug ${sessionId}]`,
638
646
  });
639
- // 兼容旧版表单登录配置(仅首次启动时执行)
640
- if (request.loginConfig && request.loginConfig.loginUrl
641
- && request.loginConfig.loginUsername && request.loginConfig.loginPassword) {
642
- const loginConfig = { ...request.loginConfig, mobileMode: request.mobileMode || false };
643
- await this.performWebLogin(page, loginConfig);
644
- }
645
647
  }
646
648
  else {
647
649
  logger.info(`[WebDebug] Reusing existing session: ${sessionId}`);
@@ -660,7 +662,7 @@ export class DebugWebSocketServer {
660
662
  // 5) 若指定了 url,则导航(首启或复用,传了就 goto)
661
663
  if (request.url) {
662
664
  try {
663
- await page.goto(request.url, { waitUntil: 'networkidle' });
665
+ await this.safeWebGoto(page, request.url, `[WebDebug] Navigate to ${request.url}`);
664
666
  logger.info(`[WebDebug] Navigated to: ${request.url}`);
665
667
  }
666
668
  catch (gotoErr) {
@@ -697,7 +699,7 @@ export class DebugWebSocketServer {
697
699
  logger.info(`[WebDebug] report uploaded: reportUrl=${reportUrl || 'null'}, reportFile=${reportFile}`);
698
700
  const success = !execError;
699
701
  sessionManager.updateStatus(sessionId, success ? 'completed' : 'failed');
700
- // ★ 关键:不调用 closeWebSession(),浏览器与投屏完全保留,等 TTL 自然回收
702
+ // 执行完成后自动关闭浏览器,释放资源
701
703
  this.sendMessage(ws, {
702
704
  type: 'debug_completed',
703
705
  sessionId,
@@ -705,6 +707,12 @@ export class DebugWebSocketServer {
705
707
  reportUrl,
706
708
  errorMessage: execError,
707
709
  });
710
+ try {
711
+ await this.closeWebSession(sessionId);
712
+ }
713
+ catch (closeErr) {
714
+ logger.warn(`[WebDebug] Auto close browser failed: ${closeErr.message}`);
715
+ }
708
716
  }
709
717
  catch (error) {
710
718
  const msg = error.message;
@@ -712,7 +720,13 @@ export class DebugWebSocketServer {
712
720
  sessionManager.updateStatus(sessionId, 'failed');
713
721
  this.sendMessage(ws, { type: 'debug_error', sessionId, error: msg });
714
722
  this.sendMessage(ws, { type: 'debug_completed', sessionId, success: false, errorMessage: msg });
715
- // 注:致命错误也不主动关浏览器,避免破坏复用契约;如需销毁让前端发 close_web_session
723
+ // 致命错误也自动关闭浏览器
724
+ try {
725
+ await this.closeWebSession(sessionId);
726
+ }
727
+ catch (closeErr) {
728
+ logger.warn(`[WebDebug] Auto close browser (fatal) failed: ${closeErr.message}`);
729
+ }
716
730
  }
717
731
  }
718
732
  /**
@@ -792,21 +806,11 @@ export class DebugWebSocketServer {
792
806
  skipAppRestart: request.skipAppRestart,
793
807
  mobileMode: request.mobileMode,
794
808
  deviceName: request.deviceName,
809
+ loginContext: request.loginContext,
795
810
  // 环境代理配置
796
811
  proxyPort: request.proxyPort,
797
812
  // 公司代理平台账号
798
813
  proxyAccount: request.proxyAccount,
799
- // 添加登录配置
800
- ...(request.loginConfig && {
801
- loginUrl: request.loginConfig.loginUrl,
802
- loginUsername: request.loginConfig.loginUsername,
803
- loginPassword: request.loginConfig.loginPassword,
804
- usernameSelector: request.loginConfig.usernameSelector,
805
- passwordSelector: request.loginConfig.passwordSelector,
806
- loginButtonSelector: request.loginConfig.loginButtonSelector,
807
- loginModeSelector: request.loginConfig.loginModeSelector,
808
- agreementSelector: request.loginConfig.agreementSelector,
809
- }),
810
814
  };
811
815
  // 注意:proxy-service.jd.com 的规则由前端(浏览器端)设置,后端只负责设备代理 + 抓包
812
816
  try {
@@ -919,21 +923,11 @@ export class DebugWebSocketServer {
919
923
  nodeId: this.config.task.nodeId,
920
924
  modelConfig: fullModelConfig,
921
925
  skipAppRestart: request.skipAppRestart,
926
+ loginContext: request.loginContext,
922
927
  // 环境代理配置
923
928
  proxyPort: request.proxyPort,
924
929
  // 公司代理平台账号
925
930
  proxyAccount: request.proxyAccount,
926
- // 添加登录配置
927
- ...(request.loginConfig && {
928
- loginUrl: request.loginConfig.loginUrl,
929
- loginUsername: request.loginConfig.loginUsername,
930
- loginPassword: request.loginConfig.loginPassword,
931
- usernameSelector: request.loginConfig.usernameSelector,
932
- passwordSelector: request.loginConfig.passwordSelector,
933
- loginButtonSelector: request.loginConfig.loginButtonSelector,
934
- loginModeSelector: request.loginConfig.loginModeSelector,
935
- agreementSelector: request.loginConfig.agreementSelector,
936
- }),
937
931
  };
938
932
  // 注意:proxy-service.jd.com 的规则由前端(浏览器端)设置,后端只负责设备代理 + 抓包
939
933
  // 始终启动请求抓包
@@ -1514,58 +1508,79 @@ export class DebugWebSocketServer {
1514
1508
  }
1515
1509
  }
1516
1510
  // ==================== Execute Web Action (Playwright + Midscene, with screencast) ====================
1517
- async performWebLogin(page, loginConfig) {
1518
- if (!loginConfig.loginUrl || !loginConfig.loginUsername || !loginConfig.loginPassword) {
1519
- logger.warn('Login credentials not provided, skipping login');
1520
- return;
1511
+ normalizeWebUrl(value) {
1512
+ if (!value)
1513
+ return '';
1514
+ try {
1515
+ const parsed = new URL(value);
1516
+ parsed.hash = '';
1517
+ return parsed.toString().replace(/\/$/, '');
1521
1518
  }
1522
- logger.info(`Performing web login at: ${loginConfig.loginUrl}`);
1519
+ catch {
1520
+ return String(value).split('#')[0].replace(/\/$/, '');
1521
+ }
1522
+ }
1523
+ getCurrentWebUrl(page) {
1523
1524
  try {
1524
- // Navigate to login page
1525
- await page.goto(loginConfig.loginUrl, { waitUntil: 'networkidle' });
1526
- logger.info(`Navigated to login page: ${loginConfig.loginUrl}`);
1527
- // Wait for page to load
1528
- await new Promise(resolve => setTimeout(resolve, loginConfig.mobileMode ? 3000 : 2000));
1529
- // Click "账号密码登录" button first
1530
- logger.info('Clicking account password login button');
1531
- await page.click('span.J_ping.planBLogin:has-text("账号密码登录")');
1532
- // Wait for login form to appear
1533
- await new Promise(resolve => setTimeout(resolve, loginConfig.mobileMode ? 2000 : 1000));
1534
- // Fill username
1535
- const usernameSelector = loginConfig.usernameSelector || '#username';
1536
- logger.info(`Filling username with selector: ${usernameSelector}`);
1537
- await page.fill(usernameSelector, loginConfig.loginUsername);
1538
- // Fill password
1539
- const passwordSelector = loginConfig.passwordSelector || '#pwd';
1540
- logger.info(`Filling password with selector: ${passwordSelector}`);
1541
- await page.fill(passwordSelector, loginConfig.loginPassword);
1542
- // Check agreement checkbox - using the specific selector
1543
- const agreementSelector = loginConfig.agreementSelector || '.policy_tip-checkbox';
1544
- logger.info(`Checking agreement with selector: ${agreementSelector}`);
1545
- await page.check(agreementSelector);
1546
- // Click login button - using the specific selector
1547
- const loginButtonSelector = loginConfig.loginButtonSelector || 'a.J_ping:has-text("同意协议并登录")';
1548
- logger.info(`Clicking login button with selector: ${loginButtonSelector}`);
1549
- await page.click(loginButtonSelector);
1550
- // Wait for login to complete
1551
- await new Promise(resolve => setTimeout(resolve, loginConfig.mobileMode ? 8000 : 5000));
1552
- // Check if login was successful
1553
- try {
1554
- const currentUrl = page.url();
1555
- if (currentUrl !== loginConfig.loginUrl && !currentUrl.includes('login')) {
1556
- logger.info('Login appears to be successful');
1557
- }
1558
- else {
1559
- logger.warn('Still on login page, login may have failed');
1560
- }
1561
- }
1562
- catch (e) {
1563
- logger.warn('Could not verify login status');
1564
- }
1525
+ return typeof page?.url === 'function' ? page.url() : '';
1526
+ }
1527
+ catch {
1528
+ return '';
1529
+ }
1530
+ }
1531
+ async waitForWebPageStable(page) {
1532
+ await page.waitForLoadState('domcontentloaded', { timeout: 5000 }).catch(() => undefined);
1533
+ await page.waitForLoadState('networkidle', { timeout: 8000 }).catch(() => undefined);
1534
+ }
1535
+ async safeWebGoto(page, url, label) {
1536
+ const targetUrl = this.normalizeWebUrl(url);
1537
+ const currentUrl = this.normalizeWebUrl(this.getCurrentWebUrl(page));
1538
+ if (currentUrl && targetUrl && currentUrl === targetUrl) {
1539
+ logger.info(`${label}: already at target url, skip duplicate goto`);
1540
+ await this.waitForWebPageStable(page);
1541
+ return null;
1542
+ }
1543
+ try {
1544
+ logger.info(`${label}: ${url}`);
1545
+ const result = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 45000 });
1546
+ await page.waitForLoadState('networkidle', { timeout: 8000 }).catch(() => undefined);
1547
+ return result;
1565
1548
  }
1566
1549
  catch (error) {
1567
- logger.error(`Web login failed: ${error.message}`);
1568
- throw new Error(`Web login failed: ${error.message}`);
1550
+ const message = error.message || '';
1551
+ const latestUrl = this.normalizeWebUrl(this.getCurrentWebUrl(page));
1552
+ // 导航被中断/中止时,检查页面是否已到达目标 URL,若已到达则继续执行
1553
+ const isAborted = message.includes('ERR_ABORTED') || message.includes('is interrupted by another navigation');
1554
+ if (isAborted) {
1555
+ // 短暂等待让页面重定向/跳转完成
1556
+ await new Promise((r) => setTimeout(r, 1500));
1557
+ const settledUrl = this.normalizeWebUrl(this.getCurrentWebUrl(page));
1558
+ if (settledUrl && settledUrl === targetUrl) {
1559
+ logger.warn(`${label}: navigation aborted but page reached target url, continue`);
1560
+ await this.waitForWebPageStable(page);
1561
+ return null;
1562
+ }
1563
+ // URL 不完全匹配但导航被中止,可能是目标页面发生了重定向(登录回调等),
1564
+ // 尝试重新导航一次
1565
+ logger.warn(`${label}: navigation aborted, current=${settledUrl}, retrying goto`);
1566
+ try {
1567
+ const retryResult = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
1568
+ await page.waitForLoadState('networkidle', { timeout: 8000 }).catch(() => undefined);
1569
+ return retryResult;
1570
+ }
1571
+ catch (retryError) {
1572
+ const retryMessage = retryError.message || '';
1573
+ const finalUrl = this.normalizeWebUrl(this.getCurrentWebUrl(page));
1574
+ // 重试后仍报错,但只要页面不在 about:blank 就继续
1575
+ if (finalUrl && finalUrl !== 'about:blank') {
1576
+ logger.warn(`${label}: retry goto also failed (${retryMessage.substring(0, 80)}), but page has content at ${finalUrl}, continue`);
1577
+ await this.waitForWebPageStable(page);
1578
+ return null;
1579
+ }
1580
+ throw retryError;
1581
+ }
1582
+ }
1583
+ throw error;
1569
1584
  }
1570
1585
  }
1571
1586
  async handleExecuteWebAction(ws, request) {
@@ -1642,15 +1657,23 @@ export class DebugWebSocketServer {
1642
1657
  };
1643
1658
  }
1644
1659
  else {
1645
- // 桌面模式:走浏览器池,复用已 launch Chromium
1660
+ // 桌面模式:每次执行独立启动 Chromium,结束后真正关闭,避免残留 Chrome 进程。
1646
1661
  const contextOptions = {
1647
1662
  viewport: request.viewport || { width: 1280, height: 800 },
1648
1663
  };
1649
- const acquired = await webBrowserPool.acquireContext(contextOptions);
1650
- browser = acquired.browser;
1651
- context = acquired.context;
1664
+ browser = await playwright.chromium.launch({ headless: false });
1665
+ context = await browser.newContext(contextOptions);
1652
1666
  page = await context.newPage();
1653
- releaseBrowser = acquired.release;
1667
+ releaseBrowser = async () => {
1668
+ try {
1669
+ await context.close();
1670
+ }
1671
+ catch { /* ignore */ }
1672
+ try {
1673
+ await browser.close();
1674
+ }
1675
+ catch { /* ignore */ }
1676
+ };
1654
1677
  }
1655
1678
  // mobileMode 下注入脚本,将 touchstart/touchmove/wheel 事件监听器设为 passive:false
1656
1679
  // 修复部分移动端网页在 passive listener 中调用 preventDefault 导致点击失效的问题
@@ -1714,20 +1737,25 @@ export class DebugWebSocketServer {
1714
1737
  // 取出 agent / page
1715
1738
  const agent = session.webAgent;
1716
1739
  const page = session.page;
1740
+ // 执行前检查浏览器和页面是否仍然存活,防止在已崩溃的浏览器上操作导致不明错误
1741
+ const browserRef = session.browser;
1742
+ if (browserRef && typeof browserRef.isConnected === 'function' && !browserRef.isConnected()) {
1743
+ throw new Error('Browser has been disconnected before action execution. The browser process may have crashed or been killed.');
1744
+ }
1745
+ if (page && typeof page.isClosed === 'function' && page.isClosed()) {
1746
+ throw new Error('Page has been closed before action execution. The browser context may have been terminated.');
1747
+ }
1717
1748
  // 刷新 web session 活跃时间,避免 TTL 误杀
1718
1749
  this.touchWebSession(sessionId);
1750
+ // 标记 web action 正在执行中,防止 TTL GC 或并发路径误关浏览器
1751
+ session.webActionRunning = true;
1719
1752
  await executeWebLogin({
1720
1753
  source: request,
1721
1754
  page,
1722
1755
  logPrefix: `[WebAction ${sessionId}]`,
1723
1756
  });
1724
- // 兼容旧版表单登录配置
1725
- if (request.loginConfig && request.loginConfig.loginUrl && request.loginConfig.loginUsername && request.loginConfig.loginPassword) {
1726
- const loginConfig = {
1727
- ...request.loginConfig,
1728
- mobileMode: request.mobileMode || false
1729
- };
1730
- await this.performWebLogin(page, loginConfig);
1757
+ if (request.url && request.actionType !== 'goto') {
1758
+ await this.safeWebGoto(page, request.url, `[WebAction] Navigate before action`);
1731
1759
  }
1732
1760
  // ===== 周期 dump(与移动端 handleExecuteAction 行为一致) =====
1733
1761
  const sendDump = (dump) => {
@@ -1765,6 +1793,8 @@ export class DebugWebSocketServer {
1765
1793
  const intervalDump = agent.dumpDataString({ inlineScreenshots: false });
1766
1794
  sendDump(intervalDump);
1767
1795
  }
1796
+ // 每次 dump 周期刷新活跃时间,防止长时间 aiAct 操作期间被 TTL GC 误杀
1797
+ this.touchWebSession(sessionId);
1768
1798
  }
1769
1799
  catch (dumpError) {
1770
1800
  logger.warn(`[WebAction] interval dump failed: ${dumpError.message}`);
@@ -1787,7 +1817,7 @@ export class DebugWebSocketServer {
1787
1817
  case 'goto': {
1788
1818
  if (!request.url)
1789
1819
  throw new Error('goto requires url parameter');
1790
- result = await page.goto(request.url, { waitUntil: 'networkidle' });
1820
+ result = await this.safeWebGoto(page, request.url, `[WebAction] goto`);
1791
1821
  break;
1792
1822
  }
1793
1823
  case 'aiAction': {
@@ -1833,11 +1863,6 @@ export class DebugWebSocketServer {
1833
1863
  const code = request.actionParams?.code || request.code || '';
1834
1864
  if (!code)
1835
1865
  throw new Error('runCode requires actionParams.code');
1836
- // 如果提供了URL,先导航到指定页面
1837
- if (request.url) {
1838
- logger.info(`[WebAction] Navigating to URL before runCode: ${request.url}`);
1839
- await page.goto(request.url, { waitUntil: 'networkidle' });
1840
- }
1841
1866
  // 行号探针:与真机 runCode 一致,通过 instrumentCode 在每条语句前插入 __lineCallback(line),
1842
1867
  // 推送 type='execution_line' 给前端,使 web 与真机调试体验一致
1843
1868
  const onExecutionLine = (line) => {
@@ -2036,6 +2061,10 @@ export class DebugWebSocketServer {
2036
2061
  });
2037
2062
  }
2038
2063
  finally {
2064
+ // 清除执行中标记,允许后续关闭浏览器
2065
+ if (session) {
2066
+ session.webActionRunning = false;
2067
+ }
2039
2068
  // 不管成功还是失败,web 调试结束后必须关闭浏览器
2040
2069
  try {
2041
2070
  await this.closeWebSession(sessionId);
@@ -2094,6 +2123,11 @@ export class DebugWebSocketServer {
2094
2123
  const session = sessionManager.get(sessionId);
2095
2124
  if (!session || !session.browser)
2096
2125
  return;
2126
+ // 如果 web action 正在执行中,跳过关闭,防止 TTL GC 或其他并发路径误杀浏览器
2127
+ if (session.webActionRunning) {
2128
+ logger.warn(`[WebSession] Session ${sessionId} has running web action, skipping close to avoid race condition`);
2129
+ return;
2130
+ }
2097
2131
  // 停止周期 dump
2098
2132
  if (session.dumpIntervalId) {
2099
2133
  clearInterval(session.dumpIntervalId);