@midscene/android 1.9.5-beta-20260611045217.0 → 1.9.5

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/lib/cli.js CHANGED
@@ -8,13 +8,11 @@ var __webpack_modules__ = {
8
8
  ScrcpyScreenshotManager: ()=>ScrcpyScreenshotManager,
9
9
  o: ()=>DEFAULT_SCRCPY_CONFIG
10
10
  });
11
- var external_node_fs_ = __webpack_require__("node:fs");
12
- var external_node_module_ = __webpack_require__("node:module");
13
- var external_node_path_ = __webpack_require__("node:path");
14
- var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_);
15
- const constants_namespaceObject = require("@midscene/shared/constants");
16
- var logger_ = __webpack_require__("@midscene/shared/logger");
17
- const timeout_namespaceObject = require("@midscene/shared/timeout");
11
+ var node_fs__rspack_import_0 = __webpack_require__("node:fs");
12
+ var node_module__rspack_import_1 = __webpack_require__("node:module");
13
+ var node_path__rspack_import_2 = __webpack_require__("node:path");
14
+ var node_path__rspack_import_2_default = /*#__PURE__*/ __webpack_require__.n(node_path__rspack_import_2);
15
+ var _midscene_shared_logger__rspack_import_3 = __webpack_require__("@midscene/shared/logger");
18
16
  function _define_property(obj, key, value) {
19
17
  if (key in obj) Object.defineProperty(obj, key, {
20
18
  value: value,
@@ -25,8 +23,8 @@ var __webpack_modules__ = {
25
23
  else obj[key] = value;
26
24
  return obj;
27
25
  }
28
- const debugScrcpy = (0, logger_.getDebug)('android:scrcpy');
29
- const warnScrcpy = (0, logger_.getDebug)('android:scrcpy', {
26
+ const debugScrcpy = (0, _midscene_shared_logger__rspack_import_3.getDebug)('android:scrcpy');
27
+ const warnScrcpy = (0, _midscene_shared_logger__rspack_import_3.getDebug)('android:scrcpy', {
30
28
  console: true
31
29
  });
32
30
  const NAL_TYPE_IDR = 5;
@@ -67,11 +65,6 @@ var __webpack_modules__ = {
67
65
  return false;
68
66
  }
69
67
  class ScrcpyScreenshotManager {
70
- shouldRetryWithForwardTunnel(error) {
71
- const cause = error instanceof Error ? error.cause : void 0;
72
- const message = error instanceof Error ? `${error.message}${cause instanceof Error ? ` ${cause.message}` : ''}` : String(error);
73
- return /more than one device\/emulator/i.test(message);
74
- }
75
68
  async validateEnvironment() {
76
69
  await this.ensureFfmpegAvailable();
77
70
  }
@@ -90,7 +83,12 @@ var __webpack_modules__ = {
90
83
  try {
91
84
  this.isConnecting = true;
92
85
  debugScrcpy('Starting scrcpy connection...');
93
- this.scrcpyClient = await this.startScrcpy(this.adb, {
86
+ const { AdbScrcpyClient, AdbScrcpyOptions3_3_3 } = await import("@yume-chan/adb-scrcpy");
87
+ const { ReadableStream } = await import("@yume-chan/stream-extra");
88
+ const { DefaultServerPath } = await import("@yume-chan/scrcpy");
89
+ const serverBinPath = this.resolveServerBinPath();
90
+ await AdbScrcpyClient.pushServer(this.adb, ReadableStream.from((0, node_fs__rspack_import_0.createReadStream)(serverBinPath)));
91
+ const scrcpyOptions = new AdbScrcpyOptions3_3_3({
94
92
  audio: false,
95
93
  control: false,
96
94
  maxSize: this.options.maxSize,
@@ -99,6 +97,7 @@ var __webpack_modules__ = {
99
97
  sendFrameMeta: true,
100
98
  videoCodecOptions: 'i-frame-interval=0,bitrate-mode=2'
101
99
  });
100
+ this.scrcpyClient = await AdbScrcpyClient.start(this.adb, DefaultServerPath, scrcpyOptions);
102
101
  const videoStreamPromise = this.scrcpyClient.videoStream;
103
102
  if (!videoStreamPromise) throw new Error('Scrcpy client did not provide video stream');
104
103
  this.videoStream = await videoStreamPromise;
@@ -120,50 +119,13 @@ var __webpack_modules__ = {
120
119
  this.isConnecting = false;
121
120
  }
122
121
  }
123
- async startScrcpyOnce(adb, options = {}, onProgress, tunnelForward = false) {
124
- const { AdbScrcpyClient, AdbScrcpyOptions3_3_3 } = await import("@yume-chan/adb-scrcpy");
125
- const { ReadableStream } = await import("@yume-chan/stream-extra");
126
- const { DefaultServerPath } = await import("@yume-chan/scrcpy");
127
- const serverBinPath = this.resolveServerBinPath();
128
- onProgress?.('pushing-server');
129
- await (0, timeout_namespaceObject.withTimeout)(AdbScrcpyClient.pushServer(adb, ReadableStream.from((0, external_node_fs_.createReadStream)(serverBinPath))), constants_namespaceObject.SCRCPY_PUSH_TIMEOUT_MS, `Timed out pushing scrcpy server to device after ${Math.round(constants_namespaceObject.SCRCPY_PUSH_TIMEOUT_MS / 1000)}s`);
130
- const scrcpyOptions = new AdbScrcpyOptions3_3_3({
131
- audio: false,
132
- control: true,
133
- maxSize: 1024,
134
- sendFrameMeta: true,
135
- videoBitRate: 2000000,
136
- ...options,
137
- tunnelForward
138
- });
139
- onProgress?.('starting-service');
140
- const startPromise = AdbScrcpyClient.start(adb, DefaultServerPath, scrcpyOptions);
141
- return await (0, timeout_namespaceObject.withTimeout)(startPromise, constants_namespaceObject.SCRCPY_START_TIMEOUT_MS, `Timed out starting scrcpy service after ${Math.round(constants_namespaceObject.SCRCPY_START_TIMEOUT_MS / 1000)}s`, {
142
- onSettledAfterTimeout: async (lateClient)=>{
143
- try {
144
- await lateClient.close();
145
- } catch (closeError) {
146
- console.error('failed to close late scrcpy client after timeout:', closeError);
147
- }
148
- }
149
- });
150
- }
151
- async startScrcpy(adb, options = {}, onProgress) {
152
- try {
153
- return await this.startScrcpyOnce(adb, options, onProgress, false);
154
- } catch (error) {
155
- if (!this.shouldRetryWithForwardTunnel(error)) throw error;
156
- warnScrcpy(`Reverse tunnel failed for device ${adb.serial}; retrying scrcpy with forward tunnel: ${error}`);
157
- return await this.startScrcpyOnce(adb, options, onProgress, true);
158
- }
159
- }
160
122
  resolveServerBinPath() {
161
- const androidPkgJson = (0, external_node_module_.createRequire)(__rslib_import_meta_url__).resolve('@midscene/android/package.json');
162
- return external_node_path_default().join(external_node_path_default().dirname(androidPkgJson), 'bin', 'scrcpy-server');
123
+ const androidPkgJson = (0, node_module__rspack_import_1.createRequire)(__rslib_import_meta_url__).resolve('@midscene/android/package.json');
124
+ return node_path__rspack_import_2_default().join(node_path__rspack_import_2_default().dirname(androidPkgJson), 'bin', 'scrcpy-server');
163
125
  }
164
126
  getFfmpegPath() {
165
127
  try {
166
- const dynamicRequire = (0, external_node_module_.createRequire)(__rslib_import_meta_url__);
128
+ const dynamicRequire = (0, node_module__rspack_import_1.createRequire)(__rslib_import_meta_url__);
167
129
  const ffmpegInstaller = dynamicRequire('@ffmpeg-installer/ffmpeg');
168
130
  debugScrcpy(`Using ffmpeg from npm package: ${ffmpegInstaller.path}`);
169
131
  return ffmpegInstaller.path;
@@ -490,6 +452,45 @@ var __webpack_exports__ = {};
490
452
  const base_tools_namespaceObject = require("@midscene/shared/mcp/base-tools");
491
453
  const agent_namespaceObject = require("@midscene/core/agent");
492
454
  const utils_namespaceObject = require("@midscene/shared/utils");
455
+ const EMPTY_ADB_SHELL_STDOUT = '<empty>';
456
+ const MAX_RUN_ADB_SHELL_STDOUT = 200;
457
+ function normalizeShellStream(value) {
458
+ if (null == value) return '';
459
+ return String(value);
460
+ }
461
+ function truncateAdbShellStream(output, streamName) {
462
+ if (output.length <= MAX_RUN_ADB_SHELL_STDOUT) return output;
463
+ return `${output.slice(0, MAX_RUN_ADB_SHELL_STDOUT)}
464
+ ...[${streamName} truncated, ${output.length - MAX_RUN_ADB_SHELL_STDOUT} more characters]`;
465
+ }
466
+ function buildRunAdbShellPlanningFeedback({ command, stdout }) {
467
+ if ('' === stdout) return;
468
+ const commandText = 'string' == typeof command && command.length > 0 ? `Command: ${command}\n` : '';
469
+ return `${commandText}Stdout:
470
+ ${stdout}`;
471
+ }
472
+ function buildAdbShellStderrErrorMessage(command, stdout, stderr) {
473
+ return `RunAdbShell command returned stderr.
474
+ Command: ${command}
475
+ Stderr:
476
+ ${truncateAdbShellStream(stderr, 'stderr')}
477
+ Stdout:
478
+ ${stdout ? truncateAdbShellStream(stdout, 'stdout') : EMPTY_ADB_SHELL_STDOUT}`;
479
+ }
480
+ function getAdbShellStdoutOrThrow(command, output) {
481
+ if ('string' == typeof output) return output;
482
+ const stdout = normalizeShellStream(output.stdout);
483
+ const stderr = normalizeShellStream(output.stderr);
484
+ if (stderr) throw new Error(buildAdbShellStderrErrorMessage(command, stdout, stderr));
485
+ return stdout;
486
+ }
487
+ async function runAdbShellStdoutOrThrow(adb, command, options = {}) {
488
+ const output = await adb.shell(command, {
489
+ ...options,
490
+ outputFormat: adb.EXEC_OUTPUT_FORMAT.FULL
491
+ });
492
+ return getAdbShellStdoutOrThrow(command, output);
493
+ }
493
494
  const defaultAppNameMapping = {
494
495
  微信: 'com.tencent.mm',
495
496
  QQ: 'com.tencent.mobileqq',
@@ -1835,16 +1836,22 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1835
1836
  const createPlatformActions = (device)=>({
1836
1837
  RunAdbShell: (0, device_namespaceObject.defineAction)({
1837
1838
  name: 'RunAdbShell',
1838
- description: 'Execute ADB shell command on Android device',
1839
+ description: 'Execute an ADB shell command on the Android device and return the command stdout. Read the returned stdout to decide the next step; the stdout may indicate either success or failure.',
1839
1840
  interfaceAlias: 'runAdbShell',
1840
1841
  paramSchema: runAdbShellParamSchema,
1841
1842
  sample: {
1842
1843
  command: 'dumpsys window displays | grep -E "mCurrentFocus"'
1843
1844
  },
1844
- call: async (param)=>{
1845
+ call: async (param, context)=>{
1845
1846
  if (!param.command || '' === param.command.trim()) throw new Error('RunAdbShell requires a non-empty command parameter');
1846
1847
  const adb = await device.getAdb();
1847
- return await adb.shell(param.command);
1848
+ const stdout = await runAdbShellStdoutOrThrow(adb, param.command);
1849
+ const planningFeedback = buildRunAdbShellPlanningFeedback({
1850
+ command: param.command,
1851
+ stdout
1852
+ });
1853
+ if (planningFeedback && context?.task) context.task.planningFeedback = planningFeedback;
1854
+ return stdout;
1848
1855
  }
1849
1856
  }),
1850
1857
  Launch: (0, device_namespaceObject.defineAction)({
@@ -1914,7 +1921,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1914
1921
  async runAdbShell(command, opt) {
1915
1922
  if (opt?.timeout !== void 0) {
1916
1923
  const adb = await this.interface.getAdb();
1917
- return await adb.shell(command, {
1924
+ return await runAdbShellStdoutOrThrow(adb, command, {
1918
1925
  timeout: opt.timeout
1919
1926
  });
1920
1927
  }
@@ -2052,7 +2059,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
2052
2059
  const tools = new AndroidMidsceneTools();
2053
2060
  (0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-android', {
2054
2061
  stripPrefix: 'android_',
2055
- version: "1.9.5-beta-20260611045217.0",
2062
+ version: "1.9.5",
2056
2063
  extraCommands: (0, core_namespaceObject.createReportCliCommands)()
2057
2064
  }).catch((e)=>{
2058
2065
  process.exit((0, cli_namespaceObject.reportCLIError)(e));
package/dist/lib/index.js CHANGED
@@ -8,13 +8,11 @@ var __webpack_modules__ = {
8
8
  ScrcpyScreenshotManager: ()=>ScrcpyScreenshotManager,
9
9
  o: ()=>DEFAULT_SCRCPY_CONFIG
10
10
  });
11
- var external_node_fs_ = __webpack_require__("node:fs");
12
- var external_node_module_ = __webpack_require__("node:module");
13
- var external_node_path_ = __webpack_require__("node:path");
14
- var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_);
15
- const constants_namespaceObject = require("@midscene/shared/constants");
16
- var logger_ = __webpack_require__("@midscene/shared/logger");
17
- const timeout_namespaceObject = require("@midscene/shared/timeout");
11
+ var node_fs__rspack_import_0 = __webpack_require__("node:fs");
12
+ var node_module__rspack_import_1 = __webpack_require__("node:module");
13
+ var node_path__rspack_import_2 = __webpack_require__("node:path");
14
+ var node_path__rspack_import_2_default = /*#__PURE__*/ __webpack_require__.n(node_path__rspack_import_2);
15
+ var _midscene_shared_logger__rspack_import_3 = __webpack_require__("@midscene/shared/logger");
18
16
  function _define_property(obj, key, value) {
19
17
  if (key in obj) Object.defineProperty(obj, key, {
20
18
  value: value,
@@ -25,8 +23,8 @@ var __webpack_modules__ = {
25
23
  else obj[key] = value;
26
24
  return obj;
27
25
  }
28
- const debugScrcpy = (0, logger_.getDebug)('android:scrcpy');
29
- const warnScrcpy = (0, logger_.getDebug)('android:scrcpy', {
26
+ const debugScrcpy = (0, _midscene_shared_logger__rspack_import_3.getDebug)('android:scrcpy');
27
+ const warnScrcpy = (0, _midscene_shared_logger__rspack_import_3.getDebug)('android:scrcpy', {
30
28
  console: true
31
29
  });
32
30
  const NAL_TYPE_IDR = 5;
@@ -67,11 +65,6 @@ var __webpack_modules__ = {
67
65
  return false;
68
66
  }
69
67
  class ScrcpyScreenshotManager {
70
- shouldRetryWithForwardTunnel(error) {
71
- const cause = error instanceof Error ? error.cause : void 0;
72
- const message = error instanceof Error ? `${error.message}${cause instanceof Error ? ` ${cause.message}` : ''}` : String(error);
73
- return /more than one device\/emulator/i.test(message);
74
- }
75
68
  async validateEnvironment() {
76
69
  await this.ensureFfmpegAvailable();
77
70
  }
@@ -90,7 +83,12 @@ var __webpack_modules__ = {
90
83
  try {
91
84
  this.isConnecting = true;
92
85
  debugScrcpy('Starting scrcpy connection...');
93
- this.scrcpyClient = await this.startScrcpy(this.adb, {
86
+ const { AdbScrcpyClient, AdbScrcpyOptions3_3_3 } = await import("@yume-chan/adb-scrcpy");
87
+ const { ReadableStream } = await import("@yume-chan/stream-extra");
88
+ const { DefaultServerPath } = await import("@yume-chan/scrcpy");
89
+ const serverBinPath = this.resolveServerBinPath();
90
+ await AdbScrcpyClient.pushServer(this.adb, ReadableStream.from((0, node_fs__rspack_import_0.createReadStream)(serverBinPath)));
91
+ const scrcpyOptions = new AdbScrcpyOptions3_3_3({
94
92
  audio: false,
95
93
  control: false,
96
94
  maxSize: this.options.maxSize,
@@ -99,6 +97,7 @@ var __webpack_modules__ = {
99
97
  sendFrameMeta: true,
100
98
  videoCodecOptions: 'i-frame-interval=0,bitrate-mode=2'
101
99
  });
100
+ this.scrcpyClient = await AdbScrcpyClient.start(this.adb, DefaultServerPath, scrcpyOptions);
102
101
  const videoStreamPromise = this.scrcpyClient.videoStream;
103
102
  if (!videoStreamPromise) throw new Error('Scrcpy client did not provide video stream');
104
103
  this.videoStream = await videoStreamPromise;
@@ -120,50 +119,13 @@ var __webpack_modules__ = {
120
119
  this.isConnecting = false;
121
120
  }
122
121
  }
123
- async startScrcpyOnce(adb, options = {}, onProgress, tunnelForward = false) {
124
- const { AdbScrcpyClient, AdbScrcpyOptions3_3_3 } = await import("@yume-chan/adb-scrcpy");
125
- const { ReadableStream } = await import("@yume-chan/stream-extra");
126
- const { DefaultServerPath } = await import("@yume-chan/scrcpy");
127
- const serverBinPath = this.resolveServerBinPath();
128
- onProgress?.('pushing-server');
129
- await (0, timeout_namespaceObject.withTimeout)(AdbScrcpyClient.pushServer(adb, ReadableStream.from((0, external_node_fs_.createReadStream)(serverBinPath))), constants_namespaceObject.SCRCPY_PUSH_TIMEOUT_MS, `Timed out pushing scrcpy server to device after ${Math.round(constants_namespaceObject.SCRCPY_PUSH_TIMEOUT_MS / 1000)}s`);
130
- const scrcpyOptions = new AdbScrcpyOptions3_3_3({
131
- audio: false,
132
- control: true,
133
- maxSize: 1024,
134
- sendFrameMeta: true,
135
- videoBitRate: 2000000,
136
- ...options,
137
- tunnelForward
138
- });
139
- onProgress?.('starting-service');
140
- const startPromise = AdbScrcpyClient.start(adb, DefaultServerPath, scrcpyOptions);
141
- return await (0, timeout_namespaceObject.withTimeout)(startPromise, constants_namespaceObject.SCRCPY_START_TIMEOUT_MS, `Timed out starting scrcpy service after ${Math.round(constants_namespaceObject.SCRCPY_START_TIMEOUT_MS / 1000)}s`, {
142
- onSettledAfterTimeout: async (lateClient)=>{
143
- try {
144
- await lateClient.close();
145
- } catch (closeError) {
146
- console.error('failed to close late scrcpy client after timeout:', closeError);
147
- }
148
- }
149
- });
150
- }
151
- async startScrcpy(adb, options = {}, onProgress) {
152
- try {
153
- return await this.startScrcpyOnce(adb, options, onProgress, false);
154
- } catch (error) {
155
- if (!this.shouldRetryWithForwardTunnel(error)) throw error;
156
- warnScrcpy(`Reverse tunnel failed for device ${adb.serial}; retrying scrcpy with forward tunnel: ${error}`);
157
- return await this.startScrcpyOnce(adb, options, onProgress, true);
158
- }
159
- }
160
122
  resolveServerBinPath() {
161
- const androidPkgJson = (0, external_node_module_.createRequire)(__rslib_import_meta_url__).resolve('@midscene/android/package.json');
162
- return external_node_path_default().join(external_node_path_default().dirname(androidPkgJson), 'bin', 'scrcpy-server');
123
+ const androidPkgJson = (0, node_module__rspack_import_1.createRequire)(__rslib_import_meta_url__).resolve('@midscene/android/package.json');
124
+ return node_path__rspack_import_2_default().join(node_path__rspack_import_2_default().dirname(androidPkgJson), 'bin', 'scrcpy-server');
163
125
  }
164
126
  getFfmpegPath() {
165
127
  try {
166
- const dynamicRequire = (0, external_node_module_.createRequire)(__rslib_import_meta_url__);
128
+ const dynamicRequire = (0, node_module__rspack_import_1.createRequire)(__rslib_import_meta_url__);
167
129
  const ffmpegInstaller = dynamicRequire('@ffmpeg-installer/ffmpeg');
168
130
  debugScrcpy(`Using ffmpeg from npm package: ${ffmpegInstaller.path}`);
169
131
  return ffmpegInstaller.path;
@@ -521,6 +483,45 @@ var __webpack_exports__ = {};
521
483
  var logger_ = __webpack_require__("@midscene/shared/logger");
522
484
  const shared_utils_namespaceObject = require("@midscene/shared/utils");
523
485
  const external_appium_adb_namespaceObject = require("appium-adb");
486
+ const EMPTY_ADB_SHELL_STDOUT = '<empty>';
487
+ const MAX_RUN_ADB_SHELL_STDOUT = 200;
488
+ function normalizeShellStream(value) {
489
+ if (null == value) return '';
490
+ return String(value);
491
+ }
492
+ function truncateAdbShellStream(output, streamName) {
493
+ if (output.length <= MAX_RUN_ADB_SHELL_STDOUT) return output;
494
+ return `${output.slice(0, MAX_RUN_ADB_SHELL_STDOUT)}
495
+ ...[${streamName} truncated, ${output.length - MAX_RUN_ADB_SHELL_STDOUT} more characters]`;
496
+ }
497
+ function buildRunAdbShellPlanningFeedback({ command, stdout }) {
498
+ if ('' === stdout) return;
499
+ const commandText = 'string' == typeof command && command.length > 0 ? `Command: ${command}\n` : '';
500
+ return `${commandText}Stdout:
501
+ ${stdout}`;
502
+ }
503
+ function buildAdbShellStderrErrorMessage(command, stdout, stderr) {
504
+ return `RunAdbShell command returned stderr.
505
+ Command: ${command}
506
+ Stderr:
507
+ ${truncateAdbShellStream(stderr, 'stderr')}
508
+ Stdout:
509
+ ${stdout ? truncateAdbShellStream(stdout, 'stdout') : EMPTY_ADB_SHELL_STDOUT}`;
510
+ }
511
+ function getAdbShellStdoutOrThrow(command, output) {
512
+ if ('string' == typeof output) return output;
513
+ const stdout = normalizeShellStream(output.stdout);
514
+ const stderr = normalizeShellStream(output.stderr);
515
+ if (stderr) throw new Error(buildAdbShellStderrErrorMessage(command, stdout, stderr));
516
+ return stdout;
517
+ }
518
+ async function runAdbShellStdoutOrThrow(adb, command, options = {}) {
519
+ const output = await adb.shell(command, {
520
+ ...options,
521
+ outputFormat: adb.EXEC_OUTPUT_FORMAT.FULL
522
+ });
523
+ return getAdbShellStdoutOrThrow(command, output);
524
+ }
524
525
  var scrcpy_manager = __webpack_require__("./src/scrcpy-manager.ts");
525
526
  function _define_property(obj, key, value) {
526
527
  if (key in obj) Object.defineProperty(obj, key, {
@@ -1757,16 +1758,22 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1757
1758
  const createPlatformActions = (device)=>({
1758
1759
  RunAdbShell: (0, device_namespaceObject.defineAction)({
1759
1760
  name: 'RunAdbShell',
1760
- description: 'Execute ADB shell command on Android device',
1761
+ description: 'Execute an ADB shell command on the Android device and return the command stdout. Read the returned stdout to decide the next step; the stdout may indicate either success or failure.',
1761
1762
  interfaceAlias: 'runAdbShell',
1762
1763
  paramSchema: runAdbShellParamSchema,
1763
1764
  sample: {
1764
1765
  command: 'dumpsys window displays | grep -E "mCurrentFocus"'
1765
1766
  },
1766
- call: async (param)=>{
1767
+ call: async (param, context)=>{
1767
1768
  if (!param.command || '' === param.command.trim()) throw new Error('RunAdbShell requires a non-empty command parameter');
1768
1769
  const adb = await device.getAdb();
1769
- return await adb.shell(param.command);
1770
+ const stdout = await runAdbShellStdoutOrThrow(adb, param.command);
1771
+ const planningFeedback = buildRunAdbShellPlanningFeedback({
1772
+ command: param.command,
1773
+ stdout
1774
+ });
1775
+ if (planningFeedback && context?.task) context.task.planningFeedback = planningFeedback;
1776
+ return stdout;
1770
1777
  }
1771
1778
  }),
1772
1779
  Launch: (0, device_namespaceObject.defineAction)({
@@ -2000,7 +2007,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
2000
2007
  async runAdbShell(command, opt) {
2001
2008
  if (opt?.timeout !== void 0) {
2002
2009
  const adb = await this.interface.getAdb();
2003
- return await adb.shell(command, {
2010
+ return await runAdbShellStdoutOrThrow(adb, command, {
2004
2011
  timeout: opt.timeout
2005
2012
  });
2006
2013
  }
@@ -8,13 +8,11 @@ var __webpack_modules__ = {
8
8
  ScrcpyScreenshotManager: ()=>ScrcpyScreenshotManager,
9
9
  o: ()=>DEFAULT_SCRCPY_CONFIG
10
10
  });
11
- var external_node_fs_ = __webpack_require__("node:fs");
12
- var external_node_module_ = __webpack_require__("node:module");
13
- var external_node_path_ = __webpack_require__("node:path");
14
- var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_);
15
- const constants_namespaceObject = require("@midscene/shared/constants");
16
- var logger_ = __webpack_require__("@midscene/shared/logger");
17
- const timeout_namespaceObject = require("@midscene/shared/timeout");
11
+ var node_fs__rspack_import_0 = __webpack_require__("node:fs");
12
+ var node_module__rspack_import_1 = __webpack_require__("node:module");
13
+ var node_path__rspack_import_2 = __webpack_require__("node:path");
14
+ var node_path__rspack_import_2_default = /*#__PURE__*/ __webpack_require__.n(node_path__rspack_import_2);
15
+ var _midscene_shared_logger__rspack_import_3 = __webpack_require__("@midscene/shared/logger");
18
16
  function _define_property(obj, key, value) {
19
17
  if (key in obj) Object.defineProperty(obj, key, {
20
18
  value: value,
@@ -25,8 +23,8 @@ var __webpack_modules__ = {
25
23
  else obj[key] = value;
26
24
  return obj;
27
25
  }
28
- const debugScrcpy = (0, logger_.getDebug)('android:scrcpy');
29
- const warnScrcpy = (0, logger_.getDebug)('android:scrcpy', {
26
+ const debugScrcpy = (0, _midscene_shared_logger__rspack_import_3.getDebug)('android:scrcpy');
27
+ const warnScrcpy = (0, _midscene_shared_logger__rspack_import_3.getDebug)('android:scrcpy', {
30
28
  console: true
31
29
  });
32
30
  const NAL_TYPE_IDR = 5;
@@ -67,11 +65,6 @@ var __webpack_modules__ = {
67
65
  return false;
68
66
  }
69
67
  class ScrcpyScreenshotManager {
70
- shouldRetryWithForwardTunnel(error) {
71
- const cause = error instanceof Error ? error.cause : void 0;
72
- const message = error instanceof Error ? `${error.message}${cause instanceof Error ? ` ${cause.message}` : ''}` : String(error);
73
- return /more than one device\/emulator/i.test(message);
74
- }
75
68
  async validateEnvironment() {
76
69
  await this.ensureFfmpegAvailable();
77
70
  }
@@ -90,7 +83,12 @@ var __webpack_modules__ = {
90
83
  try {
91
84
  this.isConnecting = true;
92
85
  debugScrcpy('Starting scrcpy connection...');
93
- this.scrcpyClient = await this.startScrcpy(this.adb, {
86
+ const { AdbScrcpyClient, AdbScrcpyOptions3_3_3 } = await import("@yume-chan/adb-scrcpy");
87
+ const { ReadableStream } = await import("@yume-chan/stream-extra");
88
+ const { DefaultServerPath } = await import("@yume-chan/scrcpy");
89
+ const serverBinPath = this.resolveServerBinPath();
90
+ await AdbScrcpyClient.pushServer(this.adb, ReadableStream.from((0, node_fs__rspack_import_0.createReadStream)(serverBinPath)));
91
+ const scrcpyOptions = new AdbScrcpyOptions3_3_3({
94
92
  audio: false,
95
93
  control: false,
96
94
  maxSize: this.options.maxSize,
@@ -99,6 +97,7 @@ var __webpack_modules__ = {
99
97
  sendFrameMeta: true,
100
98
  videoCodecOptions: 'i-frame-interval=0,bitrate-mode=2'
101
99
  });
100
+ this.scrcpyClient = await AdbScrcpyClient.start(this.adb, DefaultServerPath, scrcpyOptions);
102
101
  const videoStreamPromise = this.scrcpyClient.videoStream;
103
102
  if (!videoStreamPromise) throw new Error('Scrcpy client did not provide video stream');
104
103
  this.videoStream = await videoStreamPromise;
@@ -120,50 +119,13 @@ var __webpack_modules__ = {
120
119
  this.isConnecting = false;
121
120
  }
122
121
  }
123
- async startScrcpyOnce(adb, options = {}, onProgress, tunnelForward = false) {
124
- const { AdbScrcpyClient, AdbScrcpyOptions3_3_3 } = await import("@yume-chan/adb-scrcpy");
125
- const { ReadableStream } = await import("@yume-chan/stream-extra");
126
- const { DefaultServerPath } = await import("@yume-chan/scrcpy");
127
- const serverBinPath = this.resolveServerBinPath();
128
- onProgress?.('pushing-server');
129
- await (0, timeout_namespaceObject.withTimeout)(AdbScrcpyClient.pushServer(adb, ReadableStream.from((0, external_node_fs_.createReadStream)(serverBinPath))), constants_namespaceObject.SCRCPY_PUSH_TIMEOUT_MS, `Timed out pushing scrcpy server to device after ${Math.round(constants_namespaceObject.SCRCPY_PUSH_TIMEOUT_MS / 1000)}s`);
130
- const scrcpyOptions = new AdbScrcpyOptions3_3_3({
131
- audio: false,
132
- control: true,
133
- maxSize: 1024,
134
- sendFrameMeta: true,
135
- videoBitRate: 2000000,
136
- ...options,
137
- tunnelForward
138
- });
139
- onProgress?.('starting-service');
140
- const startPromise = AdbScrcpyClient.start(adb, DefaultServerPath, scrcpyOptions);
141
- return await (0, timeout_namespaceObject.withTimeout)(startPromise, constants_namespaceObject.SCRCPY_START_TIMEOUT_MS, `Timed out starting scrcpy service after ${Math.round(constants_namespaceObject.SCRCPY_START_TIMEOUT_MS / 1000)}s`, {
142
- onSettledAfterTimeout: async (lateClient)=>{
143
- try {
144
- await lateClient.close();
145
- } catch (closeError) {
146
- console.error('failed to close late scrcpy client after timeout:', closeError);
147
- }
148
- }
149
- });
150
- }
151
- async startScrcpy(adb, options = {}, onProgress) {
152
- try {
153
- return await this.startScrcpyOnce(adb, options, onProgress, false);
154
- } catch (error) {
155
- if (!this.shouldRetryWithForwardTunnel(error)) throw error;
156
- warnScrcpy(`Reverse tunnel failed for device ${adb.serial}; retrying scrcpy with forward tunnel: ${error}`);
157
- return await this.startScrcpyOnce(adb, options, onProgress, true);
158
- }
159
- }
160
122
  resolveServerBinPath() {
161
- const androidPkgJson = (0, external_node_module_.createRequire)(__rslib_import_meta_url__).resolve('@midscene/android/package.json');
162
- return external_node_path_default().join(external_node_path_default().dirname(androidPkgJson), 'bin', 'scrcpy-server');
123
+ const androidPkgJson = (0, node_module__rspack_import_1.createRequire)(__rslib_import_meta_url__).resolve('@midscene/android/package.json');
124
+ return node_path__rspack_import_2_default().join(node_path__rspack_import_2_default().dirname(androidPkgJson), 'bin', 'scrcpy-server');
163
125
  }
164
126
  getFfmpegPath() {
165
127
  try {
166
- const dynamicRequire = (0, external_node_module_.createRequire)(__rslib_import_meta_url__);
128
+ const dynamicRequire = (0, node_module__rspack_import_1.createRequire)(__rslib_import_meta_url__);
167
129
  const ffmpegInstaller = dynamicRequire('@ffmpeg-installer/ffmpeg');
168
130
  debugScrcpy(`Using ffmpeg from npm package: ${ffmpegInstaller.path}`);
169
131
  return ffmpegInstaller.path;
@@ -504,6 +466,45 @@ var __webpack_exports__ = {};
504
466
  const agent_namespaceObject = require("@midscene/core/agent");
505
467
  var logger_ = __webpack_require__("@midscene/shared/logger");
506
468
  const utils_namespaceObject = require("@midscene/shared/utils");
469
+ const EMPTY_ADB_SHELL_STDOUT = '<empty>';
470
+ const MAX_RUN_ADB_SHELL_STDOUT = 200;
471
+ function normalizeShellStream(value) {
472
+ if (null == value) return '';
473
+ return String(value);
474
+ }
475
+ function truncateAdbShellStream(output, streamName) {
476
+ if (output.length <= MAX_RUN_ADB_SHELL_STDOUT) return output;
477
+ return `${output.slice(0, MAX_RUN_ADB_SHELL_STDOUT)}
478
+ ...[${streamName} truncated, ${output.length - MAX_RUN_ADB_SHELL_STDOUT} more characters]`;
479
+ }
480
+ function buildRunAdbShellPlanningFeedback({ command, stdout }) {
481
+ if ('' === stdout) return;
482
+ const commandText = 'string' == typeof command && command.length > 0 ? `Command: ${command}\n` : '';
483
+ return `${commandText}Stdout:
484
+ ${stdout}`;
485
+ }
486
+ function buildAdbShellStderrErrorMessage(command, stdout, stderr) {
487
+ return `RunAdbShell command returned stderr.
488
+ Command: ${command}
489
+ Stderr:
490
+ ${truncateAdbShellStream(stderr, 'stderr')}
491
+ Stdout:
492
+ ${stdout ? truncateAdbShellStream(stdout, 'stdout') : EMPTY_ADB_SHELL_STDOUT}`;
493
+ }
494
+ function getAdbShellStdoutOrThrow(command, output) {
495
+ if ('string' == typeof output) return output;
496
+ const stdout = normalizeShellStream(output.stdout);
497
+ const stderr = normalizeShellStream(output.stderr);
498
+ if (stderr) throw new Error(buildAdbShellStderrErrorMessage(command, stdout, stderr));
499
+ return stdout;
500
+ }
501
+ async function runAdbShellStdoutOrThrow(adb, command, options = {}) {
502
+ const output = await adb.shell(command, {
503
+ ...options,
504
+ outputFormat: adb.EXEC_OUTPUT_FORMAT.FULL
505
+ });
506
+ return getAdbShellStdoutOrThrow(command, output);
507
+ }
507
508
  const defaultAppNameMapping = {
508
509
  微信: 'com.tencent.mm',
509
510
  QQ: 'com.tencent.mobileqq',
@@ -1850,16 +1851,22 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1850
1851
  const createPlatformActions = (device)=>({
1851
1852
  RunAdbShell: (0, device_namespaceObject.defineAction)({
1852
1853
  name: 'RunAdbShell',
1853
- description: 'Execute ADB shell command on Android device',
1854
+ description: 'Execute an ADB shell command on the Android device and return the command stdout. Read the returned stdout to decide the next step; the stdout may indicate either success or failure.',
1854
1855
  interfaceAlias: 'runAdbShell',
1855
1856
  paramSchema: runAdbShellParamSchema,
1856
1857
  sample: {
1857
1858
  command: 'dumpsys window displays | grep -E "mCurrentFocus"'
1858
1859
  },
1859
- call: async (param)=>{
1860
+ call: async (param, context)=>{
1860
1861
  if (!param.command || '' === param.command.trim()) throw new Error('RunAdbShell requires a non-empty command parameter');
1861
1862
  const adb = await device.getAdb();
1862
- return await adb.shell(param.command);
1863
+ const stdout = await runAdbShellStdoutOrThrow(adb, param.command);
1864
+ const planningFeedback = buildRunAdbShellPlanningFeedback({
1865
+ command: param.command,
1866
+ stdout
1867
+ });
1868
+ if (planningFeedback && context?.task) context.task.planningFeedback = planningFeedback;
1869
+ return stdout;
1863
1870
  }
1864
1871
  }),
1865
1872
  Launch: (0, device_namespaceObject.defineAction)({
@@ -1929,7 +1936,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1929
1936
  async runAdbShell(command, opt) {
1930
1937
  if (opt?.timeout !== void 0) {
1931
1938
  const adb = await this.interface.getAdb();
1932
- return await adb.shell(command, {
1939
+ return await runAdbShellStdoutOrThrow(adb, command, {
1933
1940
  timeout: opt.timeout
1934
1941
  });
1935
1942
  }
@@ -2072,7 +2079,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
2072
2079
  constructor(toolsManager){
2073
2080
  super({
2074
2081
  name: '@midscene/android-mcp',
2075
- version: "1.9.5-beta-20260611045217.0",
2082
+ version: "1.9.5",
2076
2083
  description: 'Control the Android device using natural language commands'
2077
2084
  }, toolsManager);
2078
2085
  }
@@ -369,7 +369,6 @@ declare class ScrcpyScreenshotManager {
369
369
  private videoResolution;
370
370
  private streamReader;
371
371
  constructor(adb: Adb, options?: ScrcpyScreenshotOptions);
372
- private shouldRetryWithForwardTunnel;
373
372
  /**
374
373
  * Validate environment prerequisites (ffmpeg, scrcpy-server, etc.)
375
374
  * Must be called once after construction, before any screenshot operations.
@@ -380,8 +379,6 @@ declare class ScrcpyScreenshotManager {
380
379
  * Ensure scrcpy connection is active
381
380
  */
382
381
  ensureConnected(): Promise<void>;
383
- private startScrcpyOnce;
384
- private startScrcpy;
385
382
  /**
386
383
  * Resolve path to scrcpy server binary
387
384
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midscene/android",
3
- "version": "1.9.5-beta-20260611045217.0",
3
+ "version": "1.9.5",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/web-infra-dev/midscene.git",
@@ -46,8 +46,8 @@
46
46
  "@yume-chan/stream-extra": "2.1.0",
47
47
  "appium-adb": "12.12.1",
48
48
  "sharp": "^0.34.3",
49
- "@midscene/core": "1.9.5-beta-20260611045217.0",
50
- "@midscene/shared": "1.9.5-beta-20260611045217.0"
49
+ "@midscene/shared": "1.9.5",
50
+ "@midscene/core": "1.9.5"
51
51
  },
52
52
  "optionalDependencies": {
53
53
  "@ffmpeg-installer/ffmpeg": "^1.1.0"
@@ -61,7 +61,7 @@
61
61
  "undici": "^6.0.0",
62
62
  "vitest": "3.0.5",
63
63
  "zod": "^3.25.1",
64
- "@midscene/playground": "1.9.5-beta-20260611045217.0"
64
+ "@midscene/playground": "1.9.5"
65
65
  },
66
66
  "license": "MIT",
67
67
  "scripts": {