@midscene/computer 1.12.7-beta-20260911063016.0 → 1.12.7

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
@@ -19,6 +19,73 @@ import { z } from "@midscene/core";
19
19
  import { agentBehaviorInitArgShape, extractAgentBehaviorInitArgs, getAgentInitArgsSignature, shouldRebuildAgentForInitArgs } from "@midscene/shared/agent-tools/agent-behavior-init-args";
20
20
  import { BaseMidsceneTools } from "@midscene/shared/agent-tools/base-tools";
21
21
  import { overrideAIConfig } from "@midscene/shared/env";
22
+ const debugKeyboard = getDebug('computer:keyboard');
23
+ const APPLE_SCRIPT_KEY_CODES = {
24
+ return: 36,
25
+ enter: 36,
26
+ tab: 48,
27
+ space: 49,
28
+ backspace: 51,
29
+ delete: 51,
30
+ escape: 53,
31
+ forwarddelete: 117,
32
+ left: 123,
33
+ right: 124,
34
+ down: 125,
35
+ up: 126,
36
+ home: 115,
37
+ end: 119,
38
+ pageup: 116,
39
+ pagedown: 121,
40
+ f1: 122,
41
+ f2: 120,
42
+ f3: 99,
43
+ f4: 118,
44
+ f5: 96,
45
+ f6: 97,
46
+ f7: 98,
47
+ f8: 100,
48
+ f9: 101,
49
+ f10: 109,
50
+ f11: 103,
51
+ f12: 111
52
+ };
53
+ const APPLE_SCRIPT_MODIFIER_KEYS = {
54
+ command: 'command',
55
+ cmd: 'command',
56
+ control: 'control',
57
+ ctrl: 'control',
58
+ shift: 'shift',
59
+ alt: 'option',
60
+ option: 'option',
61
+ meta: 'command'
62
+ };
63
+ function buildKeyCommand(key) {
64
+ const keyCode = APPLE_SCRIPT_KEY_CODES[key.toLowerCase()];
65
+ if (void 0 !== keyCode) return `key code ${keyCode}`;
66
+ const escapedKey = key.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
67
+ return `keystroke "${escapedKey}"`;
68
+ }
69
+ function resolveModifierKeys(modifiers) {
70
+ return modifiers.map((modifier)=>APPLE_SCRIPT_MODIFIER_KEYS[modifier.toLowerCase()]).filter((modifier)=>void 0 !== modifier);
71
+ }
72
+ function buildAppleScriptKeyPress(key, modifiers = []) {
73
+ const modifierKeys = resolveModifierKeys(modifiers);
74
+ const modifierClause = modifierKeys.length ? ` using {${modifierKeys.map((modifier)=>`${modifier} down`).join(', ')}}` : '';
75
+ return `tell application "System Events" to ${buildKeyCommand(key)}${modifierClause}`;
76
+ }
77
+ function sendKeyViaAppleScript(key, modifiers = []) {
78
+ const script = buildAppleScriptKeyPress(key, modifiers);
79
+ debugKeyboard('sendKeyViaAppleScript', {
80
+ key,
81
+ modifiers,
82
+ script
83
+ });
84
+ execFileSync("osascript", [
85
+ '-e',
86
+ script
87
+ ]);
88
+ }
22
89
  function _define_property(obj, key, value) {
23
90
  if (key in obj) Object.defineProperty(obj, key, {
24
91
  value: value,
@@ -91,6 +158,41 @@ class ComputerInputDriver {
91
158
  if (void 0 !== modifiers) lib.keyTap(key, modifiers);
92
159
  else lib.keyTap(key);
93
160
  }
161
+ keyToggle(key, state, modifiers) {
162
+ const lib = this.getLibnutOrThrow('keyToggle');
163
+ if (void 0 !== modifiers) lib.keyToggle(key, state, modifiers);
164
+ else lib.keyToggle(key, state);
165
+ }
166
+ async keyTapWithModifierDelay(key, modifiers, delayMs) {
167
+ const uniqueModifiers = [
168
+ ...new Set(modifiers)
169
+ ];
170
+ if (0 === uniqueModifiers.length) return void this.keyTap(key);
171
+ if (0 === delayMs) return void this.keyTap(key, uniqueModifiers);
172
+ const pressedModifiers = [];
173
+ let failure;
174
+ try {
175
+ for (const modifier of uniqueModifiers){
176
+ this.keyToggle(modifier, 'down');
177
+ pressedModifiers.push(modifier);
178
+ await this.delay(delayMs);
179
+ }
180
+ this.keyTap(key);
181
+ await this.delay(delayMs);
182
+ } catch (error) {
183
+ failure = error;
184
+ }
185
+ for (const modifier of pressedModifiers.reverse()){
186
+ const releaseFailure = this.tryReleaseKey(modifier);
187
+ if (void 0 === failure && void 0 !== releaseFailure) failure = releaseFailure;
188
+ if (void 0 === failure) try {
189
+ await this.delay(delayMs);
190
+ } catch (error) {
191
+ failure = error;
192
+ }
193
+ }
194
+ if (void 0 !== failure) throw failure;
195
+ }
94
196
  typeString(text) {
95
197
  this.getLibnutOrThrow('typeString').typeString(text);
96
198
  }
@@ -163,6 +265,20 @@ class ComputerInputDriver {
163
265
  this.options.debug(`Failed to release mouse button ${button}: ${error}`);
164
266
  }
165
267
  }
268
+ tryReleaseKey(key) {
269
+ try {
270
+ const libnut = this.options.getLibnut();
271
+ node_assert(libnut, 'libnut not initialized');
272
+ libnut.keyToggle(key, 'up');
273
+ return;
274
+ } catch (error) {
275
+ const releaseError = new Error(`Failed to release modifier key "${key}"`, {
276
+ cause: error
277
+ });
278
+ this.options.debug(`${releaseError.message}: ${error}`);
279
+ return releaseError;
280
+ }
281
+ }
166
282
  rejectPendingInputDelays() {
167
283
  const error = this.createDestroyedError('in-flight input');
168
284
  for (const waitRef of this.pendingInputDelayWaits){
@@ -180,6 +296,96 @@ class ComputerInputDriver {
180
296
  this.pendingInputDelayWaits = new Set();
181
297
  }
182
298
  }
299
+ const US_SHIFTED_CHARACTER_KEYS = new Map([
300
+ [
301
+ '~',
302
+ '`'
303
+ ],
304
+ [
305
+ '!',
306
+ '1'
307
+ ],
308
+ [
309
+ '@',
310
+ '2'
311
+ ],
312
+ [
313
+ '#',
314
+ '3'
315
+ ],
316
+ [
317
+ '$',
318
+ '4'
319
+ ],
320
+ [
321
+ '%',
322
+ '5'
323
+ ],
324
+ [
325
+ '^',
326
+ '6'
327
+ ],
328
+ [
329
+ '&',
330
+ '7'
331
+ ],
332
+ [
333
+ '*',
334
+ '8'
335
+ ],
336
+ [
337
+ '(',
338
+ '9'
339
+ ],
340
+ [
341
+ ')',
342
+ '0'
343
+ ],
344
+ [
345
+ '_',
346
+ '-'
347
+ ],
348
+ [
349
+ '+',
350
+ '='
351
+ ],
352
+ [
353
+ '{',
354
+ '['
355
+ ],
356
+ [
357
+ '}',
358
+ ']'
359
+ ],
360
+ [
361
+ '|',
362
+ '\\'
363
+ ],
364
+ [
365
+ ':',
366
+ ';'
367
+ ],
368
+ [
369
+ '"',
370
+ "'"
371
+ ],
372
+ [
373
+ '<',
374
+ ','
375
+ ],
376
+ [
377
+ '>',
378
+ '.'
379
+ ],
380
+ [
381
+ '?',
382
+ '/'
383
+ ]
384
+ ]);
385
+ function resolveShiftedKey(character, keyboardLayout) {
386
+ if (/^[A-Z]$/.test(character)) return character.toLowerCase();
387
+ return 'en-US' === keyboardLayout ? US_SHIFTED_CHARACTER_KEYS.get(character) : void 0;
388
+ }
183
389
  const POWERSHELL_TIMEOUT_MS = 15000;
184
390
  const POWERSHELL_MAX_BUFFER = 67108864;
185
391
  const WINDOWS_PHYSICAL_PIXEL_POWERSHELL_PREAMBLE = `
@@ -543,92 +749,6 @@ const LIBNUT_FALLBACK_PIXELS_PER_DETENT = 100;
543
749
  const LIBNUT_FALLBACK_TICK_DELAY_MS = 30;
544
750
  const LIBNUT_FALLBACK_MAX_DETENTS = 200;
545
751
  const LIBNUT_FALLBACK_DETENT_AMOUNT = 'win32' === process.platform ? 120 : 1;
546
- const LINUX_SHIFTED_CHARACTER_KEYS = new Map([
547
- [
548
- '~',
549
- '`'
550
- ],
551
- [
552
- '!',
553
- '1'
554
- ],
555
- [
556
- '@',
557
- '2'
558
- ],
559
- [
560
- '#',
561
- '3'
562
- ],
563
- [
564
- '$',
565
- '4'
566
- ],
567
- [
568
- '%',
569
- '5'
570
- ],
571
- [
572
- '^',
573
- '6'
574
- ],
575
- [
576
- '&',
577
- '7'
578
- ],
579
- [
580
- '*',
581
- '8'
582
- ],
583
- [
584
- '(',
585
- '9'
586
- ],
587
- [
588
- ')',
589
- '0'
590
- ],
591
- [
592
- '_',
593
- '-'
594
- ],
595
- [
596
- '+',
597
- '='
598
- ],
599
- [
600
- '{',
601
- '['
602
- ],
603
- [
604
- '}',
605
- ']'
606
- ],
607
- [
608
- '|',
609
- '\\'
610
- ],
611
- [
612
- ':',
613
- ';'
614
- ],
615
- [
616
- '"',
617
- "'"
618
- ],
619
- [
620
- '<',
621
- ','
622
- ],
623
- [
624
- '>',
625
- '.'
626
- ],
627
- [
628
- '?',
629
- '/'
630
- ]
631
- ]);
632
752
  const LIBNUT_FALLBACK_EDGE_DETENTS = Math.min(LIBNUT_FALLBACK_MAX_DETENTS, Math.max(1, Math.ceil(EDGE_SCROLL_TOTAL_PX / LIBNUT_FALLBACK_PIXELS_PER_DETENT)));
