@midscene/harmony 1.8.0 → 1.8.1
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/bin.mjs +72 -103
- package/dist/es/cli.mjs +73 -104
- package/dist/es/index.mjs +72 -103
- package/dist/es/mcp-server.mjs +73 -104
- package/dist/lib/bin.js +70 -101
- package/dist/lib/cli.js +71 -102
- package/dist/lib/index.js +70 -101
- package/dist/lib/mcp-server.js +71 -102
- package/dist/types/index.d.ts +8 -6
- package/dist/types/mcp-server.d.ts +8 -6
- package/package.json +4 -4
- package/static/index.html +1 -1
- package/static/static/css/{index.63b028da.css → index.26c9c911.css} +2 -2
- package/static/static/css/{index.63b028da.css.map → index.26c9c911.css.map} +1 -1
- package/static/static/js/{889.c8e2e995.js → 596.5426be9e.js} +27 -27
- package/static/static/js/596.5426be9e.js.map +1 -0
- package/static/static/js/index.acaa5ec1.js +914 -0
- package/static/static/js/index.acaa5ec1.js.map +1 -0
- package/static/static/js/889.c8e2e995.js.map +0 -1
- package/static/static/js/index.7d3d953d.js +0 -914
- package/static/static/js/index.7d3d953d.js.map +0 -1
- /package/static/static/js/{889.c8e2e995.js.LICENSE.txt → 596.5426be9e.js.LICENSE.txt} +0 -0
- /package/static/static/js/{index.7d3d953d.js.LICENSE.txt → index.acaa5ec1.js.LICENSE.txt} +0 -0
package/dist/es/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import node_assert from "node:assert";
|
|
2
2
|
import node_fs, { accessSync, constants } from "node:fs";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { z } from "@midscene/core";
|
|
4
|
+
import { createDefaultMobileActions, defineAction } from "@midscene/core/device";
|
|
5
5
|
import { getTmpFile, sleep } from "@midscene/core/utils";
|
|
6
6
|
import { createImgBase64ByFormat } from "@midscene/shared/img";
|
|
7
7
|
import { getDebug } from "@midscene/shared/logger";
|
|
@@ -221,15 +221,6 @@ function device_define_property(obj, key, value) {
|
|
|
221
221
|
else obj[key] = value;
|
|
222
222
|
return obj;
|
|
223
223
|
}
|
|
224
|
-
const harmonyInputParamSchema = z.object({
|
|
225
|
-
value: z.string().describe('The text to input. Provide the final content for replace/append modes, or an empty string when using clear mode to remove existing text.'),
|
|
226
|
-
mode: z.preprocess((val)=>'append' === val ? 'typeOnly' : val, z["enum"]([
|
|
227
|
-
'replace',
|
|
228
|
-
'clear',
|
|
229
|
-
'typeOnly'
|
|
230
|
-
]).default('replace').optional().describe('Input mode: "replace" (default) - clear the field and input the value; "typeOnly" - type the value directly without clearing the field first; "clear" - attempt to clear the field (limited support on HarmonyOS).')),
|
|
231
|
-
locate: getMidsceneLocationSchema().describe('The input field to be filled').optional()
|
|
232
|
-
});
|
|
233
224
|
const defaultScrollUntilTimes = 10;
|
|
234
225
|
const defaultFastSwipeSpeed = 2000;
|
|
235
226
|
const maxScrollDistance = 9999999;
|
|
@@ -270,89 +261,15 @@ const keyNameAliasMap = {
|
|
|
270
261
|
};
|
|
271
262
|
class HarmonyDevice {
|
|
272
263
|
actionSpace() {
|
|
264
|
+
const mobileActionContext = {
|
|
265
|
+
input: this.inputPrimitives,
|
|
266
|
+
size: ()=>this.size(),
|
|
267
|
+
sleep: async (timeMs)=>{
|
|
268
|
+
await sleep(timeMs);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
273
271
|
const defaultActions = [
|
|
274
|
-
|
|
275
|
-
const element = param.locate;
|
|
276
|
-
node_assert(element, 'Element not found, cannot tap');
|
|
277
|
-
await this.tap(element.center[0], element.center[1]);
|
|
278
|
-
}),
|
|
279
|
-
defineActionDoubleClick(async (param)=>{
|
|
280
|
-
const element = param.locate;
|
|
281
|
-
node_assert(element, 'Element not found, cannot double click');
|
|
282
|
-
await this.doubleTap(element.center[0], element.center[1]);
|
|
283
|
-
}),
|
|
284
|
-
defineAction({
|
|
285
|
-
name: 'Input',
|
|
286
|
-
description: 'Input text into the input field',
|
|
287
|
-
interfaceAlias: 'aiInput',
|
|
288
|
-
paramSchema: harmonyInputParamSchema,
|
|
289
|
-
sample: {
|
|
290
|
-
value: 'test@example.com',
|
|
291
|
-
locate: {
|
|
292
|
-
prompt: 'the email input field'
|
|
293
|
-
}
|
|
294
|
-
},
|
|
295
|
-
call: async (param)=>{
|
|
296
|
-
const element = param.locate;
|
|
297
|
-
if ('clear' === param.mode) return void await this.clearInput(element);
|
|
298
|
-
if (!param || !param.value) return;
|
|
299
|
-
const shouldReplace = 'typeOnly' !== param.mode;
|
|
300
|
-
await this.inputText(param.value, element, shouldReplace);
|
|
301
|
-
}
|
|
302
|
-
}),
|
|
303
|
-
defineActionScroll(async (param)=>{
|
|
304
|
-
const element = param.locate;
|
|
305
|
-
const startingPoint = element ? {
|
|
306
|
-
left: element.center[0],
|
|
307
|
-
top: element.center[1]
|
|
308
|
-
} : void 0;
|
|
309
|
-
const scrollToEventName = param?.scrollType;
|
|
310
|
-
if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
311
|
-
else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
312
|
-
else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
313
|
-
else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
314
|
-
else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
315
|
-
else {
|
|
316
|
-
if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance ?? void 0, startingPoint);
|
|
317
|
-
else if ('left' === param.direction) await this.scrollLeft(param.distance ?? void 0, startingPoint);
|
|
318
|
-
else if ('right' === param.direction) await this.scrollRight(param.distance ?? void 0, startingPoint);
|
|
319
|
-
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
320
|
-
else await this.scrollDown(param?.distance ?? void 0, startingPoint);
|
|
321
|
-
await sleep(500);
|
|
322
|
-
}
|
|
323
|
-
}),
|
|
324
|
-
defineActionDragAndDrop(async (param)=>{
|
|
325
|
-
const from = param.from;
|
|
326
|
-
const to = param.to;
|
|
327
|
-
node_assert(from, 'missing "from" param for drag and drop');
|
|
328
|
-
node_assert(to, 'missing "to" param for drag and drop');
|
|
329
|
-
const hdc = await this.getHdc();
|
|
330
|
-
await hdc.drag(from.center[0], from.center[1], to.center[0], to.center[1]);
|
|
331
|
-
}),
|
|
332
|
-
defineActionSwipe(async (param)=>{
|
|
333
|
-
const { startPoint, endPoint, duration, repeatCount } = normalizeMobileSwipeParam(param, await this.size());
|
|
334
|
-
const hdc = await this.getHdc();
|
|
335
|
-
for(let i = 0; i < repeatCount; i++)await hdc.swipe(startPoint.x, startPoint.y, endPoint.x, endPoint.y, duration ? Math.round(duration) : void 0);
|
|
336
|
-
}),
|
|
337
|
-
defineActionKeyboardPress(async (param)=>{
|
|
338
|
-
await this.keyboardPress(param.keyName);
|
|
339
|
-
}),
|
|
340
|
-
defineActionCursorMove(async (param)=>{
|
|
341
|
-
const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
|
|
342
|
-
const times = param.times ?? 1;
|
|
343
|
-
for(let i = 0; i < times; i++){
|
|
344
|
-
await this.keyboardPress(arrowKey);
|
|
345
|
-
await sleep(100);
|
|
346
|
-
}
|
|
347
|
-
}),
|
|
348
|
-
defineActionLongPress(async (param)=>{
|
|
349
|
-
const element = param.locate;
|
|
350
|
-
if (!element) throw new Error('LongPress requires an element to be located');
|
|
351
|
-
await this.longPress(element.center[0], element.center[1]);
|
|
352
|
-
}),
|
|
353
|
-
defineActionClearInput(async (param)=>{
|
|
354
|
-
await this.clearInput(param.locate);
|
|
355
|
-
})
|
|
272
|
+
...createDefaultMobileActions(mobileActionContext)
|
|
356
273
|
];
|
|
357
274
|
const platformSpecificActions = Object.values(createPlatformActions(this));
|
|
358
275
|
const customActions = this.customActions ?? [];
|
|
@@ -362,6 +279,27 @@ class HarmonyDevice {
|
|
|
362
279
|
...customActions
|
|
363
280
|
];
|
|
364
281
|
}
|
|
282
|
+
async performActionScroll(param) {
|
|
283
|
+
const element = param.locate;
|
|
284
|
+
const startingPoint = element ? {
|
|
285
|
+
left: element.center[0],
|
|
286
|
+
top: element.center[1]
|
|
287
|
+
} : void 0;
|
|
288
|
+
const scrollToEventName = param?.scrollType;
|
|
289
|
+
if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
290
|
+
else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
291
|
+
else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
292
|
+
else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
293
|
+
else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
294
|
+
else {
|
|
295
|
+
if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance ?? void 0, startingPoint);
|
|
296
|
+
else if ('left' === param.direction) await this.scrollLeft(param.distance ?? void 0, startingPoint);
|
|
297
|
+
else if ('right' === param.direction) await this.scrollRight(param.distance ?? void 0, startingPoint);
|
|
298
|
+
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
299
|
+
else await this.scrollDown(param?.distance ?? void 0, startingPoint);
|
|
300
|
+
await sleep(500);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
365
303
|
describe() {
|
|
366
304
|
return this.descriptionText || `DeviceId: ${this.deviceId}`;
|
|
367
305
|
}
|
|
@@ -492,23 +430,23 @@ class HarmonyDevice {
|
|
|
492
430
|
}
|
|
493
431
|
throw new Error('Screenshot buffer is empty after retries');
|
|
494
432
|
}
|
|
495
|
-
async
|
|
433
|
+
async tapPoint(point) {
|
|
496
434
|
this.lastTapPosition = {
|
|
497
|
-
x,
|
|
498
|
-
y
|
|
435
|
+
x: point.x,
|
|
436
|
+
y: point.y
|
|
499
437
|
};
|
|
500
438
|
const hdc = await this.getHdc();
|
|
501
|
-
await hdc.click(x, y);
|
|
439
|
+
await hdc.click(point.x, point.y);
|
|
502
440
|
}
|
|
503
|
-
async
|
|
441
|
+
async doubleTapPoint(point) {
|
|
504
442
|
const hdc = await this.getHdc();
|
|
505
|
-
await hdc.doubleClick(x, y);
|
|
443
|
+
await hdc.doubleClick(point.x, point.y);
|
|
506
444
|
}
|
|
507
|
-
async
|
|
445
|
+
async longPressPoint(point) {
|
|
508
446
|
const hdc = await this.getHdc();
|
|
509
|
-
await hdc.longClick(x, y);
|
|
447
|
+
await hdc.longClick(point.x, point.y);
|
|
510
448
|
}
|
|
511
|
-
async
|
|
449
|
+
async typeText(text, element, shouldReplace) {
|
|
512
450
|
if (!text) return;
|
|
513
451
|
const hdc = await this.getHdc();
|
|
514
452
|
let x;
|
|
@@ -539,7 +477,7 @@ class HarmonyDevice {
|
|
|
539
477
|
}
|
|
540
478
|
await hdc.clearTextField(100);
|
|
541
479
|
}
|
|
542
|
-
async
|
|
480
|
+
async pressKey(key) {
|
|
543
481
|
const normalizedKey = keyNameAliasMap[key.toLowerCase()] ?? key;
|
|
544
482
|
const harmonyKey = harmonyKeyCodeMap[normalizedKey] ?? key;
|
|
545
483
|
const hdc = await this.getHdc();
|
|
@@ -739,6 +677,37 @@ class HarmonyDevice {
|
|
|
739
677
|
device_define_property(this, "interfaceType", 'harmony');
|
|
740
678
|
device_define_property(this, "uri", void 0);
|
|
741
679
|
device_define_property(this, "options", void 0);
|
|
680
|
+
device_define_property(this, "inputPrimitives", {
|
|
681
|
+
pointer: {
|
|
682
|
+
tap: (point)=>this.tapPoint(point),
|
|
683
|
+
doubleClick: (point)=>this.doubleTapPoint(point),
|
|
684
|
+
longPress: (point)=>this.longPressPoint(point),
|
|
685
|
+
dragAndDrop: async (from, to)=>{
|
|
686
|
+
const hdc = await this.getHdc();
|
|
687
|
+
await hdc.drag(from.x, from.y, to.x, to.y);
|
|
688
|
+
}
|
|
689
|
+
},
|
|
690
|
+
keyboard: {
|
|
691
|
+
keyboardPress: (keyName)=>this.pressKey(keyName),
|
|
692
|
+
typeText: (value, opts)=>opts?.focusOnly ? Promise.resolve() : this.typeText(value, opts?.target, opts?.replace ?? true),
|
|
693
|
+
clearInput: (target)=>this.clearInput(target),
|
|
694
|
+
cursorMove: async (direction, times = 1)=>{
|
|
695
|
+
const arrowKey = 'left' === direction ? 'ArrowLeft' : 'ArrowRight';
|
|
696
|
+
for(let i = 0; i < times; i++)await this.pressKey(arrowKey);
|
|
697
|
+
}
|
|
698
|
+
},
|
|
699
|
+
touch: {
|
|
700
|
+
swipe: async (start, end, opts)=>{
|
|
701
|
+
const duration = opts?.duration;
|
|
702
|
+
const repeatCount = opts?.repeat ?? 1;
|
|
703
|
+
const hdc = await this.getHdc();
|
|
704
|
+
for(let i = 0; i < repeatCount; i++)await hdc.swipe(start.x, start.y, end.x, end.y, duration ? Math.round(duration) : void 0);
|
|
705
|
+
}
|
|
706
|
+
},
|
|
707
|
+
scroll: {
|
|
708
|
+
scroll: (param)=>this.performActionScroll(param)
|
|
709
|
+
}
|
|
710
|
+
});
|
|
742
711
|
device_define_property(this, "remoteScreenshotPath", '/data/local/tmp/ms_screen.jpeg');
|
|
743
712
|
device_define_property(this, "localScreenshotPath", null);
|
|
744
713
|
node_assert(deviceId, 'deviceId is required for HarmonyDevice');
|
package/dist/es/mcp-server.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { BaseMCPServer, createMCPServerLauncher } from "@midscene/shared/mcp";
|
|
2
|
-
import {
|
|
2
|
+
import { z } from "@midscene/core";
|
|
3
3
|
import { getDebug } from "@midscene/shared/logger";
|
|
4
4
|
import { BaseMidsceneTools } from "@midscene/shared/mcp/base-tools";
|
|
5
5
|
import { Agent } from "@midscene/core/agent";
|
|
6
6
|
import { mergeAndNormalizeAppNameMapping, normalizeForComparison, repeat } from "@midscene/shared/utils";
|
|
7
7
|
import node_assert from "node:assert";
|
|
8
8
|
import node_fs, { accessSync, constants } from "node:fs";
|
|
9
|
-
import {
|
|
9
|
+
import { createDefaultMobileActions, defineAction } from "@midscene/core/device";
|
|
10
10
|
import { getTmpFile, sleep } from "@midscene/core/utils";
|
|
11
11
|
import { createImgBase64ByFormat } from "@midscene/shared/img";
|
|
12
12
|
import { execFile } from "node:child_process";
|
|
@@ -250,15 +250,6 @@ function device_define_property(obj, key, value) {
|
|
|
250
250
|
else obj[key] = value;
|
|
251
251
|
return obj;
|
|
252
252
|
}
|
|
253
|
-
const harmonyInputParamSchema = z.object({
|
|
254
|
-
value: z.string().describe('The text to input. Provide the final content for replace/append modes, or an empty string when using clear mode to remove existing text.'),
|
|
255
|
-
mode: z.preprocess((val)=>'append' === val ? 'typeOnly' : val, z["enum"]([
|
|
256
|
-
'replace',
|
|
257
|
-
'clear',
|
|
258
|
-
'typeOnly'
|
|
259
|
-
]).default('replace').optional().describe('Input mode: "replace" (default) - clear the field and input the value; "typeOnly" - type the value directly without clearing the field first; "clear" - attempt to clear the field (limited support on HarmonyOS).')),
|
|
260
|
-
locate: getMidsceneLocationSchema().describe('The input field to be filled').optional()
|
|
261
|
-
});
|
|
262
253
|
const defaultScrollUntilTimes = 10;
|
|
263
254
|
const defaultFastSwipeSpeed = 2000;
|
|
264
255
|
const maxScrollDistance = 9999999;
|
|
@@ -299,89 +290,15 @@ const keyNameAliasMap = {
|
|
|
299
290
|
};
|
|
300
291
|
class HarmonyDevice {
|
|
301
292
|
actionSpace() {
|
|
293
|
+
const mobileActionContext = {
|
|
294
|
+
input: this.inputPrimitives,
|
|
295
|
+
size: ()=>this.size(),
|
|
296
|
+
sleep: async (timeMs)=>{
|
|
297
|
+
await sleep(timeMs);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
302
300
|
const defaultActions = [
|
|
303
|
-
|
|
304
|
-
const element = param.locate;
|
|
305
|
-
node_assert(element, 'Element not found, cannot tap');
|
|
306
|
-
await this.tap(element.center[0], element.center[1]);
|
|
307
|
-
}),
|
|
308
|
-
defineActionDoubleClick(async (param)=>{
|
|
309
|
-
const element = param.locate;
|
|
310
|
-
node_assert(element, 'Element not found, cannot double click');
|
|
311
|
-
await this.doubleTap(element.center[0], element.center[1]);
|
|
312
|
-
}),
|
|
313
|
-
defineAction({
|
|
314
|
-
name: 'Input',
|
|
315
|
-
description: 'Input text into the input field',
|
|
316
|
-
interfaceAlias: 'aiInput',
|
|
317
|
-
paramSchema: harmonyInputParamSchema,
|
|
318
|
-
sample: {
|
|
319
|
-
value: 'test@example.com',
|
|
320
|
-
locate: {
|
|
321
|
-
prompt: 'the email input field'
|
|
322
|
-
}
|
|
323
|
-
},
|
|
324
|
-
call: async (param)=>{
|
|
325
|
-
const element = param.locate;
|
|
326
|
-
if ('clear' === param.mode) return void await this.clearInput(element);
|
|
327
|
-
if (!param || !param.value) return;
|
|
328
|
-
const shouldReplace = 'typeOnly' !== param.mode;
|
|
329
|
-
await this.inputText(param.value, element, shouldReplace);
|
|
330
|
-
}
|
|
331
|
-
}),
|
|
332
|
-
defineActionScroll(async (param)=>{
|
|
333
|
-
const element = param.locate;
|
|
334
|
-
const startingPoint = element ? {
|
|
335
|
-
left: element.center[0],
|
|
336
|
-
top: element.center[1]
|
|
337
|
-
} : void 0;
|
|
338
|
-
const scrollToEventName = param?.scrollType;
|
|
339
|
-
if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
340
|
-
else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
341
|
-
else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
342
|
-
else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
343
|
-
else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
344
|
-
else {
|
|
345
|
-
if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance ?? void 0, startingPoint);
|
|
346
|
-
else if ('left' === param.direction) await this.scrollLeft(param.distance ?? void 0, startingPoint);
|
|
347
|
-
else if ('right' === param.direction) await this.scrollRight(param.distance ?? void 0, startingPoint);
|
|
348
|
-
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
349
|
-
else await this.scrollDown(param?.distance ?? void 0, startingPoint);
|
|
350
|
-
await sleep(500);
|
|
351
|
-
}
|
|
352
|
-
}),
|
|
353
|
-
defineActionDragAndDrop(async (param)=>{
|
|
354
|
-
const from = param.from;
|
|
355
|
-
const to = param.to;
|
|
356
|
-
node_assert(from, 'missing "from" param for drag and drop');
|
|
357
|
-
node_assert(to, 'missing "to" param for drag and drop');
|
|
358
|
-
const hdc = await this.getHdc();
|
|
359
|
-
await hdc.drag(from.center[0], from.center[1], to.center[0], to.center[1]);
|
|
360
|
-
}),
|
|
361
|
-
defineActionSwipe(async (param)=>{
|
|
362
|
-
const { startPoint, endPoint, duration, repeatCount } = normalizeMobileSwipeParam(param, await this.size());
|
|
363
|
-
const hdc = await this.getHdc();
|
|
364
|
-
for(let i = 0; i < repeatCount; i++)await hdc.swipe(startPoint.x, startPoint.y, endPoint.x, endPoint.y, duration ? Math.round(duration) : void 0);
|
|
365
|
-
}),
|
|
366
|
-
defineActionKeyboardPress(async (param)=>{
|
|
367
|
-
await this.keyboardPress(param.keyName);
|
|
368
|
-
}),
|
|
369
|
-
defineActionCursorMove(async (param)=>{
|
|
370
|
-
const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
|
|
371
|
-
const times = param.times ?? 1;
|
|
372
|
-
for(let i = 0; i < times; i++){
|
|
373
|
-
await this.keyboardPress(arrowKey);
|
|
374
|
-
await sleep(100);
|
|
375
|
-
}
|
|
376
|
-
}),
|
|
377
|
-
defineActionLongPress(async (param)=>{
|
|
378
|
-
const element = param.locate;
|
|
379
|
-
if (!element) throw new Error('LongPress requires an element to be located');
|
|
380
|
-
await this.longPress(element.center[0], element.center[1]);
|
|
381
|
-
}),
|
|
382
|
-
defineActionClearInput(async (param)=>{
|
|
383
|
-
await this.clearInput(param.locate);
|
|
384
|
-
})
|
|
301
|
+
...createDefaultMobileActions(mobileActionContext)
|
|
385
302
|
];
|
|
386
303
|
const platformSpecificActions = Object.values(createPlatformActions(this));
|
|
387
304
|
const customActions = this.customActions ?? [];
|
|
@@ -391,6 +308,27 @@ class HarmonyDevice {
|
|
|
391
308
|
...customActions
|
|
392
309
|
];
|
|
393
310
|
}
|
|
311
|
+
async performActionScroll(param) {
|
|
312
|
+
const element = param.locate;
|
|
313
|
+
const startingPoint = element ? {
|
|
314
|
+
left: element.center[0],
|
|
315
|
+
top: element.center[1]
|
|
316
|
+
} : void 0;
|
|
317
|
+
const scrollToEventName = param?.scrollType;
|
|
318
|
+
if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
319
|
+
else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
320
|
+
else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
321
|
+
else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
322
|
+
else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
323
|
+
else {
|
|
324
|
+
if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance ?? void 0, startingPoint);
|
|
325
|
+
else if ('left' === param.direction) await this.scrollLeft(param.distance ?? void 0, startingPoint);
|
|
326
|
+
else if ('right' === param.direction) await this.scrollRight(param.distance ?? void 0, startingPoint);
|
|
327
|
+
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
328
|
+
else await this.scrollDown(param?.distance ?? void 0, startingPoint);
|
|
329
|
+
await sleep(500);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
394
332
|
describe() {
|
|
395
333
|
return this.descriptionText || `DeviceId: ${this.deviceId}`;
|
|
396
334
|
}
|
|
@@ -521,23 +459,23 @@ class HarmonyDevice {
|
|
|
521
459
|
}
|
|
522
460
|
throw new Error('Screenshot buffer is empty after retries');
|
|
523
461
|
}
|
|
524
|
-
async
|
|
462
|
+
async tapPoint(point) {
|
|
525
463
|
this.lastTapPosition = {
|
|
526
|
-
x,
|
|
527
|
-
y
|
|
464
|
+
x: point.x,
|
|
465
|
+
y: point.y
|
|
528
466
|
};
|
|
529
467
|
const hdc = await this.getHdc();
|
|
530
|
-
await hdc.click(x, y);
|
|
468
|
+
await hdc.click(point.x, point.y);
|
|
531
469
|
}
|
|
532
|
-
async
|
|
470
|
+
async doubleTapPoint(point) {
|
|
533
471
|
const hdc = await this.getHdc();
|
|
534
|
-
await hdc.doubleClick(x, y);
|
|
472
|
+
await hdc.doubleClick(point.x, point.y);
|
|
535
473
|
}
|
|
536
|
-
async
|
|
474
|
+
async longPressPoint(point) {
|
|
537
475
|
const hdc = await this.getHdc();
|
|
538
|
-
await hdc.longClick(x, y);
|
|
476
|
+
await hdc.longClick(point.x, point.y);
|
|
539
477
|
}
|
|
540
|
-
async
|
|
478
|
+
async typeText(text, element, shouldReplace) {
|
|
541
479
|
if (!text) return;
|
|
542
480
|
const hdc = await this.getHdc();
|
|
543
481
|
let x;
|
|
@@ -568,7 +506,7 @@ class HarmonyDevice {
|
|
|
568
506
|
}
|
|
569
507
|
await hdc.clearTextField(100);
|
|
570
508
|
}
|
|
571
|
-
async
|
|
509
|
+
async pressKey(key) {
|
|
572
510
|
const normalizedKey = keyNameAliasMap[key.toLowerCase()] ?? key;
|
|
573
511
|
const harmonyKey = harmonyKeyCodeMap[normalizedKey] ?? key;
|
|
574
512
|
const hdc = await this.getHdc();
|
|
@@ -768,6 +706,37 @@ class HarmonyDevice {
|
|
|
768
706
|
device_define_property(this, "interfaceType", 'harmony');
|
|
769
707
|
device_define_property(this, "uri", void 0);
|
|
770
708
|
device_define_property(this, "options", void 0);
|
|
709
|
+
device_define_property(this, "inputPrimitives", {
|
|
710
|
+
pointer: {
|
|
711
|
+
tap: (point)=>this.tapPoint(point),
|
|
712
|
+
doubleClick: (point)=>this.doubleTapPoint(point),
|
|
713
|
+
longPress: (point)=>this.longPressPoint(point),
|
|
714
|
+
dragAndDrop: async (from, to)=>{
|
|
715
|
+
const hdc = await this.getHdc();
|
|
716
|
+
await hdc.drag(from.x, from.y, to.x, to.y);
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
keyboard: {
|
|
720
|
+
keyboardPress: (keyName)=>this.pressKey(keyName),
|
|
721
|
+
typeText: (value, opts)=>opts?.focusOnly ? Promise.resolve() : this.typeText(value, opts?.target, opts?.replace ?? true),
|
|
722
|
+
clearInput: (target)=>this.clearInput(target),
|
|
723
|
+
cursorMove: async (direction, times = 1)=>{
|
|
724
|
+
const arrowKey = 'left' === direction ? 'ArrowLeft' : 'ArrowRight';
|
|
725
|
+
for(let i = 0; i < times; i++)await this.pressKey(arrowKey);
|
|
726
|
+
}
|
|
727
|
+
},
|
|
728
|
+
touch: {
|
|
729
|
+
swipe: async (start, end, opts)=>{
|
|
730
|
+
const duration = opts?.duration;
|
|
731
|
+
const repeatCount = opts?.repeat ?? 1;
|
|
732
|
+
const hdc = await this.getHdc();
|
|
733
|
+
for(let i = 0; i < repeatCount; i++)await hdc.swipe(start.x, start.y, end.x, end.y, duration ? Math.round(duration) : void 0);
|
|
734
|
+
}
|
|
735
|
+
},
|
|
736
|
+
scroll: {
|
|
737
|
+
scroll: (param)=>this.performActionScroll(param)
|
|
738
|
+
}
|
|
739
|
+
});
|
|
771
740
|
device_define_property(this, "remoteScreenshotPath", '/data/local/tmp/ms_screen.jpeg');
|
|
772
741
|
device_define_property(this, "localScreenshotPath", null);
|
|
773
742
|
node_assert(deviceId, 'deviceId is required for HarmonyDevice');
|
|
@@ -1026,7 +995,7 @@ class HarmonyMCPServer extends BaseMCPServer {
|
|
|
1026
995
|
constructor(toolsManager){
|
|
1027
996
|
super({
|
|
1028
997
|
name: '@midscene/harmony-mcp',
|
|
1029
|
-
version: "1.8.
|
|
998
|
+
version: "1.8.1",
|
|
1030
999
|
description: 'Control the HarmonyOS device using natural language commands'
|
|
1031
1000
|
}, toolsManager);
|
|
1032
1001
|
}
|