@midscene/computer 1.12.5 → 1.12.6-beta-20260909033030.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/README.md +1 -49
- package/dist/es/cli.mjs +269 -155
- package/dist/es/index.mjs +269 -155
- package/dist/lib/cli.js +269 -155
- package/dist/lib/index.js +269 -155
- package/dist/types/index.d.ts +39 -1
- 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/dist/es/cli.mjs
CHANGED
|
@@ -19,6 +19,207 @@ 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 US_SHIFTED_CHARACTER_KEYS = new Map([
|
|
23
|
+
[
|
|
24
|
+
'~',
|
|
25
|
+
'`'
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
'!',
|
|
29
|
+
'1'
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
'@',
|
|
33
|
+
'2'
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
'#',
|
|
37
|
+
'3'
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
'$',
|
|
41
|
+
'4'
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
'%',
|
|
45
|
+
'5'
|
|
46
|
+
],
|
|
47
|
+
[
|
|
48
|
+
'^',
|
|
49
|
+
'6'
|
|
50
|
+
],
|
|
51
|
+
[
|
|
52
|
+
'&',
|
|
53
|
+
'7'
|
|
54
|
+
],
|
|
55
|
+
[
|
|
56
|
+
'*',
|
|
57
|
+
'8'
|
|
58
|
+
],
|
|
59
|
+
[
|
|
60
|
+
'(',
|
|
61
|
+
'9'
|
|
62
|
+
],
|
|
63
|
+
[
|
|
64
|
+
')',
|
|
65
|
+
'0'
|
|
66
|
+
],
|
|
67
|
+
[
|
|
68
|
+
'_',
|
|
69
|
+
'-'
|
|
70
|
+
],
|
|
71
|
+
[
|
|
72
|
+
'+',
|
|
73
|
+
'='
|
|
74
|
+
],
|
|
75
|
+
[
|
|
76
|
+
'{',
|
|
77
|
+
'['
|
|
78
|
+
],
|
|
79
|
+
[
|
|
80
|
+
'}',
|
|
81
|
+
']'
|
|
82
|
+
],
|
|
83
|
+
[
|
|
84
|
+
'|',
|
|
85
|
+
'\\'
|
|
86
|
+
],
|
|
87
|
+
[
|
|
88
|
+
':',
|
|
89
|
+
';'
|
|
90
|
+
],
|
|
91
|
+
[
|
|
92
|
+
'"',
|
|
93
|
+
"'"
|
|
94
|
+
],
|
|
95
|
+
[
|
|
96
|
+
'<',
|
|
97
|
+
','
|
|
98
|
+
],
|
|
99
|
+
[
|
|
100
|
+
'>',
|
|
101
|
+
'.'
|
|
102
|
+
],
|
|
103
|
+
[
|
|
104
|
+
'?',
|
|
105
|
+
'/'
|
|
106
|
+
]
|
|
107
|
+
]);
|
|
108
|
+
function resolveUSShiftedKey(character) {
|
|
109
|
+
if (/^[A-Z]$/.test(character)) return character.toLowerCase();
|
|
110
|
+
return US_SHIFTED_CHARACTER_KEYS.get(character);
|
|
111
|
+
}
|
|
112
|
+
const debugKeyboard = getDebug('computer:keyboard');
|
|
113
|
+
const APPLE_SCRIPT_KEY_CODES = {
|
|
114
|
+
return: 36,
|
|
115
|
+
enter: 36,
|
|
116
|
+
tab: 48,
|
|
117
|
+
space: 49,
|
|
118
|
+
backspace: 51,
|
|
119
|
+
delete: 51,
|
|
120
|
+
escape: 53,
|
|
121
|
+
forwarddelete: 117,
|
|
122
|
+
left: 123,
|
|
123
|
+
right: 124,
|
|
124
|
+
down: 125,
|
|
125
|
+
up: 126,
|
|
126
|
+
home: 115,
|
|
127
|
+
end: 119,
|
|
128
|
+
pageup: 116,
|
|
129
|
+
pagedown: 121,
|
|
130
|
+
f1: 122,
|
|
131
|
+
f2: 120,
|
|
132
|
+
f3: 99,
|
|
133
|
+
f4: 118,
|
|
134
|
+
f5: 96,
|
|
135
|
+
f6: 97,
|
|
136
|
+
f7: 98,
|
|
137
|
+
f8: 100,
|
|
138
|
+
f9: 101,
|
|
139
|
+
f10: 109,
|
|
140
|
+
f11: 103,
|
|
141
|
+
f12: 111
|
|
142
|
+
};
|
|
143
|
+
const APPLE_SCRIPT_MODIFIER_KEYS = {
|
|
144
|
+
command: 'command',
|
|
145
|
+
cmd: 'command',
|
|
146
|
+
control: 'control',
|
|
147
|
+
ctrl: 'control',
|
|
148
|
+
shift: 'shift',
|
|
149
|
+
alt: 'option',
|
|
150
|
+
option: 'option',
|
|
151
|
+
meta: 'command'
|
|
152
|
+
};
|
|
153
|
+
function buildKeyCommand(key) {
|
|
154
|
+
const keyCode = APPLE_SCRIPT_KEY_CODES[key.toLowerCase()];
|
|
155
|
+
if (void 0 !== keyCode) return `key code ${keyCode}`;
|
|
156
|
+
const escapedKey = key.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
157
|
+
return `keystroke "${escapedKey}"`;
|
|
158
|
+
}
|
|
159
|
+
function resolveModifierKeys(modifiers) {
|
|
160
|
+
return modifiers.map((modifier)=>APPLE_SCRIPT_MODIFIER_KEYS[modifier.toLowerCase()]).filter((modifier)=>void 0 !== modifier);
|
|
161
|
+
}
|
|
162
|
+
function buildLogicalKeyPress(key, modifiers) {
|
|
163
|
+
const modifierKeys = resolveModifierKeys(modifiers);
|
|
164
|
+
const modifierClause = modifierKeys.length ? ` using {${modifierKeys.map((modifier)=>`${modifier} down`).join(', ')}}` : '';
|
|
165
|
+
return `tell application "System Events" to ${buildKeyCommand(key)}${modifierClause}`;
|
|
166
|
+
}
|
|
167
|
+
function resolvePhysicalKey(key, modifiers) {
|
|
168
|
+
const resolvedModifiers = [
|
|
169
|
+
...modifiers
|
|
170
|
+
];
|
|
171
|
+
const shiftedBaseKey = resolveUSShiftedKey(key);
|
|
172
|
+
if (void 0 !== shiftedBaseKey) {
|
|
173
|
+
resolvedModifiers.push('shift');
|
|
174
|
+
return {
|
|
175
|
+
key: shiftedBaseKey,
|
|
176
|
+
modifiers: resolvedModifiers
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
key,
|
|
181
|
+
modifiers: resolvedModifiers
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function buildPhysicalKeyPress(key, modifiers) {
|
|
185
|
+
const resolved = resolvePhysicalKey(key, modifiers);
|
|
186
|
+
const modifierKeys = [
|
|
187
|
+
...new Set(resolveModifierKeys(resolved.modifiers))
|
|
188
|
+
];
|
|
189
|
+
const keyCommand = buildKeyCommand(resolved.key);
|
|
190
|
+
if (0 === modifierKeys.length) return `tell application "System Events" to ${keyCommand}`;
|
|
191
|
+
const releaseCommands = [
|
|
192
|
+
...modifierKeys
|
|
193
|
+
].reverse().map((modifier)=>`key up ${modifier}`);
|
|
194
|
+
return [
|
|
195
|
+
'tell application "System Events"',
|
|
196
|
+
'try',
|
|
197
|
+
...modifierKeys.map((modifier)=>`key down ${modifier}`),
|
|
198
|
+
keyCommand,
|
|
199
|
+
'on error errorMessage number errorNumber',
|
|
200
|
+
...releaseCommands,
|
|
201
|
+
'error errorMessage number errorNumber',
|
|
202
|
+
'end try',
|
|
203
|
+
...releaseCommands,
|
|
204
|
+
'end tell'
|
|
205
|
+
].join('\n');
|
|
206
|
+
}
|
|
207
|
+
function buildAppleScriptKeyPress(key, modifiers = [], eventMode = 'logical') {
|
|
208
|
+
return 'physical' === eventMode ? buildPhysicalKeyPress(key, modifiers) : buildLogicalKeyPress(key, modifiers);
|
|
209
|
+
}
|
|
210
|
+
function sendKeyViaAppleScript(key, modifiers = [], eventMode = 'logical') {
|
|
211
|
+
const script = buildAppleScriptKeyPress(key, modifiers, eventMode);
|
|
212
|
+
debugKeyboard('sendKeyViaAppleScript', {
|
|
213
|
+
key,
|
|
214
|
+
modifiers,
|
|
215
|
+
eventMode,
|
|
216
|
+
script
|
|
217
|
+
});
|
|
218
|
+
execFileSync("osascript", [
|
|
219
|
+
'-e',
|
|
220
|
+
script
|
|
221
|
+
]);
|
|
222
|
+
}
|
|
22
223
|
function _define_property(obj, key, value) {
|
|
23
224
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
24
225
|
value: value,
|
|
@@ -91,6 +292,29 @@ class ComputerInputDriver {
|
|
|
91
292
|
if (void 0 !== modifiers) lib.keyTap(key, modifiers);
|
|
92
293
|
else lib.keyTap(key);
|
|
93
294
|
}
|
|
295
|
+
keyToggle(key, state, modifiers) {
|
|
296
|
+
const lib = this.getLibnutOrThrow('keyToggle');
|
|
297
|
+
if (void 0 !== modifiers) lib.keyToggle(key, state, modifiers);
|
|
298
|
+
else lib.keyToggle(key, state);
|
|
299
|
+
}
|
|
300
|
+
async keyTapWithExplicitModifiers(key, modifiers, delayMs) {
|
|
301
|
+
const uniqueModifiers = [
|
|
302
|
+
...new Set(modifiers)
|
|
303
|
+
];
|
|
304
|
+
if (0 === uniqueModifiers.length) return void this.keyTap(key);
|
|
305
|
+
const pressedModifiers = [];
|
|
306
|
+
try {
|
|
307
|
+
for (const modifier of uniqueModifiers){
|
|
308
|
+
this.keyToggle(modifier, 'down');
|
|
309
|
+
pressedModifiers.push(modifier);
|
|
310
|
+
}
|
|
311
|
+
await this.delay(delayMs);
|
|
312
|
+
this.keyTap(key);
|
|
313
|
+
await this.delay(delayMs);
|
|
314
|
+
} finally{
|
|
315
|
+
for (const modifier of pressedModifiers.reverse())this.releaseKey(modifier);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
94
318
|
typeString(text) {
|
|
95
319
|
this.getLibnutOrThrow('typeString').typeString(text);
|
|
96
320
|
}
|
|
@@ -163,6 +387,15 @@ class ComputerInputDriver {
|
|
|
163
387
|
this.options.debug(`Failed to release mouse button ${button}: ${error}`);
|
|
164
388
|
}
|
|
165
389
|
}
|
|
390
|
+
releaseKey(key) {
|
|
391
|
+
try {
|
|
392
|
+
const libnut = this.options.getLibnut();
|
|
393
|
+
node_assert(libnut, 'libnut not initialized');
|
|
394
|
+
libnut.keyToggle(key, 'up');
|
|
395
|
+
} catch (error) {
|
|
396
|
+
this.options.debug(`Failed to release key ${key}: ${error}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
166
399
|
rejectPendingInputDelays() {
|
|
167
400
|
const error = this.createDestroyedError('in-flight input');
|
|
168
401
|
for (const waitRef of this.pendingInputDelayWaits){
|
|
@@ -543,92 +776,6 @@ const LIBNUT_FALLBACK_PIXELS_PER_DETENT = 100;
|
|
|
543
776
|
const LIBNUT_FALLBACK_TICK_DELAY_MS = 30;
|
|
544
777
|
const LIBNUT_FALLBACK_MAX_DETENTS = 200;
|
|
545
778
|
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
779
|
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
780
|
const DEFAULT_SCROLL_VIEWPORT_RATIO = 0.7;
|
|
634
781
|
const EDGE_SCROLL_SPEC = {
|
|
@@ -665,67 +812,6 @@ const EDGE_SCROLL_SPEC = {
|
|
|
665
812
|
]
|
|
666
813
|
}
|
|
667
814
|
};
|
|
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
815
|
function escapePowershellSingleQuoted(value) {
|
|
730
816
|
return value.replace(/'/g, "''");
|
|
731
817
|
}
|
|
@@ -1153,7 +1239,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
1153
1239
|
}
|
|
1154
1240
|
async healthCheck(displays) {
|
|
1155
1241
|
console.log('[HealthCheck] Starting health check...');
|
|
1156
|
-
console.log("[HealthCheck] @midscene/computer v1.12.
|
|
1242
|
+
console.log("[HealthCheck] @midscene/computer v1.12.6-beta-20260909033030.0");
|
|
1157
1243
|
console.log('[HealthCheck] Taking screenshot...');
|
|
1158
1244
|
const screenshotTimeout = 15000;
|
|
1159
1245
|
let timeoutId;
|
|
@@ -1330,7 +1416,7 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1330
1416
|
]);
|
|
1331
1417
|
else {
|
|
1332
1418
|
const modifier = 'darwin' === process.platform ? 'command' : 'control';
|
|
1333
|
-
this.
|
|
1419
|
+
await this.sendLocalModifiedKey('v', [
|
|
1334
1420
|
modifier
|
|
1335
1421
|
]);
|
|
1336
1422
|
}
|
|
@@ -1347,12 +1433,16 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1347
1433
|
}
|
|
1348
1434
|
async typeStringWithDelay(text, keyboardTypeDelay) {
|
|
1349
1435
|
await sendTextSequentially(text.replace(/\r\n?/g, '\n'), {
|
|
1350
|
-
sendCharacter: (character)=>{
|
|
1351
|
-
const linuxShiftedKey = 'linux' === process.platform ?
|
|
1436
|
+
sendCharacter: async (character)=>{
|
|
1437
|
+
const linuxShiftedKey = 'linux' === process.platform ? US_SHIFTED_CHARACTER_KEYS.get(character) : void 0;
|
|
1438
|
+
const pacedShiftedKey = !this.useAppleScript && (this.options?.keyboardShortcutDelay ?? 0) > 0 ? resolveUSShiftedKey(character) : void 0;
|
|
1352
1439
|
if ('\n' === character) this.inputDriver.sendKey('return');
|
|
1353
1440
|
else if ('\t' === character) this.inputDriver.sendKey('tab');
|
|
1354
1441
|
else if (' ' === character) this.inputDriver.sendKey('space');
|
|
1355
1442
|
else if (this.useAppleScript) this.inputDriver.sendKeyViaAppleScript(character);
|
|
1443
|
+
else if (void 0 !== pacedShiftedKey) await this.sendLocalModifiedKey(pacedShiftedKey, [
|
|
1444
|
+
'shift'
|
|
1445
|
+
]);
|
|
1356
1446
|
else if (void 0 !== linuxShiftedKey) this.inputDriver.keyTap(linuxShiftedKey, [
|
|
1357
1447
|
'shift'
|
|
1358
1448
|
]);
|
|
@@ -1373,12 +1463,17 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1373
1463
|
return;
|
|
1374
1464
|
}
|
|
1375
1465
|
const modifier = 'darwin' === process.platform ? 'command' : 'control';
|
|
1376
|
-
this.
|
|
1466
|
+
await this.sendLocalModifiedKey('a', [
|
|
1377
1467
|
modifier
|
|
1378
1468
|
]);
|
|
1379
1469
|
await this.inputDriver.delay(50);
|
|
1380
1470
|
this.inputDriver.keyTap('backspace');
|
|
1381
1471
|
}
|
|
1472
|
+
async sendLocalModifiedKey(key, modifiers) {
|
|
1473
|
+
const shortcutDelay = this.options?.keyboardShortcutDelay ?? 0;
|
|
1474
|
+
if (modifiers.length > 0 && shortcutDelay > 0) return void await this.inputDriver.keyTapWithExplicitModifiers(key, modifiers, shortcutDelay);
|
|
1475
|
+
this.inputDriver.keyTap(key, modifiers);
|
|
1476
|
+
}
|
|
1382
1477
|
async pressKeyboardShortcut(keyName) {
|
|
1383
1478
|
const keys = keyName.split('+');
|
|
1384
1479
|
const modifiers = keys.slice(0, -1).map(normalizeKeyName);
|
|
@@ -1389,6 +1484,7 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1389
1484
|
modifiers,
|
|
1390
1485
|
driver: this.useAppleScript ? "applescript" : 'libnut'
|
|
1391
1486
|
});
|
|
1487
|
+
if (!this.useAppleScript && modifiers.length > 0 && (this.options?.keyboardShortcutDelay ?? 0) > 0) return void await this.sendLocalModifiedKey(key, modifiers);
|
|
1392
1488
|
this.inputDriver.sendKey(key, modifiers);
|
|
1393
1489
|
}
|
|
1394
1490
|
resolveUntargetedScrollPoint(screenSize) {
|
|
@@ -1542,7 +1638,7 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1542
1638
|
device_define_property(this, "inputDriver", new ComputerInputDriver({
|
|
1543
1639
|
getLibnut: ()=>device_libnut,
|
|
1544
1640
|
useAppleScript: ()=>this.useAppleScript,
|
|
1545
|
-
sendKeyViaAppleScript,
|
|
1641
|
+
sendKeyViaAppleScript: (key, modifiers)=>sendKeyViaAppleScript(key, modifiers, this.options?.keyboardEventMode ?? 'logical'),
|
|
1546
1642
|
runPhasedScroll,
|
|
1547
1643
|
debug: (message)=>debugDevice(message)
|
|
1548
1644
|
}));
|
|
@@ -1668,6 +1764,7 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
|
|
|
1668
1764
|
}
|
|
1669
1765
|
}
|
|
1670
1766
|
});
|
|
1767
|
+
if (options?.keyboardShortcutDelay !== void 0 && (!Number.isFinite(options.keyboardShortcutDelay) || options.keyboardShortcutDelay < 0)) throw new Error('keyboardShortcutDelay must be a finite non-negative number');
|
|
1671
1768
|
this.options = options;
|
|
1672
1769
|
this.displayId = options?.displayId;
|
|
1673
1770
|
this.useAppleScript = 'darwin' === process.platform && options?.keyboardDriver !== 'libnut';
|
|
@@ -2370,6 +2467,8 @@ function createLocalComputerDevice(opts) {
|
|
|
2370
2467
|
keyboardTypeDelay: opts?.keyboardTypeDelay,
|
|
2371
2468
|
inputStrategy: opts?.inputStrategy,
|
|
2372
2469
|
keyboardDriver: opts?.keyboardDriver,
|
|
2470
|
+
keyboardEventMode: opts?.keyboardEventMode,
|
|
2471
|
+
keyboardShortcutDelay: opts?.keyboardShortcutDelay,
|
|
2373
2472
|
headless: opts?.headless,
|
|
2374
2473
|
xvfbResolution: opts?.xvfbResolution,
|
|
2375
2474
|
keepXvfbAliveUntilProcessExit: opts?.keepXvfbAliveUntilProcessExit
|
|
@@ -2427,6 +2526,11 @@ const computerInitArgShape = {
|
|
|
2427
2526
|
headless: z.boolean().optional().describe('Start virtual display via Xvfb (Linux local mode only). Ignored when host is set.'),
|
|
2428
2527
|
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
2528
|
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.'),
|
|
2529
|
+
keyboardEventMode: z["enum"]([
|
|
2530
|
+
'logical',
|
|
2531
|
+
'physical'
|
|
2532
|
+
]).optional().describe('macOS AppleScript keyboard event mode for local control. "logical" (default) uses a compact keystroke command; "physical" sends explicit modifier transitions for foreground apps that require separate key state changes. Both modes use AppleScript, and "physical" is not hardware input. It assumes an en-US layout for shifted punctuation and requires sequential text input or a positive keyboardTypeDelay. Ignored in RDP mode and outside the macOS AppleScript driver.'),
|
|
2533
|
+
keyboardShortcutDelay: z.number().finite().nonnegative().optional().describe('Finite non-negative delay in milliseconds around modifier transitions for local libnut keyboard events. Positive values pace shortcuts and the implicit Shift used by uppercase or en-US punctuation during sequential input. Ignored in RDP mode and by the macOS AppleScript driver.'),
|
|
2430
2534
|
host: z.string().optional().describe('RDP host (FQDN or IP). Set this to switch into RDP mode.'),
|
|
2431
2535
|
port: z.number().optional().describe('RDP port (default 3389). Requires host.'),
|
|
2432
2536
|
username: z.string().optional().describe('RDP username. Requires host.'),
|
|
@@ -2443,7 +2547,7 @@ const computerInitArgShape = {
|
|
|
2443
2547
|
function adaptComputerInitArgs(extracted) {
|
|
2444
2548
|
if (!extracted || 0 === Object.keys(extracted).length) return;
|
|
2445
2549
|
if (extracted.host) {
|
|
2446
|
-
const { displayId: _d, headless: _h, ...rdpFields } = extracted;
|
|
2550
|
+
const { displayId: _d, headless: _h, keyboardEventMode: _k, keyboardShortcutDelay: _s, ...rdpFields } = extracted;
|
|
2447
2551
|
const host = normalizeRdpHost(extracted.host);
|
|
2448
2552
|
return {
|
|
2449
2553
|
mode: 'rdp',
|
|
@@ -2457,6 +2561,8 @@ function adaptComputerInitArgs(extracted) {
|
|
|
2457
2561
|
headless: extracted.headless,
|
|
2458
2562
|
keyboardTypeDelay: extracted.keyboardTypeDelay,
|
|
2459
2563
|
inputStrategy: extracted.inputStrategy,
|
|
2564
|
+
keyboardEventMode: extracted.keyboardEventMode,
|
|
2565
|
+
keyboardShortcutDelay: extracted.keyboardShortcutDelay,
|
|
2460
2566
|
...extractAgentBehaviorInitArgs(extracted) ?? {}
|
|
2461
2567
|
};
|
|
2462
2568
|
}
|
|
@@ -2508,6 +2614,8 @@ class ComputerMidsceneTools extends BaseMidsceneTools {
|
|
|
2508
2614
|
const headless = opts?.mode === 'local' ? opts.headless : void 0;
|
|
2509
2615
|
const keyboardTypeDelay = opts?.keyboardTypeDelay;
|
|
2510
2616
|
const inputStrategy = opts?.inputStrategy;
|
|
2617
|
+
const keyboardEventMode = opts?.mode === 'local' ? opts.keyboardEventMode : void 0;
|
|
2618
|
+
const keyboardShortcutDelay = opts?.mode === 'local' ? opts.keyboardShortcutDelay : void 0;
|
|
2511
2619
|
agent_tools_debug('Creating Computer agent with displayId:', displayId || 'primary');
|
|
2512
2620
|
const agentOpts = {
|
|
2513
2621
|
...displayId ? {
|
|
@@ -2522,6 +2630,12 @@ class ComputerMidsceneTools extends BaseMidsceneTools {
|
|
|
2522
2630
|
...void 0 !== inputStrategy ? {
|
|
2523
2631
|
inputStrategy
|
|
2524
2632
|
} : {},
|
|
2633
|
+
...void 0 !== keyboardEventMode ? {
|
|
2634
|
+
keyboardEventMode
|
|
2635
|
+
} : {},
|
|
2636
|
+
...void 0 !== keyboardShortcutDelay ? {
|
|
2637
|
+
keyboardShortcutDelay
|
|
2638
|
+
} : {},
|
|
2525
2639
|
...this.options.keepXvfbAliveUntilProcessExit ? {
|
|
2526
2640
|
keepXvfbAliveUntilProcessExit: true
|
|
2527
2641
|
} : {},
|
|
@@ -2606,7 +2720,7 @@ const tools = new ComputerMidsceneTools({
|
|
|
2606
2720
|
});
|
|
2607
2721
|
runToolsCLI(tools, 'midscene-computer', {
|
|
2608
2722
|
stripPrefix: 'computer_',
|
|
2609
|
-
version: "1.12.
|
|
2723
|
+
version: "1.12.6-beta-20260909033030.0",
|
|
2610
2724
|
extraCommands: createReportCliCommands()
|
|
2611
2725
|
}).catch((e)=>{
|
|
2612
2726
|
process.exit(reportCLIError(e));
|