633
753
  const DEFAULT_SCROLL_VIEWPORT_RATIO = 0.7;
634
754
  const EDGE_SCROLL_SPEC = {
@@ -665,67 +785,6 @@ const EDGE_SCROLL_SPEC = {
665
785
  ]
666
786
  }
667
787
  };
668
- const APPLESCRIPT_KEY_CODE_MAP = {
669
- return: 36,
670
- enter: 36,
671
- tab: 48,
672
- space: 49,
673
- backspace: 51,
674
- delete: 51,
675
- escape: 53,
676
- forwarddelete: 117,
677
- left: 123,
678
- right: 124,
679
- down: 125,
680
- up: 126,
681
- home: 115,
682
- end: 119,
683
- pageup: 116,
684
- pagedown: 121,
685
- f1: 122,
686
- f2: 120,
687
- f3: 99,
688
- f4: 118,
689
- f5: 96,
690
- f6: 97,
691
- f7: 98,
692
- f8: 100,
693
- f9: 101,
694
- f10: 109,
695
- f11: 103,
696
- f12: 111
697
- };
698
- const APPLESCRIPT_MODIFIER_MAP = {
699
- command: 'command down',
700
- cmd: 'command down',
701
- control: 'control down',
702
- ctrl: 'control down',
703
- shift: 'shift down',
704
- alt: 'option down',
705
- option: 'option down',
706
- meta: 'command down'
707
- };
708
- function sendKeyViaAppleScript(key, modifiers = []) {
709
- const lowerKey = key.toLowerCase();
710
- const keyCode = APPLESCRIPT_KEY_CODE_MAP[lowerKey];
711
- const modifierParts = modifiers.map((m)=>APPLESCRIPT_MODIFIER_MAP[m.toLowerCase()]).filter(Boolean);
712
- const modifierStr = modifierParts.length > 0 ? ` using {${modifierParts.join(', ')}}` : '';
713
- let script;
714
- if (void 0 !== keyCode) script = `tell application "System Events" to key code ${keyCode}${modifierStr}`;
715
- else {
716
- const escapedKey = key.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
717
- script = `tell application "System Events" to keystroke "${escapedKey}"${modifierStr}`;
718
- }
719
- debugDevice('sendKeyViaAppleScript', {
720
- key,
721
- modifiers,
722
- script
723
- });
724
- execFileSync("osascript", [
725
- '-e',
726
- script
727
- ]);
728
- }
729
788
  function escapePowershellSingleQuoted(value) {
730
789
  return value.replace(/'/g, "''");
731
790
  }
@@ -1153,7 +1212,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
1153
1212
  }
