@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/README.md +1 -49
- package/bin/darwin/rdp-helper +0 -0
- package/bin/linux/rdp-helper +0 -0
- package/dist/es/cli.mjs +239 -157
- package/dist/es/index.mjs +239 -157
- package/dist/lib/cli.js +239 -157
- package/dist/lib/index.js +239 -157
- package/dist/types/index.d.ts +20 -1
- package/native/rdp/CMakeLists.txt +22 -0
- package/native/rdp/include/rdp_helper_framebuffer.hpp +24 -0
- package/native/rdp/include/rdp_helper_session.hpp +7 -3
- package/native/rdp/src/png.cpp +5 -2
- package/native/rdp/src/session.cpp +60 -55
- package/native/rdp/tests/screenshot_test.cpp +365 -0
- package/native/rdp/tests/xrdp_desktop.py +135 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,53 +1,5 @@
|
|
|
1
1
|
# @midscene/computer
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
- local desktop control on Windows, macOS, and Linux
|
|
6
|
-
- remote Windows desktop control over the RDP protocol
|
|
3
|
+
Desktop automation library for Midscene, providing AI-powered testing and automation capabilities for Windows, macOS, and Linux, with RDP support for remote Windows desktops.
|
|
7
4
|
|
|
8
5
|
See <https://midscenejs.com/platforms/desktop>.
|
|
9
|
-
|
|
10
|
-
## RDP support
|
|
11
|
-
|
|
12
|
-
Use `agentForRDPComputer()`:
|
|
13
|
-
|
|
14
|
-
```ts
|
|
15
|
-
import { agentForRDPComputer } from '@midscene/computer';
|
|
16
|
-
|
|
17
|
-
const agent = await agentForRDPComputer({
|
|
18
|
-
host: '10.0.0.10',
|
|
19
|
-
username: 'Admin',
|
|
20
|
-
password: 'secret',
|
|
21
|
-
ignoreCertificate: true,
|
|
22
|
-
});
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
When the machine running Midscene has multiple outbound routes, pass
|
|
26
|
-
`localAddress` to bind the RDP TCP connection to a specific local source IP:
|
|
27
|
-
|
|
28
|
-
```ts
|
|
29
|
-
const agent = await agentForRDPComputer({
|
|
30
|
-
host: '10.0.0.10',
|
|
31
|
-
username: 'Admin',
|
|
32
|
-
password: 'secret',
|
|
33
|
-
localAddress: '10.0.0.20',
|
|
34
|
-
ignoreCertificate: true,
|
|
35
|
-
});
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
RDP usage requires:
|
|
39
|
-
|
|
40
|
-
- a reachable Windows machine with RDP enabled
|
|
41
|
-
- [FreeRDP](https://www.freerdp.com/) installed on the machine running your script
|
|
42
|
-
|
|
43
|
-
If you need to rebuild the native helper locally from source:
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
pnpm --filter @midscene/computer run build:native
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Run RDP AI tests:
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
pnpm --filter @midscene/computer run test:ai:rdp
|
|
53
|
-
```
|
package/bin/darwin/rdp-helper
CHANGED
|
Binary file
|
package/bin/linux/rdp-helper
CHANGED
|
Binary file
|
package/dist/es/cli.mjs
CHANGED
|
@@ -19,6 +19,73 @@ import node_assert from "node:assert";
|
|
|
19
19
|
import { hasActiveCliInterruptWaiter } from "@midscene/shared/cli/interrupt";
|
|
20
20
|
import { once } from "node:events";
|
|
21
21
|
import { createInterface } from "node:readline";
|
|
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
|
|
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.
|
|
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 ?
|
|
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.
|
|
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';
|
|
@@ -2370,6 +2436,8 @@ function createLocalComputerDevice(opts) {
|
|
|
2370
2436
|
keyboardTypeDelay: opts?.keyboardTypeDelay,
|
|
2371
2437
|
inputStrategy: opts?.inputStrategy,
|
|
2372
2438
|
keyboardDriver: opts?.keyboardDriver,
|
|
2439
|
+
keyboardModifierDelay: opts?.keyboardModifierDelay,
|
|
2440
|
+
keyboardLayout: opts?.keyboardLayout,
|
|
2373
2441
|
headless: opts?.headless,
|
|
2374
2442
|
xvfbResolution: opts?.xvfbResolution,
|
|
2375
2443
|
keepXvfbAliveUntilProcessExit: opts?.keepXvfbAliveUntilProcessExit
|
|
@@ -2427,6 +2495,10 @@ const computerInitArgShape = {
|
|
|
2427
2495
|
headless: z.boolean().optional().describe('Start virtual display via Xvfb (Linux local mode only). Ignored when host is set.'),
|
|
2428
2496
|
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.'),
|
|
2429
2497
|
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.'),
|
|
2498
|
+
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.'),
|
|
2499
|
+
keyboardLayout: z["enum"]([
|
|
2500
|
+
'en-US'
|
|
2501
|
+
]).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.'),
|
|
2430
2502
|
host: z.string().optional().describe('RDP host (FQDN or IP). Set this to switch into RDP mode.'),
|
|
2431
2503
|
port: z.number().optional().describe('RDP port (default 3389). Requires host.'),
|
|
2432
2504
|
username: z.string().optional().describe('RDP username. Requires host.'),
|
|
@@ -2443,7 +2515,7 @@ const computerInitArgShape = {
|
|
|
2443
2515
|
function adaptComputerInitArgs(extracted) {
|
|
2444
2516
|
if (!extracted || 0 === Object.keys(extracted).length) return;
|
|
2445
2517
|
if (extracted.host) {
|
|
2446
|
-
const { displayId: _d, headless: _h, ...rdpFields } = extracted;
|
|
2518
|
+
const { displayId: _d, headless: _h, keyboardModifierDelay: _s, keyboardLayout: _l, ...rdpFields } = extracted;
|
|
2447
2519
|
const host = normalizeRdpHost(extracted.host);
|
|
2448
2520
|
return {
|
|
2449
2521
|
mode: 'rdp',
|
|
@@ -2457,6 +2529,8 @@ function adaptComputerInitArgs(extracted) {
|
|
|
2457
2529
|
headless: extracted.headless,
|
|
2458
2530
|
keyboardTypeDelay: extracted.keyboardTypeDelay,
|
|
2459
2531
|
inputStrategy: extracted.inputStrategy,
|
|
2532
|
+
keyboardModifierDelay: extracted.keyboardModifierDelay,
|
|
2533
|
+
keyboardLayout: extracted.keyboardLayout,
|
|
2460
2534
|
...extractAgentBehaviorInitArgs(extracted) ?? {}
|
|
2461
2535
|
};
|
|
2462
2536
|
}
|
|
@@ -2508,6 +2582,8 @@ class ComputerMidsceneTools extends BaseMidsceneTools {
|
|
|
2508
2582
|
const headless = opts?.mode === 'local' ? opts.headless : void 0;
|
|
2509
2583
|
const keyboardTypeDelay = opts?.keyboardTypeDelay;
|
|
2510
2584
|
const inputStrategy = opts?.inputStrategy;
|
|
2585
|
+
const keyboardModifierDelay = opts?.mode === 'local' ? opts.keyboardModifierDelay : void 0;
|
|
2586
|
+
const keyboardLayout = opts?.mode === 'local' ? opts.keyboardLayout : void 0;
|
|
2511
2587
|
agent_tools_debug('Creating Computer agent with displayId:', displayId || 'primary');
|
|
2512
2588
|
const agentOpts = {
|
|
2513
2589
|
...displayId ? {
|
|
@@ -2522,6 +2598,12 @@ class ComputerMidsceneTools extends BaseMidsceneTools {
|
|
|
2522
2598
|
...void 0 !== inputStrategy ? {
|
|
2523
2599
|
inputStrategy
|
|
2524
2600
|
} : {},
|
|
2601
|
+
...void 0 !== keyboardModifierDelay ? {
|
|
2602
|
+
keyboardModifierDelay
|
|
2603
|
+
} : {},
|
|
2604
|
+
...void 0 !== keyboardLayout ? {
|
|
2605
|
+
keyboardLayout
|
|
2606
|
+
} : {},
|
|
2525
2607
|
...this.options.keepXvfbAliveUntilProcessExit ? {
|
|
2526
2608
|
keepXvfbAliveUntilProcessExit: true
|
|
2527
2609
|
} : {},
|
|
@@ -2606,7 +2688,7 @@ const tools = new ComputerMidsceneTools({
|
|
|
2606
2688
|
});
|
|
2607
2689
|
runToolsCLI(tools, 'midscene-computer', {
|
|
2608
2690
|
stripPrefix: 'computer_',
|
|
2609
|
-
version: "1.12.7
|
|
2691
|
+
version: "1.12.7",
|
|
2610
2692
|
extraCommands: createReportCliCommands()
|
|
2611
2693
|
}).catch((e)=>{
|
|
2612
2694
|
process.exit(reportCLIError(e));
|