@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/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 { getMidsceneLocationSchema, z } from "@midscene/core";
4
- import { defineAction, defineActionClearInput, defineActionCursorMove, defineActionDoubleClick, defineActionDragAndDrop, defineActionKeyboardPress, defineActionLongPress, defineActionScroll, defineActionSwipe, defineActionTap, normalizeMobileSwipeParam } from "@midscene/core/device";
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
- defineActionTap(async (param)=>{
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 tap(x, y) {
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 doubleTap(x, y) {
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 longPress(x, y) {
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 inputText(text, element, shouldReplace) {
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 keyboardPress(key) {
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');
@@ -1,12 +1,12 @@
1
1
  import { BaseMCPServer, createMCPServerLauncher } from "@midscene/shared/mcp";
2
- import { getMidsceneLocationSchema, z } from "@midscene/core";
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 { defineAction, defineActionClearInput, defineActionCursorMove, defineActionDoubleClick, defineActionDragAndDrop, defineActionKeyboardPress, defineActionLongPress, defineActionScroll, defineActionSwipe, defineActionTap, normalizeMobileSwipeParam } from "@midscene/core/device";
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
- defineActionTap(async (param)=>{
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 tap(x, y) {
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 doubleTap(x, y) {
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 longPress(x, y) {
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 inputText(text, element, shouldReplace) {
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 keyboardPress(key) {
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.0",
998
+ version: "1.8.1",
1030
999
  description: 'Control the HarmonyOS device using natural language commands'
1031
1000
  }, toolsManager);
1032
1001
  }