1154
1213
  async healthCheck(displays) {
1155
1214
  console.log('[HealthCheck] Starting health check...');
1156
- console.log("[HealthCheck] @midscene/computer v1.12.7-beta-20260911063016.0");
1215
+ console.log("[HealthCheck] @midscene/computer v1.12.7");
1157
1216
  console.log('[HealthCheck] Taking screenshot...');
1158
1217
  const screenshotTimeout = 15000;
1159
1218
  let timeoutId;
@@ -1330,9 +1389,9 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1330
1389
  ]);
1331
1390
  else {
1332
1391
  const modifier = 'darwin' === process.platform ? 'command' : 'control';
1333
- this.inputDriver.keyTap('v', [
1392
+ await this.inputDriver.keyTapWithModifierDelay('v', [
1334
1393
  modifier
1335
- ]);
1394
+ ], this.options?.keyboardModifierDelay ?? 0);
1336
1395
  }
1337
1396
  await this.inputDriver.delay(100);
1338
1397
  } finally{
@@ -1347,12 +1406,16 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1347
1406
  }
1348
1407
  async typeStringWithDelay(text, keyboardTypeDelay) {
1349
1408
  await sendTextSequentially(text.replace(/\r\n?/g, '\n'), {
1350
- sendCharacter: (character)=>{
1351
- const linuxShiftedKey = 'linux' === process.platform ? LINUX_SHIFTED_CHARACTER_KEYS.get(character) : void 0;
1409
+ sendCharacter: async (character)=>{
1410
+ const linuxShiftedKey = 'linux' === process.platform ? US_SHIFTED_CHARACTER_KEYS.get(character) : void 0;
1411
+ const pacedShiftedKey = !this.useAppleScript && (this.options?.keyboardModifierDelay ?? 0) > 0 ? resolveShiftedKey(character, this.options?.keyboardLayout) : void 0;
1352
1412
  if ('\n' === character) this.inputDriver.sendKey('return');
1353
1413
  else if ('\t' === character) this.inputDriver.sendKey('tab');
1354
1414
  else if (' ' === character) this.inputDriver.sendKey('space');
1355
1415
  else if (this.useAppleScript) this.inputDriver.sendKeyViaAppleScript(character);
1416
+ else if (void 0 !== pacedShiftedKey) await this.inputDriver.keyTapWithModifierDelay(pacedShiftedKey, [
1417
+ 'shift'
1418
+ ], this.options?.keyboardModifierDelay ?? 0);
1356
1419
  else if (void 0 !== linuxShiftedKey) this.inputDriver.keyTap(linuxShiftedKey, [
1357
1420
  'shift'
1358
1421
  ]);
@@ -1373,9 +1436,9 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1373
1436
  return;
1374
1437
  }
1375
1438
  const modifier = 'darwin' === process.platform ? 'command' : 'control';
1376
- this.inputDriver.keyTap('a', [
1439
+ await this.inputDriver.keyTapWithModifierDelay('a', [
1377
1440
  modifier
1378
- ]);
1441
+ ], this.options?.keyboardModifierDelay ?? 0);
1379
1442
  await this.inputDriver.delay(50);
1380
1443
  this.inputDriver.keyTap('backspace');
1381
1444
  }
@@ -1389,6 +1452,7 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1389
1452
  modifiers,
1390
1453
  driver: this.useAppleScript ? "applescript" : 'libnut'
1391
1454
  });
1455
+ if (!this.useAppleScript && modifiers.length > 0 && (this.options?.keyboardModifierDelay ?? 0) > 0) return void await this.inputDriver.keyTapWithModifierDelay(key, modifiers, this.options?.keyboardModifierDelay ?? 0);
1392
1456
  this.inputDriver.sendKey(key, modifiers);
1393
1457
  }
