@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/es/cli.mjs
CHANGED
|
@@ -2,30 +2,28 @@ import * as __rspack_external__midscene_shared_logger_b1dc2426 from "@midscene/s
|
|
|
2
2
|
import * as __rspack_external_node_fs_5ea92f0c from "node:fs";
|
|
3
3
|
import * as __rspack_external_node_module_ab9f2194 from "node:module";
|
|
4
4
|
import * as __rspack_external_node_path_c5b9b54f from "node:path";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import * as __rspack_external__midscene_shared_mcp_base_tools_a0c805f3 from "@midscene/shared/mcp/base-tools";
|
|
18
|
-
import * as __rspack_external__midscene_shared_cli_24fbb5ae from "@midscene/shared/cli";
|
|
5
|
+
import { createReportCliCommands, getMidsceneLocationSchema, z } from "@midscene/core";
|
|
6
|
+
import { reportCLIError, runToolsCLI } from "@midscene/shared/cli";
|
|
7
|
+
import { BaseMidsceneTools } from "@midscene/shared/mcp/base-tools";
|
|
8
|
+
import { Agent } from "@midscene/core/agent";
|
|
9
|
+
import { mergeAndNormalizeAppNameMapping, normalizeForComparison, repeat } from "@midscene/shared/utils";
|
|
10
|
+
import node_assert from "node:assert";
|
|
11
|
+
import { execFile } from "node:child_process";
|
|
12
|
+
import { createDefaultMobileActions, defineAction } from "@midscene/core/device";
|
|
13
|
+
import { getTmpFile, sleep } from "@midscene/core/utils";
|
|
14
|
+
import { MIDSCENE_ADB_PATH, MIDSCENE_ADB_REMOTE_HOST, MIDSCENE_ADB_REMOTE_PORT, MIDSCENE_ANDROID_IME_STRATEGY, globalConfigManager } from "@midscene/shared/env";
|
|
15
|
+
import { createImgBase64ByFormat, validateScreenshotBuffer } from "@midscene/shared/img";
|
|
16
|
+
import { ADB as external_appium_adb_ADB } from "appium-adb";
|
|
19
17
|
var __webpack_modules__ = {
|
|
20
18
|
"./src/scrcpy-manager.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
21
19
|
__webpack_require__.d(__webpack_exports__, {
|
|
22
20
|
ScrcpyScreenshotManager: ()=>ScrcpyScreenshotManager,
|
|
23
21
|
o: ()=>DEFAULT_SCRCPY_CONFIG
|
|
24
22
|
});
|
|
25
|
-
var
|
|
26
|
-
var
|
|
27
|
-
var
|
|
28
|
-
var
|
|
23
|
+
var node_fs__rspack_import_0 = __webpack_require__("node:fs");
|
|
24
|
+
var node_module__rspack_import_1 = __webpack_require__("node:module");
|
|
25
|
+
var node_path__rspack_import_2 = __webpack_require__("node:path");
|
|
26
|
+
var _midscene_shared_logger__rspack_import_3 = __webpack_require__("@midscene/shared/logger");
|
|
29
27
|
function _define_property(obj, key, value) {
|
|
30
28
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
31
29
|
value: value,
|
|
@@ -36,8 +34,8 @@ var __webpack_modules__ = {
|
|
|
36
34
|
else obj[key] = value;
|
|
37
35
|
return obj;
|
|
38
36
|
}
|
|
39
|
-
const debugScrcpy = (0,
|
|
40
|
-
const warnScrcpy = (0,
|
|
37
|
+
const debugScrcpy = (0, _midscene_shared_logger__rspack_import_3.getDebug)('android:scrcpy');
|
|
38
|
+
const warnScrcpy = (0, _midscene_shared_logger__rspack_import_3.getDebug)('android:scrcpy', {
|
|
41
39
|
console: true
|
|
42
40
|
});
|
|
43
41
|
const NAL_TYPE_IDR = 5;
|
|
@@ -78,11 +76,6 @@ var __webpack_modules__ = {
|
|
|
78
76
|
return false;
|
|
79
77
|
}
|
|
80
78
|
class ScrcpyScreenshotManager {
|
|
81
|
-
shouldRetryWithForwardTunnel(error) {
|
|
82
|
-
const cause = error instanceof Error ? error.cause : void 0;
|
|
83
|
-
const message = error instanceof Error ? `${error.message}${cause instanceof Error ? ` ${cause.message}` : ''}` : String(error);
|
|
84
|
-
return /more than one device\/emulator/i.test(message);
|
|
85
|
-
}
|
|
86
79
|
async validateEnvironment() {
|
|
87
80
|
await this.ensureFfmpegAvailable();
|
|
88
81
|
}
|
|
@@ -101,7 +94,12 @@ var __webpack_modules__ = {
|
|
|
101
94
|
try {
|
|
102
95
|
this.isConnecting = true;
|
|
103
96
|
debugScrcpy('Starting scrcpy connection...');
|
|
104
|
-
|
|
97
|
+
const { AdbScrcpyClient, AdbScrcpyOptions3_3_3 } = await import("@yume-chan/adb-scrcpy");
|
|
98
|
+
const { ReadableStream } = await import("@yume-chan/stream-extra");
|
|
99
|
+
const { DefaultServerPath } = await import("@yume-chan/scrcpy");
|
|
100
|
+
const serverBinPath = this.resolveServerBinPath();
|
|
101
|
+
await AdbScrcpyClient.pushServer(this.adb, ReadableStream.from((0, node_fs__rspack_import_0.createReadStream)(serverBinPath)));
|
|
102
|
+
const scrcpyOptions = new AdbScrcpyOptions3_3_3({
|
|
105
103
|
audio: false,
|
|
106
104
|
control: false,
|
|
107
105
|
maxSize: this.options.maxSize,
|
|
@@ -110,6 +108,7 @@ var __webpack_modules__ = {
|
|
|
110
108
|
sendFrameMeta: true,
|
|
111
109
|
videoCodecOptions: 'i-frame-interval=0,bitrate-mode=2'
|
|
112
110
|
});
|
|
111
|
+
this.scrcpyClient = await AdbScrcpyClient.start(this.adb, DefaultServerPath, scrcpyOptions);
|
|
113
112
|
const videoStreamPromise = this.scrcpyClient.videoStream;
|
|
114
113
|
if (!videoStreamPromise) throw new Error('Scrcpy client did not provide video stream');
|
|
115
114
|
this.videoStream = await videoStreamPromise;
|
|
@@ -131,50 +130,13 @@ var __webpack_modules__ = {
|
|
|
131
130
|
this.isConnecting = false;
|
|
132
131
|
}
|
|
133
132
|
}
|
|
134
|
-
async startScrcpyOnce(adb, options = {}, onProgress, tunnelForward = false) {
|
|
135
|
-
const { AdbScrcpyClient, AdbScrcpyOptions3_3_3 } = await import("@yume-chan/adb-scrcpy");
|
|
136
|
-
const { ReadableStream } = await import("@yume-chan/stream-extra");
|
|
137
|
-
const { DefaultServerPath } = await import("@yume-chan/scrcpy");
|
|
138
|
-
const serverBinPath = this.resolveServerBinPath();
|
|
139
|
-
onProgress?.('pushing-server');
|
|
140
|
-
await (0, __rspack_external__midscene_shared_timeout_982edd16.withTimeout)(AdbScrcpyClient.pushServer(adb, ReadableStream.from((0, external_node_fs_.createReadStream)(serverBinPath))), __rspack_external__midscene_shared_constants_ab069bca.SCRCPY_PUSH_TIMEOUT_MS, `Timed out pushing scrcpy server to device after ${Math.round(__rspack_external__midscene_shared_constants_ab069bca.SCRCPY_PUSH_TIMEOUT_MS / 1000)}s`);
|
|
141
|
-
const scrcpyOptions = new AdbScrcpyOptions3_3_3({
|
|
142
|
-
audio: false,
|
|
143
|
-
control: true,
|
|
144
|
-
maxSize: 1024,
|
|
145
|
-
sendFrameMeta: true,
|
|
146
|
-
videoBitRate: 2000000,
|
|
147
|
-
...options,
|
|
148
|
-
tunnelForward
|
|
149
|
-
});
|
|
150
|
-
onProgress?.('starting-service');
|
|
151
|
-
const startPromise = AdbScrcpyClient.start(adb, DefaultServerPath, scrcpyOptions);
|
|
152
|
-
return await (0, __rspack_external__midscene_shared_timeout_982edd16.withTimeout)(startPromise, __rspack_external__midscene_shared_constants_ab069bca.SCRCPY_START_TIMEOUT_MS, `Timed out starting scrcpy service after ${Math.round(__rspack_external__midscene_shared_constants_ab069bca.SCRCPY_START_TIMEOUT_MS / 1000)}s`, {
|
|
153
|
-
onSettledAfterTimeout: async (lateClient)=>{
|
|
154
|
-
try {
|
|
155
|
-
await lateClient.close();
|
|
156
|
-
} catch (closeError) {
|
|
157
|
-
console.error('failed to close late scrcpy client after timeout:', closeError);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
async startScrcpy(adb, options = {}, onProgress) {
|
|
163
|
-
try {
|
|
164
|
-
return await this.startScrcpyOnce(adb, options, onProgress, false);
|
|
165
|
-
} catch (error) {
|
|
166
|
-
if (!this.shouldRetryWithForwardTunnel(error)) throw error;
|
|
167
|
-
warnScrcpy(`Reverse tunnel failed for device ${adb.serial}; retrying scrcpy with forward tunnel: ${error}`);
|
|
168
|
-
return await this.startScrcpyOnce(adb, options, onProgress, true);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
133
|
resolveServerBinPath() {
|
|
172
|
-
const androidPkgJson = (0,
|
|
173
|
-
return
|
|
134
|
+
const androidPkgJson = (0, node_module__rspack_import_1.createRequire)(import.meta.url).resolve('@midscene/android/package.json');
|
|
135
|
+
return node_path__rspack_import_2["default"].join(node_path__rspack_import_2["default"].dirname(androidPkgJson), 'bin', 'scrcpy-server');
|
|
174
136
|
}
|
|
175
137
|
getFfmpegPath() {
|
|
176
138
|
try {
|
|
177
|
-
const dynamicRequire = (0,
|
|
139
|
+
const dynamicRequire = (0, node_module__rspack_import_1.createRequire)(import.meta.url);
|
|
178
140
|
const ffmpegInstaller = dynamicRequire('@ffmpeg-installer/ffmpeg');
|
|
179
141
|
debugScrcpy(`Using ffmpeg from npm package: ${ffmpegInstaller.path}`);
|
|
180
142
|
return ffmpegInstaller.path;
|
|
@@ -485,6 +447,45 @@ function __webpack_require__(moduleId) {
|
|
|
485
447
|
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
486
448
|
})();
|
|
487
449
|
var logger_ = __webpack_require__("@midscene/shared/logger");
|
|
450
|
+
const EMPTY_ADB_SHELL_STDOUT = '<empty>';
|
|
451
|
+
const MAX_RUN_ADB_SHELL_STDOUT = 200;
|
|
452
|
+
function normalizeShellStream(value) {
|
|
453
|
+
if (null == value) return '';
|
|
454
|
+
return String(value);
|
|
455
|
+
}
|
|
456
|
+
function truncateAdbShellStream(output, streamName) {
|
|
457
|
+
if (output.length <= MAX_RUN_ADB_SHELL_STDOUT) return output;
|
|
458
|
+
return `${output.slice(0, MAX_RUN_ADB_SHELL_STDOUT)}
|
|
459
|
+
...[${streamName} truncated, ${output.length - MAX_RUN_ADB_SHELL_STDOUT} more characters]`;
|
|
460
|
+
}
|
|
461
|
+
function buildRunAdbShellPlanningFeedback({ command, stdout }) {
|
|
462
|
+
if ('' === stdout) return;
|
|
463
|
+
const commandText = 'string' == typeof command && command.length > 0 ? `Command: ${command}\n` : '';
|
|
464
|
+
return `${commandText}Stdout:
|
|
465
|
+
${stdout}`;
|
|
466
|
+
}
|
|
467
|
+
function buildAdbShellStderrErrorMessage(command, stdout, stderr) {
|
|
468
|
+
return `RunAdbShell command returned stderr.
|
|
469
|
+
Command: ${command}
|
|
470
|
+
Stderr:
|
|
471
|
+
${truncateAdbShellStream(stderr, 'stderr')}
|
|
472
|
+
Stdout:
|
|
473
|
+
${stdout ? truncateAdbShellStream(stdout, 'stdout') : EMPTY_ADB_SHELL_STDOUT}`;
|
|
474
|
+
}
|
|
475
|
+
function getAdbShellStdoutOrThrow(command, output) {
|
|
476
|
+
if ('string' == typeof output) return output;
|
|
477
|
+
const stdout = normalizeShellStream(output.stdout);
|
|
478
|
+
const stderr = normalizeShellStream(output.stderr);
|
|
479
|
+
if (stderr) throw new Error(buildAdbShellStderrErrorMessage(command, stdout, stderr));
|
|
480
|
+
return stdout;
|
|
481
|
+
}
|
|
482
|
+
async function runAdbShellStdoutOrThrow(adb, command, options = {}) {
|
|
483
|
+
const output = await adb.shell(command, {
|
|
484
|
+
...options,
|
|
485
|
+
outputFormat: adb.EXEC_OUTPUT_FORMAT.FULL
|
|
486
|
+
});
|
|
487
|
+
return getAdbShellStdoutOrThrow(command, output);
|
|
488
|
+
}
|
|
488
489
|
const defaultAppNameMapping = {
|
|
489
490
|
微信: 'com.tencent.mm',
|
|
490
491
|
QQ: 'com.tencent.mobileqq',
|
|
@@ -655,7 +656,7 @@ class ScrcpyDeviceAdapter {
|
|
|
655
656
|
async screenshotBase64(deviceInfo) {
|
|
656
657
|
const manager = await this.ensureManager(deviceInfo);
|
|
657
658
|
const screenshotBuffer = await manager.getScreenshotJpeg();
|
|
658
|
-
return
|
|
659
|
+
return createImgBase64ByFormat('jpeg', screenshotBuffer.toString('base64'));
|
|
659
660
|
}
|
|
660
661
|
getResolution() {
|
|
661
662
|
return this.manager?.getResolution() ?? null;
|
|
@@ -726,7 +727,7 @@ class AndroidDevice {
|
|
|
726
727
|
input: this.inputPrimitives,
|
|
727
728
|
size: ()=>this.size(),
|
|
728
729
|
sleep: async (timeMs)=>{
|
|
729
|
-
await
|
|
730
|
+
await sleep(timeMs);
|
|
730
731
|
},
|
|
731
732
|
getDefaultAutoDismissKeyboard: ()=>this.options?.autoDismissKeyboard,
|
|
732
733
|
systemActions: {
|
|
@@ -745,18 +746,18 @@ class AndroidDevice {
|
|
|
745
746
|
}
|
|
746
747
|
};
|
|
747
748
|
const defaultActions = [
|
|
748
|
-
...
|
|
749
|
-
|
|
749
|
+
...createDefaultMobileActions(mobileActionContext),
|
|
750
|
+
defineAction({
|
|
750
751
|
name: 'PullGesture',
|
|
751
752
|
description: 'Trigger pull down to refresh or pull up actions',
|
|
752
|
-
paramSchema:
|
|
753
|
-
direction:
|
|
753
|
+
paramSchema: z.object({
|
|
754
|
+
direction: z["enum"]([
|
|
754
755
|
'up',
|
|
755
756
|
'down'
|
|
756
757
|
]).describe('The direction to pull'),
|
|
757
|
-
distance:
|
|
758
|
-
duration:
|
|
759
|
-
locate:
|
|
758
|
+
distance: z.number().optional().describe('The distance to pull (in pixels)'),
|
|
759
|
+
duration: z.number().optional().describe('The duration of the pull (in milliseconds)'),
|
|
760
|
+
locate: getMidsceneLocationSchema().optional().describe('The element to start the pull from (optional)')
|
|
760
761
|
}),
|
|
761
762
|
sample: {
|
|
762
763
|
direction: 'down',
|
|
@@ -803,7 +804,7 @@ class AndroidDevice {
|
|
|
803
804
|
else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
|
|
804
805
|
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
805
806
|
else await this.scrollDown(param?.distance || void 0, startingPoint);
|
|
806
|
-
await
|
|
807
|
+
await sleep(500);
|
|
807
808
|
}
|
|
808
809
|
}
|
|
809
810
|
describe() {
|
|
@@ -830,10 +831,10 @@ class AndroidDevice {
|
|
|
830
831
|
let error = null;
|
|
831
832
|
debugDevice(`Initializing ADB with device ID: ${this.deviceId}`);
|
|
832
833
|
try {
|
|
833
|
-
const androidAdbPath = this.options?.androidAdbPath ||
|
|
834
|
-
const remoteAdbHost = this.options?.remoteAdbHost ||
|
|
835
|
-
const remoteAdbPort = this.options?.remoteAdbPort ||
|
|
836
|
-
this.adb = new
|
|
834
|
+
const androidAdbPath = this.options?.androidAdbPath || globalConfigManager.getEnvConfigValue(MIDSCENE_ADB_PATH);
|
|
835
|
+
const remoteAdbHost = this.options?.remoteAdbHost || globalConfigManager.getEnvConfigValue(MIDSCENE_ADB_REMOTE_HOST);
|
|
836
|
+
const remoteAdbPort = this.options?.remoteAdbPort || globalConfigManager.getEnvConfigValue(MIDSCENE_ADB_REMOTE_PORT);
|
|
837
|
+
this.adb = new external_appium_adb_ADB({
|
|
837
838
|
udid: this.deviceId,
|
|
838
839
|
adbExecTimeout: 60000,
|
|
839
840
|
executable: androidAdbPath ? {
|
|
@@ -907,7 +908,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
907
908
|
this.appNameMapping = mapping;
|
|
908
909
|
}
|
|
909
910
|
resolvePackageName(appName) {
|
|
910
|
-
const normalizedAppName =
|
|
911
|
+
const normalizedAppName = normalizeForComparison(appName);
|
|
911
912
|
return this.appNameMapping[normalizedAppName];
|
|
912
913
|
}
|
|
913
914
|
async launch(uri) {
|
|
@@ -1226,7 +1227,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1226
1227
|
screenshotBuffer = await adb.takeScreenshot.call(adb);
|
|
1227
1228
|
debugDevice('adb.takeScreenshot completed');
|
|
1228
1229
|
try {
|
|
1229
|
-
|
|
1230
|
+
validateScreenshotBuffer(screenshotBuffer, {
|
|
1230
1231
|
label: 'Screenshot',
|
|
1231
1232
|
minBufferSize: this.options?.minScreenshotBufferSize ?? AndroidDevice.DEFAULT_MIN_SCREENSHOT_BUFFER_SIZE
|
|
1232
1233
|
});
|
|
@@ -1242,7 +1243,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1242
1243
|
}
|
|
1243
1244
|
} catch (error) {
|
|
1244
1245
|
debugDevice(`Taking screenshot via adb.takeScreenshot failed or was skipped: ${error}`);
|
|
1245
|
-
const screenshotPath =
|
|
1246
|
+
const screenshotPath = getTmpFile('png');
|
|
1246
1247
|
localScreenshotPath = screenshotPath;
|
|
1247
1248
|
try {
|
|
1248
1249
|
debugDevice('Fallback: taking screenshot via shell screencap');
|
|
@@ -1260,14 +1261,14 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1260
1261
|
await adb.pull(androidScreenshotPath, screenshotPath);
|
|
1261
1262
|
debugDevice(`adb.pull completed, local path: ${screenshotPath}`);
|
|
1262
1263
|
screenshotBuffer = await external_node_fs_["default"].promises.readFile(screenshotPath);
|
|
1263
|
-
|
|
1264
|
+
validateScreenshotBuffer(screenshotBuffer, {
|
|
1264
1265
|
label: 'Fallback screenshot',
|
|
1265
1266
|
minBufferSize: this.options?.minScreenshotBufferSize ?? AndroidDevice.DEFAULT_MIN_SCREENSHOT_BUFFER_SIZE
|
|
1266
1267
|
});
|
|
1267
1268
|
debugDevice(`Fallback screenshot validated successfully: ${screenshotBuffer.length} bytes`);
|
|
1268
1269
|
} finally{
|
|
1269
1270
|
const adbPath = adb.executable?.path ?? 'adb';
|
|
1270
|
-
const child =
|
|
1271
|
+
const child = execFile(adbPath, [
|
|
1271
1272
|
'-s',
|
|
1272
1273
|
this.deviceId,
|
|
1273
1274
|
'shell',
|
|
@@ -1282,7 +1283,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1282
1283
|
}
|
|
1283
1284
|
if (!screenshotBuffer) throw new Error('Failed to capture screenshot: all methods failed');
|
|
1284
1285
|
debugDevice('Converting to base64');
|
|
1285
|
-
const result =
|
|
1286
|
+
const result = createImgBase64ByFormat('png', screenshotBuffer.toString('base64'));
|
|
1286
1287
|
if (localScreenshotPath) {
|
|
1287
1288
|
debugDevice(`Deleting local screenshot: ${localScreenshotPath}`);
|
|
1288
1289
|
(0, external_node_fs_.unlink)(localScreenshotPath, (unlinkError)=>{
|
|
@@ -1299,7 +1300,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1299
1300
|
});
|
|
1300
1301
|
await this.ensureYadb();
|
|
1301
1302
|
const adb = await this.getAdb();
|
|
1302
|
-
const IME_STRATEGY = (this.options?.imeStrategy ||
|
|
1303
|
+
const IME_STRATEGY = (this.options?.imeStrategy || globalConfigManager.getEnvConfigValue(MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
1303
1304
|
if (IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII) await adb.clearTextField(100);
|
|
1304
1305
|
else await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboardClear`);
|
|
1305
1306
|
if (await adb.isSoftKeyboardPresent()) return;
|
|
@@ -1327,12 +1328,12 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1327
1328
|
x: start.x,
|
|
1328
1329
|
y: Math.round(height)
|
|
1329
1330
|
};
|
|
1330
|
-
await
|
|
1331
|
-
await
|
|
1331
|
+
await repeat(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
|
|
1332
|
+
await sleep(1000);
|
|
1332
1333
|
return;
|
|
1333
1334
|
}
|
|
1334
|
-
await
|
|
1335
|
-
await
|
|
1335
|
+
await repeat(defaultScrollUntilTimes, ()=>this.scroll(0, -9999999, defaultFastScrollDuration));
|
|
1336
|
+
await sleep(1000);
|
|
1336
1337
|
}
|
|
1337
1338
|
async scrollUntilBottom(startPoint) {
|
|
1338
1339
|
if (startPoint) {
|
|
@@ -1344,12 +1345,12 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1344
1345
|
x: start.x,
|
|
1345
1346
|
y: 0
|
|
1346
1347
|
};
|
|
1347
|
-
await
|
|
1348
|
-
await
|
|
1348
|
+
await repeat(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
|
|
1349
|
+
await sleep(1000);
|
|
1349
1350
|
return;
|
|
1350
1351
|
}
|
|
1351
|
-
await
|
|
1352
|
-
await
|
|
1352
|
+
await repeat(defaultScrollUntilTimes, ()=>this.scroll(0, 9999999, defaultFastScrollDuration));
|
|
1353
|
+
await sleep(1000);
|
|
1353
1354
|
}
|
|
1354
1355
|
async scrollUntilLeft(startPoint) {
|
|
1355
1356
|
if (startPoint) {
|
|
@@ -1362,12 +1363,12 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1362
1363
|
x: Math.round(width),
|
|
1363
1364
|
y: start.y
|
|
1364
1365
|
};
|
|
1365
|
-
await
|
|
1366
|
-
await
|
|
1366
|
+
await repeat(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
|
|
1367
|
+
await sleep(1000);
|
|
1367
1368
|
return;
|
|
1368
1369
|
}
|
|
1369
|
-
await
|
|
1370
|
-
await
|
|
1370
|
+
await repeat(defaultScrollUntilTimes, ()=>this.scroll(-9999999, 0, defaultFastScrollDuration));
|
|
1371
|
+
await sleep(1000);
|
|
1371
1372
|
}
|
|
1372
1373
|
async scrollUntilRight(startPoint) {
|
|
1373
1374
|
if (startPoint) {
|
|
@@ -1379,12 +1380,12 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1379
1380
|
x: 0,
|
|
1380
1381
|
y: start.y
|
|
1381
1382
|
};
|
|
1382
|
-
await
|
|
1383
|
-
await
|
|
1383
|
+
await repeat(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
|
|
1384
|
+
await sleep(1000);
|
|
1384
1385
|
return;
|
|
1385
1386
|
}
|
|
1386
|
-
await
|
|
1387
|
-
await
|
|
1387
|
+
await repeat(defaultScrollUntilTimes, ()=>this.scroll(9999999, 0, defaultFastScrollDuration));
|
|
1388
|
+
await sleep(1000);
|
|
1388
1389
|
}
|
|
1389
1390
|
async scrollUp(distance, startPoint) {
|
|
1390
1391
|
const { height } = await this.size();
|
|
@@ -1469,7 +1470,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1469
1470
|
async typeText(text, options) {
|
|
1470
1471
|
if (!text) return;
|
|
1471
1472
|
const adb = await this.getAdb();
|
|
1472
|
-
const IME_STRATEGY = (this.options?.imeStrategy ||
|
|
1473
|
+
const IME_STRATEGY = (this.options?.imeStrategy || globalConfigManager.getEnvConfigValue(MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
1473
1474
|
const shouldAutoDismissKeyboard = options?.autoDismissKeyboard ?? this.options?.autoDismissKeyboard ?? true;
|
|
1474
1475
|
const useYadb = IME_STRATEGY === IME_STRATEGY_ALWAYS_YADB || IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII && this.shouldUseYadbForText(text);
|
|
1475
1476
|
if (useYadb) await this.execYadb(escapeForShell(text));
|
|
@@ -1535,7 +1536,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1535
1536
|
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(point.x, point.y);
|
|
1536
1537
|
const tapCommand = `input${this.getDisplayArg()} tap ${adjustedX} ${adjustedY}`;
|
|
1537
1538
|
await adb.shell(tapCommand);
|
|
1538
|
-
await
|
|
1539
|
+
await sleep(50);
|
|
1539
1540
|
await adb.shell(tapCommand);
|
|
1540
1541
|
}
|
|
1541
1542
|
async mouseMove() {
|
|
@@ -1646,7 +1647,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1646
1647
|
y: start.y + pullDistance
|
|
1647
1648
|
};
|
|
1648
1649
|
await this.pullDrag(start, end, duration);
|
|
1649
|
-
await
|
|
1650
|
+
await sleep(200);
|
|
1650
1651
|
}
|
|
1651
1652
|
async pullDrag(from, to, duration) {
|
|
1652
1653
|
await this.swipePoint(from, to, duration);
|
|
@@ -1666,7 +1667,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1666
1667
|
y: start.y - pullDistance
|
|
1667
1668
|
};
|
|
1668
1669
|
await this.pullDrag(start, end, duration);
|
|
1669
|
-
await
|
|
1670
|
+
await sleep(100);
|
|
1670
1671
|
}
|
|
1671
1672
|
getDisplayArg() {
|
|
1672
1673
|
return 'number' == typeof this.options?.displayId ? ` -d ${this.options.displayId}` : '';
|
|
@@ -1717,7 +1718,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1717
1718
|
const startTime = Date.now();
|
|
1718
1719
|
const intervalMs = 100;
|
|
1719
1720
|
while(Date.now() - startTime < timeoutMs){
|
|
1720
|
-
await
|
|
1721
|
+
await sleep(intervalMs);
|
|
1721
1722
|
const currentStatus = await adb.isSoftKeyboardPresent();
|
|
1722
1723
|
const isStillShown = 'boolean' == typeof currentStatus ? currentStatus : currentStatus?.isKeyboardShown;
|
|
1723
1724
|
if (!isStillShown) {
|
|
@@ -1800,7 +1801,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1800
1801
|
recentAppsButton: ()=>this.recentApps()
|
|
1801
1802
|
}
|
|
1802
1803
|
});
|
|
1803
|
-
(
|
|
1804
|
+
node_assert(deviceId, 'deviceId is required for AndroidDevice');
|
|
1804
1805
|
this.deviceId = deviceId;
|
|
1805
1806
|
this.options = options;
|
|
1806
1807
|
this.customActions = options?.customActions;
|
|
@@ -1808,31 +1809,37 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1808
1809
|
}
|
|
1809
1810
|
device_define_property(AndroidDevice, "TAKE_SCREENSHOT_FAIL_THRESHOLD", 3);
|
|
1810
1811
|
device_define_property(AndroidDevice, "DEFAULT_MIN_SCREENSHOT_BUFFER_SIZE", 1024);
|
|
1811
|
-
const runAdbShellParamSchema =
|
|
1812
|
-
command:
|
|
1812
|
+
const runAdbShellParamSchema = z.object({
|
|
1813
|
+
command: z.string().describe('ADB shell command to execute')
|
|
1813
1814
|
});
|
|
1814
|
-
const launchParamSchema =
|
|
1815
|
-
uri:
|
|
1815
|
+
const launchParamSchema = z.object({
|
|
1816
|
+
uri: z.string().describe('App name, package name, or URL to launch. Prioritize using the exact package name or URL the user has provided. If none provided, use the accurate app name.')
|
|
1816
1817
|
});
|
|
1817
|
-
const terminateParamSchema =
|
|
1818
|
-
uri:
|
|
1818
|
+
const terminateParamSchema = z.object({
|
|
1819
|
+
uri: z.string().describe('Package name or app name to terminate. Use the exact package name, e.g. com.android.settings.')
|
|
1819
1820
|
});
|
|
1820
1821
|
const createPlatformActions = (device)=>({
|
|
1821
|
-
RunAdbShell:
|
|
1822
|
+
RunAdbShell: defineAction({
|
|
1822
1823
|
name: 'RunAdbShell',
|
|
1823
|
-
description: 'Execute ADB shell command on Android device',
|
|
1824
|
+
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.',
|
|
1824
1825
|
interfaceAlias: 'runAdbShell',
|
|
1825
1826
|
paramSchema: runAdbShellParamSchema,
|
|
1826
1827
|
sample: {
|
|
1827
1828
|
command: 'dumpsys window displays | grep -E "mCurrentFocus"'
|
|
1828
1829
|
},
|
|
1829
|
-
call: async (param)=>{
|
|
1830
|
+
call: async (param, context)=>{
|
|
1830
1831
|
if (!param.command || '' === param.command.trim()) throw new Error('RunAdbShell requires a non-empty command parameter');
|
|
1831
1832
|
const adb = await device.getAdb();
|
|
1832
|
-
|
|
1833
|
+
const stdout = await runAdbShellStdoutOrThrow(adb, param.command);
|
|
1834
|
+
const planningFeedback = buildRunAdbShellPlanningFeedback({
|
|
1835
|
+
command: param.command,
|
|
1836
|
+
stdout
|
|
1837
|
+
});
|
|
1838
|
+
if (planningFeedback && context?.task) context.task.planningFeedback = planningFeedback;
|
|
1839
|
+
return stdout;
|
|
1833
1840
|
}
|
|
1834
1841
|
}),
|
|
1835
|
-
Launch:
|
|
1842
|
+
Launch: defineAction({
|
|
1836
1843
|
name: 'Launch',
|
|
1837
1844
|
description: 'Launch an Android app or URL',
|
|
1838
1845
|
interfaceAlias: 'launch',
|
|
@@ -1845,7 +1852,7 @@ const createPlatformActions = (device)=>({
|
|
|
1845
1852
|
await device.launch(param.uri);
|
|
1846
1853
|
}
|
|
1847
1854
|
}),
|
|
1848
|
-
Terminate:
|
|
1855
|
+
Terminate: defineAction({
|
|
1849
1856
|
name: 'Terminate',
|
|
1850
1857
|
description: 'Terminate (force-stop) an Android app by package name',
|
|
1851
1858
|
interfaceAlias: 'terminate',
|
|
@@ -1859,7 +1866,7 @@ const createPlatformActions = (device)=>({
|
|
|
1859
1866
|
const debugUtils = (0, logger_.getDebug)('android:utils');
|
|
1860
1867
|
async function getConnectedDevices() {
|
|
1861
1868
|
try {
|
|
1862
|
-
const adb = await
|
|
1869
|
+
const adb = await external_appium_adb_ADB.createADB({
|
|
1863
1870
|
adbExecTimeout: 60000
|
|
1864
1871
|
});
|
|
1865
1872
|
const devices = await adb.getConnectedDevices();
|
|
@@ -1883,7 +1890,7 @@ function agent_define_property(obj, key, value) {
|
|
|
1883
1890
|
return obj;
|
|
1884
1891
|
}
|
|
1885
1892
|
const debugAgent = (0, logger_.getDebug)('android:agent');
|
|
1886
|
-
class AndroidAgent extends
|
|
1893
|
+
class AndroidAgent extends Agent {
|
|
1887
1894
|
async launch(uri) {
|
|
1888
1895
|
const action = this.wrapActionInActionSpace('Launch');
|
|
1889
1896
|
return action({
|
|
@@ -1899,7 +1906,7 @@ class AndroidAgent extends __rspack_external__midscene_core_agent_2b7f9158.Agent
|
|
|
1899
1906
|
async runAdbShell(command, opt) {
|
|
1900
1907
|
if (opt?.timeout !== void 0) {
|
|
1901
1908
|
const adb = await this.interface.getAdb();
|
|
1902
|
-
return await adb
|
|
1909
|
+
return await runAdbShellStdoutOrThrow(adb, command, {
|
|
1903
1910
|
timeout: opt.timeout
|
|
1904
1911
|
});
|
|
1905
1912
|
}
|
|
@@ -1914,7 +1921,7 @@ class AndroidAgent extends __rspack_external__midscene_core_agent_2b7f9158.Agent
|
|
|
1914
1921
|
}
|
|
1915
1922
|
constructor(device, opts){
|
|
1916
1923
|
super(device, opts), agent_define_property(this, "back", void 0), agent_define_property(this, "home", void 0), agent_define_property(this, "recentApps", void 0), agent_define_property(this, "appNameMapping", void 0);
|
|
1917
|
-
this.appNameMapping =
|
|
1924
|
+
this.appNameMapping = mergeAndNormalizeAppNameMapping(defaultAppNameMapping, opts?.appNameMapping);
|
|
1918
1925
|
device.setAppNameMapping(this.appNameMapping);
|
|
1919
1926
|
this.back = this.createActionWrapper('AndroidBackButton');
|
|
1920
1927
|
this.home = this.createActionWrapper('AndroidHomeButton');
|
|
@@ -1943,7 +1950,7 @@ function mcp_tools_define_property(obj, key, value) {
|
|
|
1943
1950
|
return obj;
|
|
1944
1951
|
}
|
|
1945
1952
|
const debug = (0, logger_.getDebug)('mcp:android-tools');
|
|
1946
|
-
class AndroidMidsceneTools extends
|
|
1953
|
+
class AndroidMidsceneTools extends BaseMidsceneTools {
|
|
1947
1954
|
getCliReportSessionName() {
|
|
1948
1955
|
return 'midscene-android';
|
|
1949
1956
|
}
|
|
@@ -2021,8 +2028,8 @@ class AndroidMidsceneTools extends __rspack_external__midscene_shared_mcp_base_t
|
|
|
2021
2028
|
super(...args), mcp_tools_define_property(this, "initArgSpec", {
|
|
2022
2029
|
namespace: 'android',
|
|
2023
2030
|
shape: {
|
|
2024
|
-
deviceId:
|
|
2025
|
-
useScrcpy:
|
|
2031
|
+
deviceId: z.string().optional().describe('Android device ID (from adb devices)'),
|
|
2032
|
+
useScrcpy: z.boolean().optional().describe('Enable scrcpy accelerated screenshots')
|
|
2026
2033
|
},
|
|
2027
2034
|
cli: {
|
|
2028
2035
|
preferBareKeys: true
|
|
@@ -2035,10 +2042,10 @@ class AndroidMidsceneTools extends __rspack_external__midscene_shared_mcp_base_t
|
|
|
2035
2042
|
}
|
|
2036
2043
|
}
|
|
2037
2044
|
const tools = new AndroidMidsceneTools();
|
|
2038
|
-
|
|
2045
|
+
runToolsCLI(tools, 'midscene-android', {
|
|
2039
2046
|
stripPrefix: 'android_',
|
|
2040
|
-
version: "1.9.5
|
|
2041
|
-
extraCommands:
|
|
2047
|
+
version: "1.9.5",
|
|
2048
|
+
extraCommands: createReportCliCommands()
|
|
2042
2049
|
}).catch((e)=>{
|
|
2043
|
-
process.exit(
|
|
2050
|
+
process.exit(reportCLIError(e));
|
|
2044
2051
|
});
|