@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/es/cli.mjs +139 -132
- package/dist/es/index.mjs +136 -130
- package/dist/es/mcp-server.mjs +138 -131
- package/dist/lib/cli.js +67 -60
- package/dist/lib/index.js +66 -59
- package/dist/lib/mcp-server.js +67 -60
- package/dist/types/index.d.ts +0 -3
- package/package.json +4 -4
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
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
-
var
|
|
15
|
-
|
|
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,
|
|
29
|
-
const warnScrcpy = (0,
|
|
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
|
-
|
|
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,
|
|
162
|
-
return
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
-
var
|
|
15
|
-
|
|
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,
|
|
29
|
-
const warnScrcpy = (0,
|
|
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
|
-
|
|
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,
|
|
162
|
-
return
|
|
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,
|
|
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
|
-
|
|
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
|
|
2010
|
+
return await runAdbShellStdoutOrThrow(adb, command, {
|
|
2004
2011
|
timeout: opt.timeout
|
|
2005
2012
|
});
|
|
2006
2013
|
}
|
package/dist/lib/mcp-server.js
CHANGED
|
@@ -8,13 +8,11 @@ var __webpack_modules__ = {
|
|
|
8
8
|
ScrcpyScreenshotManager: ()=>ScrcpyScreenshotManager,
|
|
9
9
|
o: ()=>DEFAULT_SCRCPY_CONFIG
|
|
10
10
|
});
|
|
11
|
-
var
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
-
var
|
|
15
|
-
|
|
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,
|
|
29
|
-
const warnScrcpy = (0,
|
|
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
|
-
|
|
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,
|
|
162
|
-
return
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
2082
|
+
version: "1.9.5",
|
|
2076
2083
|
description: 'Control the Android device using natural language commands'
|
|
2077
2084
|
}, toolsManager);
|
|
2078
2085
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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
|
|
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/
|
|
50
|
-
"@midscene/
|
|
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
|
|
64
|
+
"@midscene/playground": "1.9.5"
|
|
65
65
|
},
|
|
66
66
|
"license": "MIT",
|
|
67
67
|
"scripts": {
|