1394
1458
  resolveUntargetedScrollPoint(screenSize) {
@@ -1542,7 +1606,7 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1542
1606
  device_define_property(this, "inputDriver", new ComputerInputDriver({
1543
1607
  getLibnut: ()=>device_libnut,
1544
1608
  useAppleScript: ()=>this.useAppleScript,
1545
- sendKeyViaAppleScript,
1609
+ sendKeyViaAppleScript: sendKeyViaAppleScript,
1546
1610
  runPhasedScroll,
1547
1611
  debug: (message)=>debugDevice(message)
1548
1612
  }));
@@ -1668,6 +1732,8 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1668
1732
  }
1669
1733
  }
1670
1734
  });
1735
+ if (options?.keyboardModifierDelay !== void 0 && (!Number.isFinite(options.keyboardModifierDelay) || options.keyboardModifierDelay < 0)) throw new Error('keyboardModifierDelay must be a finite non-negative number');
1736
+ if (options?.keyboardLayout !== void 0 && 'en-US' !== options.keyboardLayout) throw new Error('keyboardLayout must be "en-US" when specified');
1671
1737
  this.options = options;
1672
1738
  this.displayId = options?.displayId;
1673
1739
  this.useAppleScript = 'darwin' === process.platform && options?.keyboardDriver !== 'libnut';
