@midscene/android 1.7.4 → 1.7.5-beta-20260418223706.0
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 +42 -8
- package/dist/es/index.mjs +41 -7
- package/dist/es/mcp-server.mjs +42 -8
- package/dist/lib/cli.js +42 -8
- package/dist/lib/index.js +41 -7
- package/dist/lib/mcp-server.js +42 -8
- package/dist/types/index.d.ts +4 -1
- package/dist/types/mcp-server.d.ts +4 -1
- package/package.json +5 -5
package/dist/es/cli.mjs
CHANGED
|
@@ -676,6 +676,9 @@ const defaultNormalScrollDuration = 1000;
|
|
|
676
676
|
const IME_STRATEGY_ALWAYS_YADB = 'always-yadb';
|
|
677
677
|
const IME_STRATEGY_YADB_FOR_NON_ASCII = 'yadb-for-non-ascii';
|
|
678
678
|
const debugDevice = (0, logger_.getDebug)('android:device');
|
|
679
|
+
const warnDevice = (0, logger_.getDebug)('android:device', {
|
|
680
|
+
console: true
|
|
681
|
+
});
|
|
679
682
|
function escapeForShell(text) {
|
|
680
683
|
return text.replace(/'/g, "'\\''").replace(/\n/g, '\\n');
|
|
681
684
|
}
|
|
@@ -855,7 +858,7 @@ class AndroidDevice {
|
|
|
855
858
|
console.log(`[midscene] Using scrcpy for screenshots (device: ${this.deviceId})`);
|
|
856
859
|
} catch (error) {
|
|
857
860
|
const msg = error instanceof Error ? error.message : String(error);
|
|
858
|
-
|
|
861
|
+
warnDevice(`[midscene] Scrcpy unavailable, using ADB fallback (device: ${this.deviceId}): ${msg}`);
|
|
859
862
|
}
|
|
860
863
|
return adb;
|
|
861
864
|
}
|
|
@@ -1223,6 +1226,22 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1223
1226
|
y: endY
|
|
1224
1227
|
};
|
|
1225
1228
|
}
|
|
1229
|
+
warnScrollDistanceClamped(direction, requestedDistance, appliedDistance) {
|
|
1230
|
+
if (requestedDistance <= appliedDistance) return;
|
|
1231
|
+
const scrollToSuggestion = {
|
|
1232
|
+
down: 'scrollToBottom',
|
|
1233
|
+
up: 'scrollToTop',
|
|
1234
|
+
left: 'scrollToLeft',
|
|
1235
|
+
right: 'scrollToRight'
|
|
1236
|
+
};
|
|
1237
|
+
const edgeLabel = {
|
|
1238
|
+
down: 'bottom',
|
|
1239
|
+
up: 'top',
|
|
1240
|
+
left: 'left edge',
|
|
1241
|
+
right: 'right edge'
|
|
1242
|
+
};
|
|
1243
|
+
warnDevice(`[midscene] Android ADB swipe coordinates must stay within the screen bounds. The requested scroll distance (${requestedDistance}px) exceeds the maximum single swipe distance (${appliedDistance}px) from the current start point, so it will be clamped. If you want to scroll to the ${edgeLabel[direction]}, use ${scrollToSuggestion[direction]} instead.`);
|
|
1244
|
+
}
|
|
1226
1245
|
async screenshotBase64() {
|
|
1227
1246
|
debugDevice('screenshotBase64 begin');
|
|
1228
1247
|
const adapter = this.getScrcpyAdapter();
|
|
@@ -1407,58 +1426,66 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1407
1426
|
async scrollUp(distance, startPoint) {
|
|
1408
1427
|
const { height } = await this.size();
|
|
1409
1428
|
const scrollDistance = Math.round(distance || height);
|
|
1429
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1410
1430
|
if (startPoint) {
|
|
1411
1431
|
const start = {
|
|
1412
1432
|
x: Math.round(startPoint.left),
|
|
1413
1433
|
y: Math.round(startPoint.top)
|
|
1414
1434
|
};
|
|
1415
1435
|
const end = this.calculateScrollEndPoint(start, 0, scrollDistance, 0, height);
|
|
1436
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('up', scrollDistance, Math.abs(end.y - start.y));
|
|
1416
1437
|
await this.mouseDrag(start, end);
|
|
1417
1438
|
return;
|
|
1418
1439
|
}
|
|
1419
|
-
await this.scroll(0, -scrollDistance);
|
|
1440
|
+
await this.scroll(0, -scrollDistance, void 0, hasExplicitDistance, 'up');
|
|
1420
1441
|
}
|
|
1421
1442
|
async scrollDown(distance, startPoint) {
|
|
1422
1443
|
const { height } = await this.size();
|
|
1423
1444
|
const scrollDistance = Math.round(distance || height);
|
|
1445
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1424
1446
|
if (startPoint) {
|
|
1425
1447
|
const start = {
|
|
1426
1448
|
x: Math.round(startPoint.left),
|
|
1427
1449
|
y: Math.round(startPoint.top)
|
|
1428
1450
|
};
|
|
1429
1451
|
const end = this.calculateScrollEndPoint(start, 0, -scrollDistance, 0, height);
|
|
1452
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('down', scrollDistance, Math.abs(end.y - start.y));
|
|
1430
1453
|
await this.mouseDrag(start, end);
|
|
1431
1454
|
return;
|
|
1432
1455
|
}
|
|
1433
|
-
await this.scroll(0, scrollDistance);
|
|
1456
|
+
await this.scroll(0, scrollDistance, void 0, hasExplicitDistance, 'down');
|
|
1434
1457
|
}
|
|
1435
1458
|
async scrollLeft(distance, startPoint) {
|
|
1436
1459
|
const { width } = await this.size();
|
|
1437
1460
|
const scrollDistance = Math.round(distance || width);
|
|
1461
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1438
1462
|
if (startPoint) {
|
|
1439
1463
|
const start = {
|
|
1440
1464
|
x: Math.round(startPoint.left),
|
|
1441
1465
|
y: Math.round(startPoint.top)
|
|
1442
1466
|
};
|
|
1443
1467
|
const end = this.calculateScrollEndPoint(start, scrollDistance, 0, width, 0);
|
|
1468
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('left', scrollDistance, Math.abs(end.x - start.x));
|
|
1444
1469
|
await this.mouseDrag(start, end);
|
|
1445
1470
|
return;
|
|
1446
1471
|
}
|
|
1447
|
-
await this.scroll(-scrollDistance, 0);
|
|
1472
|
+
await this.scroll(-scrollDistance, 0, void 0, hasExplicitDistance, 'left');
|
|
1448
1473
|
}
|
|
1449
1474
|
async scrollRight(distance, startPoint) {
|
|
1450
1475
|
const { width } = await this.size();
|
|
1451
1476
|
const scrollDistance = Math.round(distance || width);
|
|
1477
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1452
1478
|
if (startPoint) {
|
|
1453
1479
|
const start = {
|
|
1454
1480
|
x: Math.round(startPoint.left),
|
|
1455
1481
|
y: Math.round(startPoint.top)
|
|
1456
1482
|
};
|
|
1457
1483
|
const end = this.calculateScrollEndPoint(start, -scrollDistance, 0, width, 0);
|
|
1484
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('right', scrollDistance, Math.abs(end.x - start.x));
|
|
1458
1485
|
await this.mouseDrag(start, end);
|
|
1459
1486
|
return;
|
|
1460
1487
|
}
|
|
1461
|
-
await this.scroll(scrollDistance, 0);
|
|
1488
|
+
await this.scroll(scrollDistance, 0, void 0, hasExplicitDistance, 'right');
|
|
1462
1489
|
}
|
|
1463
1490
|
async ensureYadb() {
|
|
1464
1491
|
if (!this.yadbPushed) {
|
|
@@ -1558,7 +1585,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1558
1585
|
const swipeDuration = duration ?? defaultNormalScrollDuration;
|
|
1559
1586
|
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
|
|
1560
1587
|
}
|
|
1561
|
-
async scroll(deltaX, deltaY, duration) {
|
|
1588
|
+
async scroll(deltaX, deltaY, duration, warnOnClamp = false, direction) {
|
|
1562
1589
|
if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
|
|
1563
1590
|
const { width, height } = await this.size();
|
|
1564
1591
|
const n = 4;
|
|
@@ -1568,8 +1595,15 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1568
1595
|
const maxNegativeDeltaX = width - startX;
|
|
1569
1596
|
const maxPositiveDeltaY = startY;
|
|
1570
1597
|
const maxNegativeDeltaY = height - startY;
|
|
1598
|
+
const originalDeltaX = deltaX;
|
|
1599
|
+
const originalDeltaY = deltaY;
|
|
1571
1600
|
deltaX = Math.max(-maxNegativeDeltaX, Math.min(deltaX, maxPositiveDeltaX));
|
|
1572
1601
|
deltaY = Math.max(-maxNegativeDeltaY, Math.min(deltaY, maxPositiveDeltaY));
|
|
1602
|
+
if (warnOnClamp && direction && (deltaX !== originalDeltaX || deltaY !== originalDeltaY)) {
|
|
1603
|
+
const requestedDistance = 'left' === direction || 'right' === direction ? Math.abs(originalDeltaX) : Math.abs(originalDeltaY);
|
|
1604
|
+
const appliedDistance = 'left' === direction || 'right' === direction ? Math.abs(deltaX) : Math.abs(deltaY);
|
|
1605
|
+
this.warnScrollDistanceClamped(direction, requestedDistance, appliedDistance);
|
|
1606
|
+
}
|
|
1573
1607
|
const endX = Math.round(startX - deltaX);
|
|
1574
1608
|
const endY = Math.round(startY - deltaY);
|
|
1575
1609
|
const { x: adjustedStartX, y: adjustedStartY } = await this.adjustCoordinates(startX, startY);
|
|
@@ -1726,7 +1760,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1726
1760
|
}
|
|
1727
1761
|
debugDevice(`Keyboard still shown after keycode ${keyCode}, trying next key`);
|
|
1728
1762
|
}
|
|
1729
|
-
|
|
1763
|
+
warnDevice('Warning: Failed to hide the software keyboard after trying both ESC and BACK keys');
|
|
1730
1764
|
return false;
|
|
1731
1765
|
}
|
|
1732
1766
|
constructor(deviceId, options){
|
|
@@ -1952,7 +1986,7 @@ class AndroidMidsceneTools extends BaseMidsceneTools {
|
|
|
1952
1986
|
const tools = new AndroidMidsceneTools();
|
|
1953
1987
|
runToolsCLI(tools, 'midscene-android', {
|
|
1954
1988
|
stripPrefix: 'android_',
|
|
1955
|
-
version: "1.7.
|
|
1989
|
+
version: "1.7.5-beta-20260418223706.0",
|
|
1956
1990
|
extraCommands: createReportCliCommands()
|
|
1957
1991
|
}).catch((e)=>{
|
|
1958
1992
|
if (!(e instanceof CLIError)) console.error(e);
|
package/dist/es/index.mjs
CHANGED
|
@@ -579,6 +579,9 @@ const defaultNormalScrollDuration = 1000;
|
|
|
579
579
|
const IME_STRATEGY_ALWAYS_YADB = 'always-yadb';
|
|
580
580
|
const IME_STRATEGY_YADB_FOR_NON_ASCII = 'yadb-for-non-ascii';
|
|
581
581
|
const debugDevice = (0, logger_.getDebug)('android:device');
|
|
582
|
+
const warnDevice = (0, logger_.getDebug)('android:device', {
|
|
583
|
+
console: true
|
|
584
|
+
});
|
|
582
585
|
function escapeForShell(text) {
|
|
583
586
|
return text.replace(/'/g, "'\\''").replace(/\n/g, '\\n');
|
|
584
587
|
}
|
|
@@ -758,7 +761,7 @@ class AndroidDevice {
|
|
|
758
761
|
console.log(`[midscene] Using scrcpy for screenshots (device: ${this.deviceId})`);
|
|
759
762
|
} catch (error) {
|
|
760
763
|
const msg = error instanceof Error ? error.message : String(error);
|
|
761
|
-
|
|
764
|
+
warnDevice(`[midscene] Scrcpy unavailable, using ADB fallback (device: ${this.deviceId}): ${msg}`);
|
|
762
765
|
}
|
|
763
766
|
return adb;
|
|
764
767
|
}
|
|
@@ -1126,6 +1129,22 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1126
1129
|
y: endY
|
|
1127
1130
|
};
|
|
1128
1131
|
}
|
|
1132
|
+
warnScrollDistanceClamped(direction, requestedDistance, appliedDistance) {
|
|
1133
|
+
if (requestedDistance <= appliedDistance) return;
|
|
1134
|
+
const scrollToSuggestion = {
|
|
1135
|
+
down: 'scrollToBottom',
|
|
1136
|
+
up: 'scrollToTop',
|
|
1137
|
+
left: 'scrollToLeft',
|
|
1138
|
+
right: 'scrollToRight'
|
|
1139
|
+
};
|
|
1140
|
+
const edgeLabel = {
|
|
1141
|
+
down: 'bottom',
|
|
1142
|
+
up: 'top',
|
|
1143
|
+
left: 'left edge',
|
|
1144
|
+
right: 'right edge'
|
|
1145
|
+
};
|
|
1146
|
+
warnDevice(`[midscene] Android ADB swipe coordinates must stay within the screen bounds. The requested scroll distance (${requestedDistance}px) exceeds the maximum single swipe distance (${appliedDistance}px) from the current start point, so it will be clamped. If you want to scroll to the ${edgeLabel[direction]}, use ${scrollToSuggestion[direction]} instead.`);
|
|
1147
|
+
}
|
|
1129
1148
|
async screenshotBase64() {
|
|
1130
1149
|
debugDevice('screenshotBase64 begin');
|
|
1131
1150
|
const adapter = this.getScrcpyAdapter();
|
|
@@ -1310,58 +1329,66 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1310
1329
|
async scrollUp(distance, startPoint) {
|
|
1311
1330
|
const { height } = await this.size();
|
|
1312
1331
|
const scrollDistance = Math.round(distance || height);
|
|
1332
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1313
1333
|
if (startPoint) {
|
|
1314
1334
|
const start = {
|
|
1315
1335
|
x: Math.round(startPoint.left),
|
|
1316
1336
|
y: Math.round(startPoint.top)
|
|
1317
1337
|
};
|
|
1318
1338
|
const end = this.calculateScrollEndPoint(start, 0, scrollDistance, 0, height);
|
|
1339
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('up', scrollDistance, Math.abs(end.y - start.y));
|
|
1319
1340
|
await this.mouseDrag(start, end);
|
|
1320
1341
|
return;
|
|
1321
1342
|
}
|
|
1322
|
-
await this.scroll(0, -scrollDistance);
|
|
1343
|
+
await this.scroll(0, -scrollDistance, void 0, hasExplicitDistance, 'up');
|
|
1323
1344
|
}
|
|
1324
1345
|
async scrollDown(distance, startPoint) {
|
|
1325
1346
|
const { height } = await this.size();
|
|
1326
1347
|
const scrollDistance = Math.round(distance || height);
|
|
1348
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1327
1349
|
if (startPoint) {
|
|
1328
1350
|
const start = {
|
|
1329
1351
|
x: Math.round(startPoint.left),
|
|
1330
1352
|
y: Math.round(startPoint.top)
|
|
1331
1353
|
};
|
|
1332
1354
|
const end = this.calculateScrollEndPoint(start, 0, -scrollDistance, 0, height);
|
|
1355
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('down', scrollDistance, Math.abs(end.y - start.y));
|
|
1333
1356
|
await this.mouseDrag(start, end);
|
|
1334
1357
|
return;
|
|
1335
1358
|
}
|
|
1336
|
-
await this.scroll(0, scrollDistance);
|
|
1359
|
+
await this.scroll(0, scrollDistance, void 0, hasExplicitDistance, 'down');
|
|
1337
1360
|
}
|
|
1338
1361
|
async scrollLeft(distance, startPoint) {
|
|
1339
1362
|
const { width } = await this.size();
|
|
1340
1363
|
const scrollDistance = Math.round(distance || width);
|
|
1364
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1341
1365
|
if (startPoint) {
|
|
1342
1366
|
const start = {
|
|
1343
1367
|
x: Math.round(startPoint.left),
|
|
1344
1368
|
y: Math.round(startPoint.top)
|
|
1345
1369
|
};
|
|
1346
1370
|
const end = this.calculateScrollEndPoint(start, scrollDistance, 0, width, 0);
|
|
1371
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('left', scrollDistance, Math.abs(end.x - start.x));
|
|
1347
1372
|
await this.mouseDrag(start, end);
|
|
1348
1373
|
return;
|
|
1349
1374
|
}
|
|
1350
|
-
await this.scroll(-scrollDistance, 0);
|
|
1375
|
+
await this.scroll(-scrollDistance, 0, void 0, hasExplicitDistance, 'left');
|
|
1351
1376
|
}
|
|
1352
1377
|
async scrollRight(distance, startPoint) {
|
|
1353
1378
|
const { width } = await this.size();
|
|
1354
1379
|
const scrollDistance = Math.round(distance || width);
|
|
1380
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1355
1381
|
if (startPoint) {
|
|
1356
1382
|
const start = {
|
|
1357
1383
|
x: Math.round(startPoint.left),
|
|
1358
1384
|
y: Math.round(startPoint.top)
|
|
1359
1385
|
};
|
|
1360
1386
|
const end = this.calculateScrollEndPoint(start, -scrollDistance, 0, width, 0);
|
|
1387
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('right', scrollDistance, Math.abs(end.x - start.x));
|
|
1361
1388
|
await this.mouseDrag(start, end);
|
|
1362
1389
|
return;
|
|
1363
1390
|
}
|
|
1364
|
-
await this.scroll(scrollDistance, 0);
|
|
1391
|
+
await this.scroll(scrollDistance, 0, void 0, hasExplicitDistance, 'right');
|
|
1365
1392
|
}
|
|
1366
1393
|
async ensureYadb() {
|
|
1367
1394
|
if (!this.yadbPushed) {
|
|
@@ -1461,7 +1488,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1461
1488
|
const swipeDuration = duration ?? defaultNormalScrollDuration;
|
|
1462
1489
|
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
|
|
1463
1490
|
}
|
|
1464
|
-
async scroll(deltaX, deltaY, duration) {
|
|
1491
|
+
async scroll(deltaX, deltaY, duration, warnOnClamp = false, direction) {
|
|
1465
1492
|
if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
|
|
1466
1493
|
const { width, height } = await this.size();
|
|
1467
1494
|
const n = 4;
|
|
@@ -1471,8 +1498,15 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1471
1498
|
const maxNegativeDeltaX = width - startX;
|
|
1472
1499
|
const maxPositiveDeltaY = startY;
|
|
1473
1500
|
const maxNegativeDeltaY = height - startY;
|
|
1501
|
+
const originalDeltaX = deltaX;
|
|
1502
|
+
const originalDeltaY = deltaY;
|
|
1474
1503
|
deltaX = Math.max(-maxNegativeDeltaX, Math.min(deltaX, maxPositiveDeltaX));
|
|
1475
1504
|
deltaY = Math.max(-maxNegativeDeltaY, Math.min(deltaY, maxPositiveDeltaY));
|
|
1505
|
+
if (warnOnClamp && direction && (deltaX !== originalDeltaX || deltaY !== originalDeltaY)) {
|
|
1506
|
+
const requestedDistance = 'left' === direction || 'right' === direction ? Math.abs(originalDeltaX) : Math.abs(originalDeltaY);
|
|
1507
|
+
const appliedDistance = 'left' === direction || 'right' === direction ? Math.abs(deltaX) : Math.abs(deltaY);
|
|
1508
|
+
this.warnScrollDistanceClamped(direction, requestedDistance, appliedDistance);
|
|
1509
|
+
}
|
|
1476
1510
|
const endX = Math.round(startX - deltaX);
|
|
1477
1511
|
const endY = Math.round(startY - deltaY);
|
|
1478
1512
|
const { x: adjustedStartX, y: adjustedStartY } = await this.adjustCoordinates(startX, startY);
|
|
@@ -1629,7 +1663,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1629
1663
|
}
|
|
1630
1664
|
debugDevice(`Keyboard still shown after keycode ${keyCode}, trying next key`);
|
|
1631
1665
|
}
|
|
1632
|
-
|
|
1666
|
+
warnDevice('Warning: Failed to hide the software keyboard after trying both ESC and BACK keys');
|
|
1633
1667
|
return false;
|
|
1634
1668
|
}
|
|
1635
1669
|
constructor(deviceId, options){
|
package/dist/es/mcp-server.mjs
CHANGED
|
@@ -675,6 +675,9 @@ const defaultNormalScrollDuration = 1000;
|
|
|
675
675
|
const IME_STRATEGY_ALWAYS_YADB = 'always-yadb';
|
|
676
676
|
const IME_STRATEGY_YADB_FOR_NON_ASCII = 'yadb-for-non-ascii';
|
|
677
677
|
const debugDevice = (0, logger_.getDebug)('android:device');
|
|
678
|
+
const warnDevice = (0, logger_.getDebug)('android:device', {
|
|
679
|
+
console: true
|
|
680
|
+
});
|
|
678
681
|
function escapeForShell(text) {
|
|
679
682
|
return text.replace(/'/g, "'\\''").replace(/\n/g, '\\n');
|
|
680
683
|
}
|
|
@@ -854,7 +857,7 @@ class AndroidDevice {
|
|
|
854
857
|
console.log(`[midscene] Using scrcpy for screenshots (device: ${this.deviceId})`);
|
|
855
858
|
} catch (error) {
|
|
856
859
|
const msg = error instanceof Error ? error.message : String(error);
|
|
857
|
-
|
|
860
|
+
warnDevice(`[midscene] Scrcpy unavailable, using ADB fallback (device: ${this.deviceId}): ${msg}`);
|
|
858
861
|
}
|
|
859
862
|
return adb;
|
|
860
863
|
}
|
|
@@ -1222,6 +1225,22 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1222
1225
|
y: endY
|
|
1223
1226
|
};
|
|
1224
1227
|
}
|
|
1228
|
+
warnScrollDistanceClamped(direction, requestedDistance, appliedDistance) {
|
|
1229
|
+
if (requestedDistance <= appliedDistance) return;
|
|
1230
|
+
const scrollToSuggestion = {
|
|
1231
|
+
down: 'scrollToBottom',
|
|
1232
|
+
up: 'scrollToTop',
|
|
1233
|
+
left: 'scrollToLeft',
|
|
1234
|
+
right: 'scrollToRight'
|
|
1235
|
+
};
|
|
1236
|
+
const edgeLabel = {
|
|
1237
|
+
down: 'bottom',
|
|
1238
|
+
up: 'top',
|
|
1239
|
+
left: 'left edge',
|
|
1240
|
+
right: 'right edge'
|
|
1241
|
+
};
|
|
1242
|
+
warnDevice(`[midscene] Android ADB swipe coordinates must stay within the screen bounds. The requested scroll distance (${requestedDistance}px) exceeds the maximum single swipe distance (${appliedDistance}px) from the current start point, so it will be clamped. If you want to scroll to the ${edgeLabel[direction]}, use ${scrollToSuggestion[direction]} instead.`);
|
|
1243
|
+
}
|
|
1225
1244
|
async screenshotBase64() {
|
|
1226
1245
|
debugDevice('screenshotBase64 begin');
|
|
1227
1246
|
const adapter = this.getScrcpyAdapter();
|
|
@@ -1406,58 +1425,66 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1406
1425
|
async scrollUp(distance, startPoint) {
|
|
1407
1426
|
const { height } = await this.size();
|
|
1408
1427
|
const scrollDistance = Math.round(distance || height);
|
|
1428
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1409
1429
|
if (startPoint) {
|
|
1410
1430
|
const start = {
|
|
1411
1431
|
x: Math.round(startPoint.left),
|
|
1412
1432
|
y: Math.round(startPoint.top)
|
|
1413
1433
|
};
|
|
1414
1434
|
const end = this.calculateScrollEndPoint(start, 0, scrollDistance, 0, height);
|
|
1435
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('up', scrollDistance, Math.abs(end.y - start.y));
|
|
1415
1436
|
await this.mouseDrag(start, end);
|
|
1416
1437
|
return;
|
|
1417
1438
|
}
|
|
1418
|
-
await this.scroll(0, -scrollDistance);
|
|
1439
|
+
await this.scroll(0, -scrollDistance, void 0, hasExplicitDistance, 'up');
|
|
1419
1440
|
}
|
|
1420
1441
|
async scrollDown(distance, startPoint) {
|
|
1421
1442
|
const { height } = await this.size();
|
|
1422
1443
|
const scrollDistance = Math.round(distance || height);
|
|
1444
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1423
1445
|
if (startPoint) {
|
|
1424
1446
|
const start = {
|
|
1425
1447
|
x: Math.round(startPoint.left),
|
|
1426
1448
|
y: Math.round(startPoint.top)
|
|
1427
1449
|
};
|
|
1428
1450
|
const end = this.calculateScrollEndPoint(start, 0, -scrollDistance, 0, height);
|
|
1451
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('down', scrollDistance, Math.abs(end.y - start.y));
|
|
1429
1452
|
await this.mouseDrag(start, end);
|
|
1430
1453
|
return;
|
|
1431
1454
|
}
|
|
1432
|
-
await this.scroll(0, scrollDistance);
|
|
1455
|
+
await this.scroll(0, scrollDistance, void 0, hasExplicitDistance, 'down');
|
|
1433
1456
|
}
|
|
1434
1457
|
async scrollLeft(distance, startPoint) {
|
|
1435
1458
|
const { width } = await this.size();
|
|
1436
1459
|
const scrollDistance = Math.round(distance || width);
|
|
1460
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1437
1461
|
if (startPoint) {
|
|
1438
1462
|
const start = {
|
|
1439
1463
|
x: Math.round(startPoint.left),
|
|
1440
1464
|
y: Math.round(startPoint.top)
|
|
1441
1465
|
};
|
|
1442
1466
|
const end = this.calculateScrollEndPoint(start, scrollDistance, 0, width, 0);
|
|
1467
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('left', scrollDistance, Math.abs(end.x - start.x));
|
|
1443
1468
|
await this.mouseDrag(start, end);
|
|
1444
1469
|
return;
|
|
1445
1470
|
}
|
|
1446
|
-
await this.scroll(-scrollDistance, 0);
|
|
1471
|
+
await this.scroll(-scrollDistance, 0, void 0, hasExplicitDistance, 'left');
|
|
1447
1472
|
}
|
|
1448
1473
|
async scrollRight(distance, startPoint) {
|
|
1449
1474
|
const { width } = await this.size();
|
|
1450
1475
|
const scrollDistance = Math.round(distance || width);
|
|
1476
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1451
1477
|
if (startPoint) {
|
|
1452
1478
|
const start = {
|
|
1453
1479
|
x: Math.round(startPoint.left),
|
|
1454
1480
|
y: Math.round(startPoint.top)
|
|
1455
1481
|
};
|
|
1456
1482
|
const end = this.calculateScrollEndPoint(start, -scrollDistance, 0, width, 0);
|
|
1483
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('right', scrollDistance, Math.abs(end.x - start.x));
|
|
1457
1484
|
await this.mouseDrag(start, end);
|
|
1458
1485
|
return;
|
|
1459
1486
|
}
|
|
1460
|
-
await this.scroll(scrollDistance, 0);
|
|
1487
|
+
await this.scroll(scrollDistance, 0, void 0, hasExplicitDistance, 'right');
|
|
1461
1488
|
}
|
|
1462
1489
|
async ensureYadb() {
|
|
1463
1490
|
if (!this.yadbPushed) {
|
|
@@ -1557,7 +1584,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1557
1584
|
const swipeDuration = duration ?? defaultNormalScrollDuration;
|
|
1558
1585
|
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
|
|
1559
1586
|
}
|
|
1560
|
-
async scroll(deltaX, deltaY, duration) {
|
|
1587
|
+
async scroll(deltaX, deltaY, duration, warnOnClamp = false, direction) {
|
|
1561
1588
|
if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
|
|
1562
1589
|
const { width, height } = await this.size();
|
|
1563
1590
|
const n = 4;
|
|
@@ -1567,8 +1594,15 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1567
1594
|
const maxNegativeDeltaX = width - startX;
|
|
1568
1595
|
const maxPositiveDeltaY = startY;
|
|
1569
1596
|
const maxNegativeDeltaY = height - startY;
|
|
1597
|
+
const originalDeltaX = deltaX;
|
|
1598
|
+
const originalDeltaY = deltaY;
|
|
1570
1599
|
deltaX = Math.max(-maxNegativeDeltaX, Math.min(deltaX, maxPositiveDeltaX));
|
|
1571
1600
|
deltaY = Math.max(-maxNegativeDeltaY, Math.min(deltaY, maxPositiveDeltaY));
|
|
1601
|
+
if (warnOnClamp && direction && (deltaX !== originalDeltaX || deltaY !== originalDeltaY)) {
|
|
1602
|
+
const requestedDistance = 'left' === direction || 'right' === direction ? Math.abs(originalDeltaX) : Math.abs(originalDeltaY);
|
|
1603
|
+
const appliedDistance = 'left' === direction || 'right' === direction ? Math.abs(deltaX) : Math.abs(deltaY);
|
|
1604
|
+
this.warnScrollDistanceClamped(direction, requestedDistance, appliedDistance);
|
|
1605
|
+
}
|
|
1572
1606
|
const endX = Math.round(startX - deltaX);
|
|
1573
1607
|
const endY = Math.round(startY - deltaY);
|
|
1574
1608
|
const { x: adjustedStartX, y: adjustedStartY } = await this.adjustCoordinates(startX, startY);
|
|
@@ -1725,7 +1759,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1725
1759
|
}
|
|
1726
1760
|
debugDevice(`Keyboard still shown after keycode ${keyCode}, trying next key`);
|
|
1727
1761
|
}
|
|
1728
|
-
|
|
1762
|
+
warnDevice('Warning: Failed to hide the software keyboard after trying both ESC and BACK keys');
|
|
1729
1763
|
return false;
|
|
1730
1764
|
}
|
|
1731
1765
|
constructor(deviceId, options){
|
|
@@ -1955,7 +1989,7 @@ class AndroidMCPServer extends BaseMCPServer {
|
|
|
1955
1989
|
constructor(toolsManager){
|
|
1956
1990
|
super({
|
|
1957
1991
|
name: '@midscene/android-mcp',
|
|
1958
|
-
version: "1.7.
|
|
1992
|
+
version: "1.7.5-beta-20260418223706.0",
|
|
1959
1993
|
description: 'Control the Android device using natural language commands'
|
|
1960
1994
|
}, toolsManager);
|
|
1961
1995
|
}
|
package/dist/lib/cli.js
CHANGED
|
@@ -691,6 +691,9 @@ var __webpack_exports__ = {};
|
|
|
691
691
|
const IME_STRATEGY_ALWAYS_YADB = 'always-yadb';
|
|
692
692
|
const IME_STRATEGY_YADB_FOR_NON_ASCII = 'yadb-for-non-ascii';
|
|
693
693
|
const debugDevice = (0, logger_.getDebug)('android:device');
|
|
694
|
+
const warnDevice = (0, logger_.getDebug)('android:device', {
|
|
695
|
+
console: true
|
|
696
|
+
});
|
|
694
697
|
function escapeForShell(text) {
|
|
695
698
|
return text.replace(/'/g, "'\\''").replace(/\n/g, '\\n');
|
|
696
699
|
}
|
|
@@ -870,7 +873,7 @@ var __webpack_exports__ = {};
|
|
|
870
873
|
console.log(`[midscene] Using scrcpy for screenshots (device: ${this.deviceId})`);
|
|
871
874
|
} catch (error) {
|
|
872
875
|
const msg = error instanceof Error ? error.message : String(error);
|
|
873
|
-
|
|
876
|
+
warnDevice(`[midscene] Scrcpy unavailable, using ADB fallback (device: ${this.deviceId}): ${msg}`);
|
|
874
877
|
}
|
|
875
878
|
return adb;
|
|
876
879
|
}
|
|
@@ -1238,6 +1241,22 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1238
1241
|
y: endY
|
|
1239
1242
|
};
|
|
1240
1243
|
}
|
|
1244
|
+
warnScrollDistanceClamped(direction, requestedDistance, appliedDistance) {
|
|
1245
|
+
if (requestedDistance <= appliedDistance) return;
|
|
1246
|
+
const scrollToSuggestion = {
|
|
1247
|
+
down: 'scrollToBottom',
|
|
1248
|
+
up: 'scrollToTop',
|
|
1249
|
+
left: 'scrollToLeft',
|
|
1250
|
+
right: 'scrollToRight'
|
|
1251
|
+
};
|
|
1252
|
+
const edgeLabel = {
|
|
1253
|
+
down: 'bottom',
|
|
1254
|
+
up: 'top',
|
|
1255
|
+
left: 'left edge',
|
|
1256
|
+
right: 'right edge'
|
|
1257
|
+
};
|
|
1258
|
+
warnDevice(`[midscene] Android ADB swipe coordinates must stay within the screen bounds. The requested scroll distance (${requestedDistance}px) exceeds the maximum single swipe distance (${appliedDistance}px) from the current start point, so it will be clamped. If you want to scroll to the ${edgeLabel[direction]}, use ${scrollToSuggestion[direction]} instead.`);
|
|
1259
|
+
}
|
|
1241
1260
|
async screenshotBase64() {
|
|
1242
1261
|
debugDevice('screenshotBase64 begin');
|
|
1243
1262
|
const adapter = this.getScrcpyAdapter();
|
|
@@ -1422,58 +1441,66 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1422
1441
|
async scrollUp(distance, startPoint) {
|
|
1423
1442
|
const { height } = await this.size();
|
|
1424
1443
|
const scrollDistance = Math.round(distance || height);
|
|
1444
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1425
1445
|
if (startPoint) {
|
|
1426
1446
|
const start = {
|
|
1427
1447
|
x: Math.round(startPoint.left),
|
|
1428
1448
|
y: Math.round(startPoint.top)
|
|
1429
1449
|
};
|
|
1430
1450
|
const end = this.calculateScrollEndPoint(start, 0, scrollDistance, 0, height);
|
|
1451
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('up', scrollDistance, Math.abs(end.y - start.y));
|
|
1431
1452
|
await this.mouseDrag(start, end);
|
|
1432
1453
|
return;
|
|
1433
1454
|
}
|
|
1434
|
-
await this.scroll(0, -scrollDistance);
|
|
1455
|
+
await this.scroll(0, -scrollDistance, void 0, hasExplicitDistance, 'up');
|
|
1435
1456
|
}
|
|
1436
1457
|
async scrollDown(distance, startPoint) {
|
|
1437
1458
|
const { height } = await this.size();
|
|
1438
1459
|
const scrollDistance = Math.round(distance || height);
|
|
1460
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1439
1461
|
if (startPoint) {
|
|
1440
1462
|
const start = {
|
|
1441
1463
|
x: Math.round(startPoint.left),
|
|
1442
1464
|
y: Math.round(startPoint.top)
|
|
1443
1465
|
};
|
|
1444
1466
|
const end = this.calculateScrollEndPoint(start, 0, -scrollDistance, 0, height);
|
|
1467
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('down', scrollDistance, Math.abs(end.y - start.y));
|
|
1445
1468
|
await this.mouseDrag(start, end);
|
|
1446
1469
|
return;
|
|
1447
1470
|
}
|
|
1448
|
-
await this.scroll(0, scrollDistance);
|
|
1471
|
+
await this.scroll(0, scrollDistance, void 0, hasExplicitDistance, 'down');
|
|
1449
1472
|
}
|
|
1450
1473
|
async scrollLeft(distance, startPoint) {
|
|
1451
1474
|
const { width } = await this.size();
|
|
1452
1475
|
const scrollDistance = Math.round(distance || width);
|
|
1476
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1453
1477
|
if (startPoint) {
|
|
1454
1478
|
const start = {
|
|
1455
1479
|
x: Math.round(startPoint.left),
|
|
1456
1480
|
y: Math.round(startPoint.top)
|
|
1457
1481
|
};
|
|
1458
1482
|
const end = this.calculateScrollEndPoint(start, scrollDistance, 0, width, 0);
|
|
1483
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('left', scrollDistance, Math.abs(end.x - start.x));
|
|
1459
1484
|
await this.mouseDrag(start, end);
|
|
1460
1485
|
return;
|
|
1461
1486
|
}
|
|
1462
|
-
await this.scroll(-scrollDistance, 0);
|
|
1487
|
+
await this.scroll(-scrollDistance, 0, void 0, hasExplicitDistance, 'left');
|
|
1463
1488
|
}
|
|
1464
1489
|
async scrollRight(distance, startPoint) {
|
|
1465
1490
|
const { width } = await this.size();
|
|
1466
1491
|
const scrollDistance = Math.round(distance || width);
|
|
1492
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1467
1493
|
if (startPoint) {
|
|
1468
1494
|
const start = {
|
|
1469
1495
|
x: Math.round(startPoint.left),
|
|
1470
1496
|
y: Math.round(startPoint.top)
|
|
1471
1497
|
};
|
|
1472
1498
|
const end = this.calculateScrollEndPoint(start, -scrollDistance, 0, width, 0);
|
|
1499
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('right', scrollDistance, Math.abs(end.x - start.x));
|
|
1473
1500
|
await this.mouseDrag(start, end);
|
|
1474
1501
|
return;
|
|
1475
1502
|
}
|
|
1476
|
-
await this.scroll(scrollDistance, 0);
|
|
1503
|
+
await this.scroll(scrollDistance, 0, void 0, hasExplicitDistance, 'right');
|
|
1477
1504
|
}
|
|
1478
1505
|
async ensureYadb() {
|
|
1479
1506
|
if (!this.yadbPushed) {
|
|
@@ -1573,7 +1600,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1573
1600
|
const swipeDuration = duration ?? defaultNormalScrollDuration;
|
|
1574
1601
|
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
|
|
1575
1602
|
}
|
|
1576
|
-
async scroll(deltaX, deltaY, duration) {
|
|
1603
|
+
async scroll(deltaX, deltaY, duration, warnOnClamp = false, direction) {
|
|
1577
1604
|
if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
|
|
1578
1605
|
const { width, height } = await this.size();
|
|
1579
1606
|
const n = 4;
|
|
@@ -1583,8 +1610,15 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1583
1610
|
const maxNegativeDeltaX = width - startX;
|
|
1584
1611
|
const maxPositiveDeltaY = startY;
|
|
1585
1612
|
const maxNegativeDeltaY = height - startY;
|
|
1613
|
+
const originalDeltaX = deltaX;
|
|
1614
|
+
const originalDeltaY = deltaY;
|
|
1586
1615
|
deltaX = Math.max(-maxNegativeDeltaX, Math.min(deltaX, maxPositiveDeltaX));
|
|
1587
1616
|
deltaY = Math.max(-maxNegativeDeltaY, Math.min(deltaY, maxPositiveDeltaY));
|
|
1617
|
+
if (warnOnClamp && direction && (deltaX !== originalDeltaX || deltaY !== originalDeltaY)) {
|
|
1618
|
+
const requestedDistance = 'left' === direction || 'right' === direction ? Math.abs(originalDeltaX) : Math.abs(originalDeltaY);
|
|
1619
|
+
const appliedDistance = 'left' === direction || 'right' === direction ? Math.abs(deltaX) : Math.abs(deltaY);
|
|
1620
|
+
this.warnScrollDistanceClamped(direction, requestedDistance, appliedDistance);
|
|
1621
|
+
}
|
|
1588
1622
|
const endX = Math.round(startX - deltaX);
|
|
1589
1623
|
const endY = Math.round(startY - deltaY);
|
|
1590
1624
|
const { x: adjustedStartX, y: adjustedStartY } = await this.adjustCoordinates(startX, startY);
|
|
@@ -1741,7 +1775,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1741
1775
|
}
|
|
1742
1776
|
debugDevice(`Keyboard still shown after keycode ${keyCode}, trying next key`);
|
|
1743
1777
|
}
|
|
1744
|
-
|
|
1778
|
+
warnDevice('Warning: Failed to hide the software keyboard after trying both ESC and BACK keys');
|
|
1745
1779
|
return false;
|
|
1746
1780
|
}
|
|
1747
1781
|
constructor(deviceId, options){
|
|
@@ -1967,7 +2001,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1967
2001
|
const tools = new AndroidMidsceneTools();
|
|
1968
2002
|
(0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-android', {
|
|
1969
2003
|
stripPrefix: 'android_',
|
|
1970
|
-
version: "1.7.
|
|
2004
|
+
version: "1.7.5-beta-20260418223706.0",
|
|
1971
2005
|
extraCommands: (0, core_namespaceObject.createReportCliCommands)()
|
|
1972
2006
|
}).catch((e)=>{
|
|
1973
2007
|
if (!(e instanceof cli_namespaceObject.CLIError)) console.error(e);
|
package/dist/lib/index.js
CHANGED
|
@@ -613,6 +613,9 @@ var __webpack_exports__ = {};
|
|
|
613
613
|
const IME_STRATEGY_ALWAYS_YADB = 'always-yadb';
|
|
614
614
|
const IME_STRATEGY_YADB_FOR_NON_ASCII = 'yadb-for-non-ascii';
|
|
615
615
|
const debugDevice = (0, logger_.getDebug)('android:device');
|
|
616
|
+
const warnDevice = (0, logger_.getDebug)('android:device', {
|
|
617
|
+
console: true
|
|
618
|
+
});
|
|
616
619
|
function escapeForShell(text) {
|
|
617
620
|
return text.replace(/'/g, "'\\''").replace(/\n/g, '\\n');
|
|
618
621
|
}
|
|
@@ -792,7 +795,7 @@ var __webpack_exports__ = {};
|
|
|
792
795
|
console.log(`[midscene] Using scrcpy for screenshots (device: ${this.deviceId})`);
|
|
793
796
|
} catch (error) {
|
|
794
797
|
const msg = error instanceof Error ? error.message : String(error);
|
|
795
|
-
|
|
798
|
+
warnDevice(`[midscene] Scrcpy unavailable, using ADB fallback (device: ${this.deviceId}): ${msg}`);
|
|
796
799
|
}
|
|
797
800
|
return adb;
|
|
798
801
|
}
|
|
@@ -1160,6 +1163,22 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1160
1163
|
y: endY
|
|
1161
1164
|
};
|
|
1162
1165
|
}
|
|
1166
|
+
warnScrollDistanceClamped(direction, requestedDistance, appliedDistance) {
|
|
1167
|
+
if (requestedDistance <= appliedDistance) return;
|
|
1168
|
+
const scrollToSuggestion = {
|
|
1169
|
+
down: 'scrollToBottom',
|
|
1170
|
+
up: 'scrollToTop',
|
|
1171
|
+
left: 'scrollToLeft',
|
|
1172
|
+
right: 'scrollToRight'
|
|
1173
|
+
};
|
|
1174
|
+
const edgeLabel = {
|
|
1175
|
+
down: 'bottom',
|
|
1176
|
+
up: 'top',
|
|
1177
|
+
left: 'left edge',
|
|
1178
|
+
right: 'right edge'
|
|
1179
|
+
};
|
|
1180
|
+
warnDevice(`[midscene] Android ADB swipe coordinates must stay within the screen bounds. The requested scroll distance (${requestedDistance}px) exceeds the maximum single swipe distance (${appliedDistance}px) from the current start point, so it will be clamped. If you want to scroll to the ${edgeLabel[direction]}, use ${scrollToSuggestion[direction]} instead.`);
|
|
1181
|
+
}
|
|
1163
1182
|
async screenshotBase64() {
|
|
1164
1183
|
debugDevice('screenshotBase64 begin');
|
|
1165
1184
|
const adapter = this.getScrcpyAdapter();
|
|
@@ -1344,58 +1363,66 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1344
1363
|
async scrollUp(distance, startPoint) {
|
|
1345
1364
|
const { height } = await this.size();
|
|
1346
1365
|
const scrollDistance = Math.round(distance || height);
|
|
1366
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1347
1367
|
if (startPoint) {
|
|
1348
1368
|
const start = {
|
|
1349
1369
|
x: Math.round(startPoint.left),
|
|
1350
1370
|
y: Math.round(startPoint.top)
|
|
1351
1371
|
};
|
|
1352
1372
|
const end = this.calculateScrollEndPoint(start, 0, scrollDistance, 0, height);
|
|
1373
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('up', scrollDistance, Math.abs(end.y - start.y));
|
|
1353
1374
|
await this.mouseDrag(start, end);
|
|
1354
1375
|
return;
|
|
1355
1376
|
}
|
|
1356
|
-
await this.scroll(0, -scrollDistance);
|
|
1377
|
+
await this.scroll(0, -scrollDistance, void 0, hasExplicitDistance, 'up');
|
|
1357
1378
|
}
|
|
1358
1379
|
async scrollDown(distance, startPoint) {
|
|
1359
1380
|
const { height } = await this.size();
|
|
1360
1381
|
const scrollDistance = Math.round(distance || height);
|
|
1382
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1361
1383
|
if (startPoint) {
|
|
1362
1384
|
const start = {
|
|
1363
1385
|
x: Math.round(startPoint.left),
|
|
1364
1386
|
y: Math.round(startPoint.top)
|
|
1365
1387
|
};
|
|
1366
1388
|
const end = this.calculateScrollEndPoint(start, 0, -scrollDistance, 0, height);
|
|
1389
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('down', scrollDistance, Math.abs(end.y - start.y));
|
|
1367
1390
|
await this.mouseDrag(start, end);
|
|
1368
1391
|
return;
|
|
1369
1392
|
}
|
|
1370
|
-
await this.scroll(0, scrollDistance);
|
|
1393
|
+
await this.scroll(0, scrollDistance, void 0, hasExplicitDistance, 'down');
|
|
1371
1394
|
}
|
|
1372
1395
|
async scrollLeft(distance, startPoint) {
|
|
1373
1396
|
const { width } = await this.size();
|
|
1374
1397
|
const scrollDistance = Math.round(distance || width);
|
|
1398
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1375
1399
|
if (startPoint) {
|
|
1376
1400
|
const start = {
|
|
1377
1401
|
x: Math.round(startPoint.left),
|
|
1378
1402
|
y: Math.round(startPoint.top)
|
|
1379
1403
|
};
|
|
1380
1404
|
const end = this.calculateScrollEndPoint(start, scrollDistance, 0, width, 0);
|
|
1405
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('left', scrollDistance, Math.abs(end.x - start.x));
|
|
1381
1406
|
await this.mouseDrag(start, end);
|
|
1382
1407
|
return;
|
|
1383
1408
|
}
|
|
1384
|
-
await this.scroll(-scrollDistance, 0);
|
|
1409
|
+
await this.scroll(-scrollDistance, 0, void 0, hasExplicitDistance, 'left');
|
|
1385
1410
|
}
|
|
1386
1411
|
async scrollRight(distance, startPoint) {
|
|
1387
1412
|
const { width } = await this.size();
|
|
1388
1413
|
const scrollDistance = Math.round(distance || width);
|
|
1414
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1389
1415
|
if (startPoint) {
|
|
1390
1416
|
const start = {
|
|
1391
1417
|
x: Math.round(startPoint.left),
|
|
1392
1418
|
y: Math.round(startPoint.top)
|
|
1393
1419
|
};
|
|
1394
1420
|
const end = this.calculateScrollEndPoint(start, -scrollDistance, 0, width, 0);
|
|
1421
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('right', scrollDistance, Math.abs(end.x - start.x));
|
|
1395
1422
|
await this.mouseDrag(start, end);
|
|
1396
1423
|
return;
|
|
1397
1424
|
}
|
|
1398
|
-
await this.scroll(scrollDistance, 0);
|
|
1425
|
+
await this.scroll(scrollDistance, 0, void 0, hasExplicitDistance, 'right');
|
|
1399
1426
|
}
|
|
1400
1427
|
async ensureYadb() {
|
|
1401
1428
|
if (!this.yadbPushed) {
|
|
@@ -1495,7 +1522,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1495
1522
|
const swipeDuration = duration ?? defaultNormalScrollDuration;
|
|
1496
1523
|
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
|
|
1497
1524
|
}
|
|
1498
|
-
async scroll(deltaX, deltaY, duration) {
|
|
1525
|
+
async scroll(deltaX, deltaY, duration, warnOnClamp = false, direction) {
|
|
1499
1526
|
if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
|
|
1500
1527
|
const { width, height } = await this.size();
|
|
1501
1528
|
const n = 4;
|
|
@@ -1505,8 +1532,15 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1505
1532
|
const maxNegativeDeltaX = width - startX;
|
|
1506
1533
|
const maxPositiveDeltaY = startY;
|
|
1507
1534
|
const maxNegativeDeltaY = height - startY;
|
|
1535
|
+
const originalDeltaX = deltaX;
|
|
1536
|
+
const originalDeltaY = deltaY;
|
|
1508
1537
|
deltaX = Math.max(-maxNegativeDeltaX, Math.min(deltaX, maxPositiveDeltaX));
|
|
1509
1538
|
deltaY = Math.max(-maxNegativeDeltaY, Math.min(deltaY, maxPositiveDeltaY));
|
|
1539
|
+
if (warnOnClamp && direction && (deltaX !== originalDeltaX || deltaY !== originalDeltaY)) {
|
|
1540
|
+
const requestedDistance = 'left' === direction || 'right' === direction ? Math.abs(originalDeltaX) : Math.abs(originalDeltaY);
|
|
1541
|
+
const appliedDistance = 'left' === direction || 'right' === direction ? Math.abs(deltaX) : Math.abs(deltaY);
|
|
1542
|
+
this.warnScrollDistanceClamped(direction, requestedDistance, appliedDistance);
|
|
1543
|
+
}
|
|
1510
1544
|
const endX = Math.round(startX - deltaX);
|
|
1511
1545
|
const endY = Math.round(startY - deltaY);
|
|
1512
1546
|
const { x: adjustedStartX, y: adjustedStartY } = await this.adjustCoordinates(startX, startY);
|
|
@@ -1663,7 +1697,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1663
1697
|
}
|
|
1664
1698
|
debugDevice(`Keyboard still shown after keycode ${keyCode}, trying next key`);
|
|
1665
1699
|
}
|
|
1666
|
-
|
|
1700
|
+
warnDevice('Warning: Failed to hide the software keyboard after trying both ESC and BACK keys');
|
|
1667
1701
|
return false;
|
|
1668
1702
|
}
|
|
1669
1703
|
constructor(deviceId, options){
|
package/dist/lib/mcp-server.js
CHANGED
|
@@ -706,6 +706,9 @@ var __webpack_exports__ = {};
|
|
|
706
706
|
const IME_STRATEGY_ALWAYS_YADB = 'always-yadb';
|
|
707
707
|
const IME_STRATEGY_YADB_FOR_NON_ASCII = 'yadb-for-non-ascii';
|
|
708
708
|
const debugDevice = (0, logger_.getDebug)('android:device');
|
|
709
|
+
const warnDevice = (0, logger_.getDebug)('android:device', {
|
|
710
|
+
console: true
|
|
711
|
+
});
|
|
709
712
|
function escapeForShell(text) {
|
|
710
713
|
return text.replace(/'/g, "'\\''").replace(/\n/g, '\\n');
|
|
711
714
|
}
|
|
@@ -885,7 +888,7 @@ var __webpack_exports__ = {};
|
|
|
885
888
|
console.log(`[midscene] Using scrcpy for screenshots (device: ${this.deviceId})`);
|
|
886
889
|
} catch (error) {
|
|
887
890
|
const msg = error instanceof Error ? error.message : String(error);
|
|
888
|
-
|
|
891
|
+
warnDevice(`[midscene] Scrcpy unavailable, using ADB fallback (device: ${this.deviceId}): ${msg}`);
|
|
889
892
|
}
|
|
890
893
|
return adb;
|
|
891
894
|
}
|
|
@@ -1253,6 +1256,22 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1253
1256
|
y: endY
|
|
1254
1257
|
};
|
|
1255
1258
|
}
|
|
1259
|
+
warnScrollDistanceClamped(direction, requestedDistance, appliedDistance) {
|
|
1260
|
+
if (requestedDistance <= appliedDistance) return;
|
|
1261
|
+
const scrollToSuggestion = {
|
|
1262
|
+
down: 'scrollToBottom',
|
|
1263
|
+
up: 'scrollToTop',
|
|
1264
|
+
left: 'scrollToLeft',
|
|
1265
|
+
right: 'scrollToRight'
|
|
1266
|
+
};
|
|
1267
|
+
const edgeLabel = {
|
|
1268
|
+
down: 'bottom',
|
|
1269
|
+
up: 'top',
|
|
1270
|
+
left: 'left edge',
|
|
1271
|
+
right: 'right edge'
|
|
1272
|
+
};
|
|
1273
|
+
warnDevice(`[midscene] Android ADB swipe coordinates must stay within the screen bounds. The requested scroll distance (${requestedDistance}px) exceeds the maximum single swipe distance (${appliedDistance}px) from the current start point, so it will be clamped. If you want to scroll to the ${edgeLabel[direction]}, use ${scrollToSuggestion[direction]} instead.`);
|
|
1274
|
+
}
|
|
1256
1275
|
async screenshotBase64() {
|
|
1257
1276
|
debugDevice('screenshotBase64 begin');
|
|
1258
1277
|
const adapter = this.getScrcpyAdapter();
|
|
@@ -1437,58 +1456,66 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1437
1456
|
async scrollUp(distance, startPoint) {
|
|
1438
1457
|
const { height } = await this.size();
|
|
1439
1458
|
const scrollDistance = Math.round(distance || height);
|
|
1459
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1440
1460
|
if (startPoint) {
|
|
1441
1461
|
const start = {
|
|
1442
1462
|
x: Math.round(startPoint.left),
|
|
1443
1463
|
y: Math.round(startPoint.top)
|
|
1444
1464
|
};
|
|
1445
1465
|
const end = this.calculateScrollEndPoint(start, 0, scrollDistance, 0, height);
|
|
1466
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('up', scrollDistance, Math.abs(end.y - start.y));
|
|
1446
1467
|
await this.mouseDrag(start, end);
|
|
1447
1468
|
return;
|
|
1448
1469
|
}
|
|
1449
|
-
await this.scroll(0, -scrollDistance);
|
|
1470
|
+
await this.scroll(0, -scrollDistance, void 0, hasExplicitDistance, 'up');
|
|
1450
1471
|
}
|
|
1451
1472
|
async scrollDown(distance, startPoint) {
|
|
1452
1473
|
const { height } = await this.size();
|
|
1453
1474
|
const scrollDistance = Math.round(distance || height);
|
|
1475
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1454
1476
|
if (startPoint) {
|
|
1455
1477
|
const start = {
|
|
1456
1478
|
x: Math.round(startPoint.left),
|
|
1457
1479
|
y: Math.round(startPoint.top)
|
|
1458
1480
|
};
|
|
1459
1481
|
const end = this.calculateScrollEndPoint(start, 0, -scrollDistance, 0, height);
|
|
1482
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('down', scrollDistance, Math.abs(end.y - start.y));
|
|
1460
1483
|
await this.mouseDrag(start, end);
|
|
1461
1484
|
return;
|
|
1462
1485
|
}
|
|
1463
|
-
await this.scroll(0, scrollDistance);
|
|
1486
|
+
await this.scroll(0, scrollDistance, void 0, hasExplicitDistance, 'down');
|
|
1464
1487
|
}
|
|
1465
1488
|
async scrollLeft(distance, startPoint) {
|
|
1466
1489
|
const { width } = await this.size();
|
|
1467
1490
|
const scrollDistance = Math.round(distance || width);
|
|
1491
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1468
1492
|
if (startPoint) {
|
|
1469
1493
|
const start = {
|
|
1470
1494
|
x: Math.round(startPoint.left),
|
|
1471
1495
|
y: Math.round(startPoint.top)
|
|
1472
1496
|
};
|
|
1473
1497
|
const end = this.calculateScrollEndPoint(start, scrollDistance, 0, width, 0);
|
|
1498
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('left', scrollDistance, Math.abs(end.x - start.x));
|
|
1474
1499
|
await this.mouseDrag(start, end);
|
|
1475
1500
|
return;
|
|
1476
1501
|
}
|
|
1477
|
-
await this.scroll(-scrollDistance, 0);
|
|
1502
|
+
await this.scroll(-scrollDistance, 0, void 0, hasExplicitDistance, 'left');
|
|
1478
1503
|
}
|
|
1479
1504
|
async scrollRight(distance, startPoint) {
|
|
1480
1505
|
const { width } = await this.size();
|
|
1481
1506
|
const scrollDistance = Math.round(distance || width);
|
|
1507
|
+
const hasExplicitDistance = void 0 !== distance;
|
|
1482
1508
|
if (startPoint) {
|
|
1483
1509
|
const start = {
|
|
1484
1510
|
x: Math.round(startPoint.left),
|
|
1485
1511
|
y: Math.round(startPoint.top)
|
|
1486
1512
|
};
|
|
1487
1513
|
const end = this.calculateScrollEndPoint(start, -scrollDistance, 0, width, 0);
|
|
1514
|
+
if (hasExplicitDistance) this.warnScrollDistanceClamped('right', scrollDistance, Math.abs(end.x - start.x));
|
|
1488
1515
|
await this.mouseDrag(start, end);
|
|
1489
1516
|
return;
|
|
1490
1517
|
}
|
|
1491
|
-
await this.scroll(scrollDistance, 0);
|
|
1518
|
+
await this.scroll(scrollDistance, 0, void 0, hasExplicitDistance, 'right');
|
|
1492
1519
|
}
|
|
1493
1520
|
async ensureYadb() {
|
|
1494
1521
|
if (!this.yadbPushed) {
|
|
@@ -1588,7 +1615,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1588
1615
|
const swipeDuration = duration ?? defaultNormalScrollDuration;
|
|
1589
1616
|
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
|
|
1590
1617
|
}
|
|
1591
|
-
async scroll(deltaX, deltaY, duration) {
|
|
1618
|
+
async scroll(deltaX, deltaY, duration, warnOnClamp = false, direction) {
|
|
1592
1619
|
if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
|
|
1593
1620
|
const { width, height } = await this.size();
|
|
1594
1621
|
const n = 4;
|
|
@@ -1598,8 +1625,15 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1598
1625
|
const maxNegativeDeltaX = width - startX;
|
|
1599
1626
|
const maxPositiveDeltaY = startY;
|
|
1600
1627
|
const maxNegativeDeltaY = height - startY;
|
|
1628
|
+
const originalDeltaX = deltaX;
|
|
1629
|
+
const originalDeltaY = deltaY;
|
|
1601
1630
|
deltaX = Math.max(-maxNegativeDeltaX, Math.min(deltaX, maxPositiveDeltaX));
|
|
1602
1631
|
deltaY = Math.max(-maxNegativeDeltaY, Math.min(deltaY, maxPositiveDeltaY));
|
|
1632
|
+
if (warnOnClamp && direction && (deltaX !== originalDeltaX || deltaY !== originalDeltaY)) {
|
|
1633
|
+
const requestedDistance = 'left' === direction || 'right' === direction ? Math.abs(originalDeltaX) : Math.abs(originalDeltaY);
|
|
1634
|
+
const appliedDistance = 'left' === direction || 'right' === direction ? Math.abs(deltaX) : Math.abs(deltaY);
|
|
1635
|
+
this.warnScrollDistanceClamped(direction, requestedDistance, appliedDistance);
|
|
1636
|
+
}
|
|
1603
1637
|
const endX = Math.round(startX - deltaX);
|
|
1604
1638
|
const endY = Math.round(startY - deltaY);
|
|
1605
1639
|
const { x: adjustedStartX, y: adjustedStartY } = await this.adjustCoordinates(startX, startY);
|
|
@@ -1756,7 +1790,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1756
1790
|
}
|
|
1757
1791
|
debugDevice(`Keyboard still shown after keycode ${keyCode}, trying next key`);
|
|
1758
1792
|
}
|
|
1759
|
-
|
|
1793
|
+
warnDevice('Warning: Failed to hide the software keyboard after trying both ESC and BACK keys');
|
|
1760
1794
|
return false;
|
|
1761
1795
|
}
|
|
1762
1796
|
constructor(deviceId, options){
|
|
@@ -1986,7 +2020,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1986
2020
|
constructor(toolsManager){
|
|
1987
2021
|
super({
|
|
1988
2022
|
name: '@midscene/android-mcp',
|
|
1989
|
-
version: "1.7.
|
|
2023
|
+
version: "1.7.5-beta-20260418223706.0",
|
|
1990
2024
|
description: 'Control the Android device using natural language commands'
|
|
1991
2025
|
}, toolsManager);
|
|
1992
2026
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -168,6 +168,7 @@ export declare class AndroidDevice implements AbstractInterface {
|
|
|
168
168
|
* @returns The calculated end point for the scroll gesture
|
|
169
169
|
*/
|
|
170
170
|
private calculateScrollEndPoint;
|
|
171
|
+
private warnScrollDistanceClamped;
|
|
171
172
|
screenshotBase64(): Promise<string>;
|
|
172
173
|
clearInput(element?: ElementInfo): Promise<void>;
|
|
173
174
|
forceScreenshot(path: string): Promise<void>;
|
|
@@ -206,7 +207,7 @@ export declare class AndroidDevice implements AbstractInterface {
|
|
|
206
207
|
x: number;
|
|
207
208
|
y: number;
|
|
208
209
|
}, duration?: number): Promise<void>;
|
|
209
|
-
scroll(deltaX: number, deltaY: number, duration?: number): Promise<void>;
|
|
210
|
+
scroll(deltaX: number, deltaY: number, duration?: number, warnOnClamp?: boolean, direction?: ScrollDirection): Promise<void>;
|
|
210
211
|
destroy(): Promise<void>;
|
|
211
212
|
/**
|
|
212
213
|
* Get the current time from the Android device.
|
|
@@ -445,6 +446,8 @@ declare interface ScrcpyScreenshotOptions {
|
|
|
445
446
|
idleTimeoutMs?: number;
|
|
446
447
|
}
|
|
447
448
|
|
|
449
|
+
declare type ScrollDirection = 'up' | 'down' | 'left' | 'right';
|
|
450
|
+
|
|
448
451
|
/**
|
|
449
452
|
* Helper type to convert DeviceAction to wrapped method signature
|
|
450
453
|
*/
|
|
@@ -160,6 +160,7 @@ declare class AndroidDevice implements AbstractInterface {
|
|
|
160
160
|
* @returns The calculated end point for the scroll gesture
|
|
161
161
|
*/
|
|
162
162
|
private calculateScrollEndPoint;
|
|
163
|
+
private warnScrollDistanceClamped;
|
|
163
164
|
screenshotBase64(): Promise<string>;
|
|
164
165
|
clearInput(element?: ElementInfo): Promise<void>;
|
|
165
166
|
forceScreenshot(path: string): Promise<void>;
|
|
@@ -198,7 +199,7 @@ declare class AndroidDevice implements AbstractInterface {
|
|
|
198
199
|
x: number;
|
|
199
200
|
y: number;
|
|
200
201
|
}, duration?: number): Promise<void>;
|
|
201
|
-
scroll(deltaX: number, deltaY: number, duration?: number): Promise<void>;
|
|
202
|
+
scroll(deltaX: number, deltaY: number, duration?: number, warnOnClamp?: boolean, direction?: ScrollDirection): Promise<void>;
|
|
202
203
|
destroy(): Promise<void>;
|
|
203
204
|
/**
|
|
204
205
|
* Get the current time from the Android device.
|
|
@@ -270,6 +271,8 @@ export declare function mcpServerForAgent(agent: Agent | AndroidAgent): {
|
|
|
270
271
|
launchHttp(options: LaunchMCPServerOptions): Promise<LaunchMCPServerResult>;
|
|
271
272
|
};
|
|
272
273
|
|
|
274
|
+
declare type ScrollDirection = 'up' | 'down' | 'left' | 'right';
|
|
275
|
+
|
|
273
276
|
/**
|
|
274
277
|
* Helper type to convert DeviceAction to wrapped method signature
|
|
275
278
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@midscene/android",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.5-beta-20260418223706.0",
|
|
4
4
|
"description": "Android automation library for Midscene",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Android UI automation",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"@yume-chan/stream-extra": "2.1.0",
|
|
42
42
|
"appium-adb": "12.12.1",
|
|
43
43
|
"sharp": "^0.34.3",
|
|
44
|
-
"@midscene/
|
|
45
|
-
"@midscene/
|
|
44
|
+
"@midscene/core": "1.7.5-beta-20260418223706.0",
|
|
45
|
+
"@midscene/shared": "1.7.5-beta-20260418223706.0"
|
|
46
46
|
},
|
|
47
47
|
"optionalDependencies": {
|
|
48
48
|
"@ffmpeg-installer/ffmpeg": "^1.1.0"
|
|
@@ -51,12 +51,12 @@
|
|
|
51
51
|
"@rslib/core": "^0.18.3",
|
|
52
52
|
"@types/node": "^18.0.0",
|
|
53
53
|
"dotenv": "^16.4.5",
|
|
54
|
-
"gh-release-fetch": "^4.0.3",
|
|
55
54
|
"typescript": "^5.8.3",
|
|
56
55
|
"tsx": "^4.19.2",
|
|
56
|
+
"undici": "^6.0.0",
|
|
57
57
|
"vitest": "3.0.5",
|
|
58
58
|
"zod": "^3.25.1",
|
|
59
|
-
"@midscene/playground": "1.7.
|
|
59
|
+
"@midscene/playground": "1.7.5-beta-20260418223706.0"
|
|
60
60
|
},
|
|
61
61
|
"license": "MIT",
|
|
62
62
|
"scripts": {
|