@@ -2413,6 +2479,8 @@ function createLocalComputerDevice(opts) {
2413
2479
  keyboardTypeDelay: opts?.keyboardTypeDelay,
2414
2480
  inputStrategy: opts?.inputStrategy,
2415
2481
  keyboardDriver: opts?.keyboardDriver,
2482
+ keyboardModifierDelay: opts?.keyboardModifierDelay,
2483
+ keyboardLayout: opts?.keyboardLayout,
2416
2484
  headless: opts?.headless,
2417
2485
  xvfbResolution: opts?.xvfbResolution,
2418
2486
  keepXvfbAliveUntilProcessExit: opts?.keepXvfbAliveUntilProcessExit
@@ -2470,6 +2538,10 @@ const computerInitArgShape = {
2470
2538
  headless: z.boolean().optional().describe('Start virtual display via Xvfb (Linux local mode only). Ignored when host is set.'),
2471
2539
  keyboardTypeDelay: z.number().finite().nonnegative().optional().describe('Finite non-negative delay in milliseconds between keystrokes. In "legacy" mode, positive values enable key-by-key input; zero or omitted uses clipboard input.'),
2472
2540
  inputStrategy: z["enum"](inputStrategies).optional().describe('Text input strategy. "legacy" (default) preserves current Computer behavior, "sequential" sends one Unicode code point at a time, and "bulk" uses one backend input operation. "bulk" requires keyboardTypeDelay to be omitted or set to 0.'),
2541
+ keyboardModifierDelay: z.number().finite().nonnegative().optional().describe('Finite non-negative delay in milliseconds after each modifier transition and the main key for local libnut keyboard events. Positive values pace shortcuts and the implicit Shift used by uppercase letters during sequential input. Layout-dependent punctuation also requires keyboardLayout. Ignored in RDP mode and by the macOS AppleScript driver.'),
2542
+ keyboardLayout: z["enum"]([
2543
+ 'en-US'
2544
+ ]).optional().describe('Keyboard layout used to resolve layout-dependent shifted punctuation to base keys during sequential local libnut input. Currently only "en-US" is supported. Ignored in RDP mode and by the macOS AppleScript driver.'),
2473
2545
  host: z.string().optional().describe('RDP host (FQDN or IP). Set this to switch into RDP mode.'),
2474
2546
  port: z.number().optional().describe('RDP port (default 3389). Requires host.'),
2475
2547
  username: z.string().optional().describe('RDP username. Requires host.'),
@@ -2486,7 +2558,7 @@ const computerInitArgShape = {
2486
2558
  function adaptComputerInitArgs(extracted) {
2487
2559
  if (!extracted || 0 === Object.keys(extracted).length) return;
2488
2560
  if (extracted.host) {
2489
- const { displayId: _d, headless: _h, ...rdpFields } = extracted;
2561
+ const { displayId: _d, headless: _h, keyboardModifierDelay: _s, keyboardLayout: _l, ...rdpFields } = extracted;
2490
2562
  const host = normalizeRdpHost(extracted.host);
2491
2563
  return {
2492
2564
  mode: 'rdp',
@@ -2500,6 +2572,8 @@ function adaptComputerInitArgs(extracted) {
2500
2572
  headless: extracted.headless,
2501
2573
  keyboardTypeDelay: extracted.keyboardTypeDelay,
2502
2574
  inputStrategy: extracted.inputStrategy,
2575
+ keyboardModifierDelay: extracted.keyboardModifierDelay,
2576
+ keyboardLayout: extracted.keyboardLayout,
2503
2577
  ...extractAgentBehaviorInitArgs(extracted) ?? {}
2504
2578
  };
2505
2579
  }
@@ -2551,6 +2625,8 @@ class ComputerMidsceneTools extends BaseMidsceneTools {
2551
2625
  const headless = opts?.mode === 'local' ? opts.headless : void 0;
2552
2626
  const keyboardTypeDelay = opts?.keyboardTypeDelay;
2553
2627
  const inputStrategy = opts?.inputStrategy;
2628
+ const keyboardModifierDelay = opts?.mode === 'local' ? opts.keyboardModifierDelay : void 0;
2629
+ const keyboardLayout = opts?.mode === 'local' ? opts.keyboardLayout : void 0;
2554
2630
  agent_tools_debug('Creating Computer agent with displayId:', displayId || 'primary');
2555
2631
  const agentOpts = {
2556
2632
  ...displayId ? {
@@ -2565,6 +2641,12 @@ class ComputerMidsceneTools extends BaseMidsceneTools {
2565
2641
  ...void 0 !== inputStrategy ? {
2566
2642
  inputStrategy
2567
2643
  } : {},
2644
+ ...void 0 !== keyboardModifierDelay ? {
2645
+ keyboardModifierDelay
2646
+ } : {},
2647
+ ...void 0 !== keyboardLayout ? {
2648
+ keyboardLayout
2649
+ } : {},
2568
2650
  ...this.options.keepXvfbAliveUntilProcessExit ? {
2569
2651
  keepXvfbAliveUntilProcessExit: true
2570
2652
  } : {},
@@ -2645,7 +2727,7 @@ class ComputerMidsceneTools extends BaseMidsceneTools {
2645
2727
  }
2646
2728
  }
2647
2729
  function version() {
2648
- const currentVersion = "1.12.7-beta-20260911063016.0";
2730
+ const currentVersion = "1.12.7";
2649
2731
  console.log(`@midscene/computer v${currentVersion}`);
2650
2732
  return currentVersion;
2651
2733
  }