@midscene/computer 1.9.3-beta-20260610061213.0 → 1.9.3-beta-20260610071913.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 +0 -13
- package/bin/darwin/rdp-helper +0 -0
- package/bin/linux/rdp-helper +0 -0
- package/dist/es/cli.mjs +137 -166
- package/dist/es/index.mjs +137 -166
- package/dist/es/mcp-server.mjs +137 -166
- package/dist/lib/cli.js +137 -166
- package/dist/lib/index.js +137 -166
- package/dist/lib/mcp-server.js +137 -166
- package/dist/types/index.d.ts +5 -5
- package/dist/types/mcp-server.d.ts +2 -1
- package/native/rdp/include/rdp_helper_protocol.hpp +0 -1
- package/native/rdp/src/main.cpp +1 -13
- package/native/rdp/src/session.cpp +3 -420
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -22,19 +22,6 @@ const agent = await agentForRDPComputer({
|
|
|
22
22
|
});
|
|
23
23
|
```
|
|
24
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
25
|
RDP usage requires:
|
|
39
26
|
|
|
40
27
|
- a reachable Windows machine with RDP enabled
|
package/bin/darwin/rdp-helper
CHANGED
|
Binary file
|
package/bin/linux/rdp-helper
CHANGED
|
Binary file
|
package/dist/es/cli.mjs
CHANGED
|
@@ -40,6 +40,33 @@ class ComputerInputDriver {
|
|
|
40
40
|
moveMouse(x, y) {
|
|
41
41
|
this.getLibnutOrThrow('moveMouse').moveMouse(x, y);
|
|
42
42
|
}
|
|
43
|
+
focusActiveWindow() {
|
|
44
|
+
const lib = this.getLibnutOrThrow('focusActiveWindow');
|
|
45
|
+
if ('function' != typeof lib.getActiveWindow || 'function' != typeof lib.focusWindow) return false;
|
|
46
|
+
try {
|
|
47
|
+
const handle = lib.getActiveWindow();
|
|
48
|
+
if (!handle) return false;
|
|
49
|
+
lib.focusWindow(handle);
|
|
50
|
+
return true;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
this.options.debug(`focusActiveWindow failed: ${error}`);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
getActiveWindowRect() {
|
|
57
|
+
const lib = this.getLibnutOrThrow('getActiveWindowRect');
|
|
58
|
+
if ('function' != typeof lib.getActiveWindow || 'function' != typeof lib.getWindowRect) return null;
|
|
59
|
+
try {
|
|
60
|
+
const handle = lib.getActiveWindow();
|
|
61
|
+
if (!handle) return null;
|
|
62
|
+
const rect = lib.getWindowRect(handle);
|
|
63
|
+
if (!Number.isFinite(rect.x) || !Number.isFinite(rect.y) || !Number.isFinite(rect.width) || !Number.isFinite(rect.height) || rect.width <= 0 || rect.height <= 0) return null;
|
|
64
|
+
return rect;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
this.options.debug(`getActiveWindowRect failed: ${error}`);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
43
70
|
mouseClick(button, double) {
|
|
44
71
|
const lib = this.getLibnutOrThrow('mouseClick');
|
|
45
72
|
if (void 0 !== double) lib.mouseClick(button, double);
|
|
@@ -52,6 +79,13 @@ class ComputerInputDriver {
|
|
|
52
79
|
scrollMouse(x, y) {
|
|
53
80
|
this.getLibnutOrThrow('scrollMouse').scrollMouse(x, y);
|
|
54
81
|
}
|
|
82
|
+
async emitScrollDetents(dx, dy, detents, delayMs) {
|
|
83
|
+
this.assertActive('emitScrollDetents');
|
|
84
|
+
for(let i = 0; i < detents; i++){
|
|
85
|
+
this.scrollMouse(dx, dy);
|
|
86
|
+
if (i < detents - 1) await this.delay(delayMs);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
55
89
|
keyTap(key, modifiers) {
|
|
56
90
|
const lib = this.getLibnutOrThrow('keyTap');
|
|
57
91
|
if (void 0 !== modifiers) lib.keyTap(key, modifiers);
|
|
@@ -230,13 +264,17 @@ const CLICK_HOLD_DURATION = 100;
|
|
|
230
264
|
const CLICK_FOCUS_SETTLE_DELAY = 120;
|
|
231
265
|
const INPUT_FOCUS_DELAY = 300;
|
|
232
266
|
const INPUT_CLEAR_DELAY = 150;
|
|
233
|
-
const SCROLL_REPEAT_COUNT = 10;
|
|
234
267
|
const SCROLL_STEP_DELAY = 100;
|
|
235
268
|
const SCROLL_COMPLETE_DELAY = 500;
|
|
236
269
|
const EDGE_SCROLL_TOTAL_PX = 50000;
|
|
237
270
|
const EDGE_SCROLL_STEPS = 400;
|
|
238
271
|
const PHASED_PIXELS_PER_STEP = 30;
|
|
239
272
|
const PHASED_MIN_STEPS = 10;
|
|
273
|
+
const LIBNUT_FALLBACK_PIXELS_PER_DETENT = 100;
|
|
274
|
+
const LIBNUT_FALLBACK_TICK_DELAY_MS = 30;
|
|
275
|
+
const LIBNUT_FALLBACK_MAX_DETENTS = 200;
|
|
276
|
+
const LIBNUT_FALLBACK_DETENT_AMOUNT = 'win32' === process.platform ? 120 : 1;
|
|
277
|
+
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)));
|
|
240
278
|
const DEFAULT_SCROLL_VIEWPORT_RATIO = 0.7;
|
|
241
279
|
const EDGE_SCROLL_SPEC = {
|
|
242
280
|
scrollToTop: {
|
|
@@ -244,7 +282,7 @@ const EDGE_SCROLL_SPEC = {
|
|
|
244
282
|
key: 'home',
|
|
245
283
|
libnut: [
|
|
246
284
|
0,
|
|
247
|
-
|
|
285
|
+
1
|
|
248
286
|
]
|
|
249
287
|
},
|
|
250
288
|
scrollToBottom: {
|
|
@@ -252,14 +290,14 @@ const EDGE_SCROLL_SPEC = {
|
|
|
252
290
|
key: 'end',
|
|
253
291
|
libnut: [
|
|
254
292
|
0,
|
|
255
|
-
-
|
|
293
|
+
-1
|
|
256
294
|
]
|
|
257
295
|
},
|
|
258
296
|
scrollToLeft: {
|
|
259
297
|
direction: 'left',
|
|
260
298
|
key: 'home',
|
|
261
299
|
libnut: [
|
|
262
|
-
-
|
|
300
|
+
-1,
|
|
263
301
|
0
|
|
264
302
|
]
|
|
265
303
|
},
|
|
@@ -267,7 +305,7 @@ const EDGE_SCROLL_SPEC = {
|
|
|
267
305
|
direction: 'right',
|
|
268
306
|
key: 'end',
|
|
269
307
|
libnut: [
|
|
270
|
-
|
|
308
|
+
1,
|
|
271
309
|
0
|
|
272
310
|
]
|
|
273
311
|
}
|
|
@@ -656,7 +694,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
656
694
|
}
|
|
657
695
|
async healthCheck() {
|
|
658
696
|
console.log('[HealthCheck] Starting health check...');
|
|
659
|
-
console.log("[HealthCheck] @midscene/computer v1.9.3-beta-
|
|
697
|
+
console.log("[HealthCheck] @midscene/computer v1.9.3-beta-20260610071913.0");
|
|
660
698
|
console.log('[HealthCheck] Taking screenshot...');
|
|
661
699
|
const screenshotTimeout = 15000;
|
|
662
700
|
let timeoutId;
|
|
@@ -834,7 +872,20 @@ Original error: ${lastRawMessage}`);
|
|
|
834
872
|
});
|
|
835
873
|
this.inputDriver.sendKey(key, modifiers);
|
|
836
874
|
}
|
|
837
|
-
|
|
875
|
+
resolveUntargetedScrollPoint(screenSize) {
|
|
876
|
+
if ('win32' === process.platform) {
|
|
877
|
+
const activeWindowRect = this.inputDriver.getActiveWindowRect();
|
|
878
|
+
if (activeWindowRect) return {
|
|
879
|
+
x: activeWindowRect.x + activeWindowRect.width / 2,
|
|
880
|
+
y: activeWindowRect.y + activeWindowRect.height / 2
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
return this.toGlobalPoint({
|
|
884
|
+
x: screenSize.width / 2,
|
|
885
|
+
y: screenSize.height / 2
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
async moveMouseToScrollTarget(param) {
|
|
838
889
|
if (param.locate) {
|
|
839
890
|
const element = param.locate;
|
|
840
891
|
const [x, y] = element.center;
|
|
@@ -843,7 +894,17 @@ Original error: ${lastRawMessage}`);
|
|
|
843
894
|
y
|
|
844
895
|
});
|
|
845
896
|
this.inputDriver.moveMouse(Math.round(point.x), Math.round(point.y));
|
|
897
|
+
return;
|
|
846
898
|
}
|
|
899
|
+
const screenSize = await this.size();
|
|
900
|
+
if ('win32' === process.platform && this.inputDriver.focusActiveWindow()) await this.inputDriver.delay(CLICK_FOCUS_SETTLE_DELAY);
|
|
901
|
+
const point = this.resolveUntargetedScrollPoint(screenSize);
|
|
902
|
+
this.inputDriver.moveMouse(Math.round(point.x), Math.round(point.y));
|
|
903
|
+
await this.inputDriver.delay(MOUSE_MOVE_EFFECT_WAIT);
|
|
904
|
+
return screenSize;
|
|
905
|
+
}
|
|
906
|
+
async performScroll(param) {
|
|
907
|
+
let screenSize = await this.moveMouseToScrollTarget(param);
|
|
847
908
|
const scrollType = param?.scrollType;
|
|
848
909
|
const edgeSpec = scrollType && scrollType in EDGE_SCROLL_SPEC ? EDGE_SCROLL_SPEC[scrollType] : null;
|
|
849
910
|
if (edgeSpec) {
|
|
@@ -853,11 +914,8 @@ Original error: ${lastRawMessage}`);
|
|
|
853
914
|
await this.inputDriver.delay(SCROLL_COMPLETE_DELAY);
|
|
854
915
|
return;
|
|
855
916
|
}
|
|
856
|
-
const [
|
|
857
|
-
|
|
858
|
-
this.inputDriver.scrollMouse(dx, dy);
|
|
859
|
-
await this.inputDriver.delay(SCROLL_STEP_DELAY);
|
|
860
|
-
}
|
|
917
|
+
const [ux, uy] = edgeSpec.libnut;
|
|
918
|
+
await this.inputDriver.emitScrollDetents(ux * LIBNUT_FALLBACK_DETENT_AMOUNT, uy * LIBNUT_FALLBACK_DETENT_AMOUNT, LIBNUT_FALLBACK_EDGE_DETENTS, LIBNUT_FALLBACK_TICK_DELAY_MS);
|
|
861
919
|
return;
|
|
862
920
|
}
|
|
863
921
|
if ('singleAction' === scrollType || !scrollType) {
|
|
@@ -865,9 +923,8 @@ Original error: ${lastRawMessage}`);
|
|
|
865
923
|
const isKnownDirection = 'up' === direction || 'down' === direction || 'left' === direction || 'right' === direction;
|
|
866
924
|
const isHorizontal = 'left' === direction || 'right' === direction;
|
|
867
925
|
let distance = param?.distance ?? void 0;
|
|
868
|
-
let screenSize;
|
|
869
926
|
if (!distance) {
|
|
870
|
-
screenSize
|
|
927
|
+
screenSize ??= await this.size();
|
|
871
928
|
const base = isHorizontal ? screenSize.width : screenSize.height;
|
|
872
929
|
distance = Math.max(1, Math.round(base * DEFAULT_SCROLL_VIEWPORT_RATIO));
|
|
873
930
|
}
|
|
@@ -886,30 +943,30 @@ Original error: ${lastRawMessage}`);
|
|
|
886
943
|
await this.inputDriver.delay(SCROLL_COMPLETE_DELAY);
|
|
887
944
|
return;
|
|
888
945
|
}
|
|
889
|
-
const
|
|
890
|
-
const
|
|
946
|
+
const detents = Math.min(LIBNUT_FALLBACK_MAX_DETENTS, Math.max(1, Math.ceil(distance / LIBNUT_FALLBACK_PIXELS_PER_DETENT)));
|
|
947
|
+
const directionUnit = {
|
|
891
948
|
up: [
|
|
892
949
|
0,
|
|
893
|
-
|
|
950
|
+
1
|
|
894
951
|
],
|
|
895
952
|
down: [
|
|
896
953
|
0,
|
|
897
|
-
-
|
|
954
|
+
-1
|
|
898
955
|
],
|
|
899
956
|
left: [
|
|
900
|
-
-
|
|
957
|
+
-1,
|
|
901
958
|
0
|
|
902
959
|
],
|
|
903
960
|
right: [
|
|
904
|
-
|
|
961
|
+
1,
|
|
905
962
|
0
|
|
906
963
|
]
|
|
907
964
|
};
|
|
908
|
-
const [
|
|
965
|
+
const [ux, uy] = directionUnit[direction] || [
|
|
909
966
|
0,
|
|
910
|
-
-
|
|
967
|
+
-1
|
|
911
968
|
];
|
|
912
|
-
this.inputDriver.
|
|
969
|
+
await this.inputDriver.emitScrollDetents(ux * LIBNUT_FALLBACK_DETENT_AMOUNT, uy * LIBNUT_FALLBACK_DETENT_AMOUNT, detents, LIBNUT_FALLBACK_TICK_DELAY_MS);
|
|
913
970
|
await this.inputDriver.delay(SCROLL_COMPLETE_DELAY);
|
|
914
971
|
return;
|
|
915
972
|
}
|
|
@@ -1114,27 +1171,6 @@ function createPlatformActions() {
|
|
|
1114
1171
|
})
|
|
1115
1172
|
};
|
|
1116
1173
|
}
|
|
1117
|
-
function normalizeRdpHost(host) {
|
|
1118
|
-
const trimmed = host.trim();
|
|
1119
|
-
if (trimmed.length >= 2 && trimmed.startsWith('[') && trimmed.endsWith(']') && trimmed.includes(':')) return trimmed.slice(1, -1);
|
|
1120
|
-
return trimmed;
|
|
1121
|
-
}
|
|
1122
|
-
function formatRdpHost(host) {
|
|
1123
|
-
const normalizedHost = normalizeRdpHost(host);
|
|
1124
|
-
return normalizedHost.includes(':') ? `[${normalizedHost}]` : normalizedHost;
|
|
1125
|
-
}
|
|
1126
|
-
function formatRdpServerAddress(host, port) {
|
|
1127
|
-
return `${formatRdpHost(host)}:${port}`;
|
|
1128
|
-
}
|
|
1129
|
-
function normalizeRdpConnectionConfig(config) {
|
|
1130
|
-
return {
|
|
1131
|
-
...config,
|
|
1132
|
-
host: normalizeRdpHost(config.host),
|
|
1133
|
-
...config.localAddress ? {
|
|
1134
|
-
localAddress: config.localAddress.trim()
|
|
1135
|
-
} : {}
|
|
1136
|
-
};
|
|
1137
|
-
}
|
|
1138
1174
|
const platformBinaryMap = {
|
|
1139
1175
|
darwin: {
|
|
1140
1176
|
directory: 'darwin',
|
|
@@ -1182,14 +1218,14 @@ function backend_client_define_property(obj, key, value) {
|
|
|
1182
1218
|
}
|
|
1183
1219
|
const debug = getDebug('rdp:backend');
|
|
1184
1220
|
const HELPER_SHUTDOWN_TIMEOUT_MS = 3000;
|
|
1185
|
-
const
|
|
1186
|
-
const MAX_STDERR_CHARS = 16384;
|
|
1221
|
+
const MAX_STDERR_LINES = 40;
|
|
1187
1222
|
class HelperProcessRDPBackendClient {
|
|
1188
1223
|
async connect(config) {
|
|
1189
1224
|
this.fatalHelperError = void 0;
|
|
1225
|
+
await this.ensureHelperStarted();
|
|
1190
1226
|
const response = await this.send({
|
|
1191
1227
|
type: 'connect',
|
|
1192
|
-
config
|
|
1228
|
+
config
|
|
1193
1229
|
});
|
|
1194
1230
|
if ('connected' !== response.type) throw new Error(`Expected connected response, got ${response.type}`);
|
|
1195
1231
|
this.connected = true;
|
|
@@ -1273,8 +1309,8 @@ class HelperProcessRDPBackendClient {
|
|
|
1273
1309
|
});
|
|
1274
1310
|
this.expectOk(response, 'clearInput');
|
|
1275
1311
|
}
|
|
1276
|
-
ensureHelperStarted() {
|
|
1277
|
-
if (this.child && null === this.child.exitCode) return
|
|
1312
|
+
async ensureHelperStarted() {
|
|
1313
|
+
if (this.child && null === this.child.exitCode) return;
|
|
1278
1314
|
const helperPath = this.resolveHelperPath();
|
|
1279
1315
|
debug('starting rdp helper', {
|
|
1280
1316
|
helperPath
|
|
@@ -1289,111 +1325,66 @@ class HelperProcessRDPBackendClient {
|
|
|
1289
1325
|
child.stdout.setEncoding('utf8');
|
|
1290
1326
|
child.stderr.setEncoding('utf8');
|
|
1291
1327
|
this.child = child;
|
|
1292
|
-
|
|
1293
|
-
path: helperPath,
|
|
1294
|
-
pid: child.pid,
|
|
1295
|
-
stderrBuffer: ''
|
|
1296
|
-
};
|
|
1297
|
-
this.helperDiagnostics.set(child, diagnostics);
|
|
1298
|
-
debug('started rdp helper', {
|
|
1299
|
-
helperPath,
|
|
1300
|
-
pid: child.pid
|
|
1301
|
-
});
|
|
1328
|
+
this.stderrLines.length = 0;
|
|
1302
1329
|
this.stdoutReader = createInterface({
|
|
1303
1330
|
input: child.stdout,
|
|
1304
1331
|
crlfDelay: 1 / 0
|
|
1305
1332
|
});
|
|
1306
|
-
this.
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
child.stderr.on('data', (chunk)=>{
|
|
1310
|
-
this.captureStderrChunk(chunk, diagnostics);
|
|
1311
|
-
});
|
|
1312
|
-
child.stdin.on('error', (error)=>{
|
|
1313
|
-
this.handleHelperStreamError(child, 'stdin', error, diagnostics);
|
|
1333
|
+
this.stderrReader = createInterface({
|
|
1334
|
+
input: child.stderr,
|
|
1335
|
+
crlfDelay: 1 / 0
|
|
1314
1336
|
});
|
|
1315
|
-
|
|
1316
|
-
this.
|
|
1337
|
+
this.stdoutReader.on('line', (line)=>{
|
|
1338
|
+
this.handleStdoutLine(line);
|
|
1317
1339
|
});
|
|
1318
|
-
|
|
1319
|
-
this.
|
|
1340
|
+
this.stderrReader.on('line', (line)=>{
|
|
1341
|
+
this.captureStderrLine(line);
|
|
1320
1342
|
});
|
|
1321
1343
|
child.on('exit', (code, signal)=>{
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
pid: diagnostics.pid,
|
|
1329
|
-
code,
|
|
1330
|
-
signal
|
|
1331
|
-
});
|
|
1332
|
-
const helperError = this.createHelperError(`RDP helper exited unexpectedly (code=${code}, signal=${signal})`, void 0, diagnostics);
|
|
1333
|
-
this.rejectPendingForChild(child, helperError);
|
|
1334
|
-
if (this.child === child) {
|
|
1335
|
-
this.connected = false;
|
|
1336
|
-
this.fatalHelperError = helperError;
|
|
1337
|
-
this.disposeReaders();
|
|
1338
|
-
this.child = void 0;
|
|
1339
|
-
}
|
|
1344
|
+
this.connected = false;
|
|
1345
|
+
const error = this.createHelperError(`RDP helper exited unexpectedly (code=${code}, signal=${signal})`);
|
|
1346
|
+
this.fatalHelperError = error;
|
|
1347
|
+
this.rejectPending(error);
|
|
1348
|
+
this.disposeReaders();
|
|
1349
|
+
this.child = void 0;
|
|
1340
1350
|
});
|
|
1341
1351
|
child.on('error', (error)=>{
|
|
1342
|
-
|
|
1343
|
-
this.
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
this.child = void 0;
|
|
1349
|
-
}
|
|
1352
|
+
this.connected = false;
|
|
1353
|
+
const helperError = this.createHelperError(`Failed to start RDP helper: ${error.message}`);
|
|
1354
|
+
this.fatalHelperError = helperError;
|
|
1355
|
+
this.rejectPending(helperError);
|
|
1356
|
+
this.disposeReaders();
|
|
1357
|
+
this.child = void 0;
|
|
1350
1358
|
});
|
|
1351
|
-
return child;
|
|
1352
|
-
}
|
|
1353
|
-
handleHelperStreamError(child, streamName, error, diagnostics) {
|
|
1354
|
-
const nodeError = error;
|
|
1355
|
-
const helperError = this.createHelperError(`RDP helper ${streamName} stream error: ${error.message}`, nodeError.code, diagnostics);
|
|
1356
|
-
this.rejectPendingForChild(child, helperError);
|
|
1357
|
-
if (this.child !== child) return;
|
|
1358
|
-
this.connected = false;
|
|
1359
|
-
this.fatalHelperError = helperError;
|
|
1360
|
-
if (null === child.exitCode) child.kill('SIGTERM');
|
|
1361
|
-
this.child = void 0;
|
|
1362
1359
|
}
|
|
1363
|
-
handleStdoutLine(
|
|
1360
|
+
handleStdoutLine(line) {
|
|
1364
1361
|
if (!line.trim()) return;
|
|
1365
1362
|
let parsed;
|
|
1366
1363
|
try {
|
|
1367
1364
|
parsed = JSON.parse(line);
|
|
1368
1365
|
} catch (error) {
|
|
1369
|
-
const protocolError = this.createHelperError(`RDP helper emitted malformed JSON: ${line}
|
|
1370
|
-
this.
|
|
1371
|
-
|
|
1366
|
+
const protocolError = this.createHelperError(`RDP helper emitted malformed JSON: ${line}`);
|
|
1367
|
+
this.rejectPending(protocolError);
|
|
1368
|
+
this.shutdownHelper(protocolError);
|
|
1372
1369
|
return;
|
|
1373
1370
|
}
|
|
1374
1371
|
const pending = this.pending.get(parsed.id);
|
|
1375
1372
|
if (!pending) return void debug('dropping response for unknown request id', parsed);
|
|
1376
1373
|
this.pending.delete(parsed.id);
|
|
1377
1374
|
if (parsed.ok) return void pending.resolve(parsed.payload);
|
|
1378
|
-
pending.reject(this.createHelperError(parsed.error.message, parsed.error.code
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
helperPath: diagnostics.path,
|
|
1385
|
-
pid: diagnostics.pid,
|
|
1386
|
-
stderr: text.trim()
|
|
1387
|
-
});
|
|
1388
|
-
diagnostics.stderrBuffer += text;
|
|
1389
|
-
if (diagnostics.stderrBuffer.length > MAX_STDERR_CHARS) diagnostics.stderrBuffer = diagnostics.stderrBuffer.slice(-MAX_STDERR_CHARS);
|
|
1375
|
+
pending.reject(this.createHelperError(parsed.error.message, parsed.error.code));
|
|
1376
|
+
}
|
|
1377
|
+
captureStderrLine(line) {
|
|
1378
|
+
if (!line.trim()) return;
|
|
1379
|
+
this.stderrLines.push(line);
|
|
1380
|
+
if (this.stderrLines.length > MAX_STDERR_LINES) this.stderrLines.shift();
|
|
1390
1381
|
}
|
|
1391
1382
|
async send(payload) {
|
|
1392
1383
|
if ('connect' !== payload.type && this.fatalHelperError && (!this.child || null !== this.child.exitCode)) throw this.fatalHelperError;
|
|
1393
|
-
|
|
1394
|
-
|
|
1384
|
+
await this.ensureHelperStarted();
|
|
1385
|
+
const child = this.child;
|
|
1386
|
+
if (!child || null !== child.exitCode) throw this.createHelperError('RDP helper is not running');
|
|
1395
1387
|
const id = `req-${++this.nextRequestId}`;
|
|
1396
|
-
const diagnostics = this.helperDiagnostics.get(child);
|
|
1397
1388
|
const request = {
|
|
1398
1389
|
id,
|
|
1399
1390
|
payload
|
|
@@ -1401,17 +1392,12 @@ class HelperProcessRDPBackendClient {
|
|
|
1401
1392
|
return new Promise((resolve, reject)=>{
|
|
1402
1393
|
this.pending.set(id, {
|
|
1403
1394
|
resolve,
|
|
1404
|
-
reject
|
|
1405
|
-
child
|
|
1395
|
+
reject
|
|
1406
1396
|
});
|
|
1407
1397
|
child.stdin.write(`${JSON.stringify(request)}\n`, (error)=>{
|
|
1408
1398
|
if (!error) return;
|
|
1409
1399
|
this.pending.delete(id);
|
|
1410
|
-
|
|
1411
|
-
const timer = setTimeout(()=>{
|
|
1412
|
-
reject(this.createHelperError(`Failed to send ${payload.type} request to RDP helper: ${error.message}`, nodeError.code, diagnostics));
|
|
1413
|
-
}, HELPER_WRITE_ERROR_DIAGNOSTIC_DELAY_MS);
|
|
1414
|
-
timer.unref?.();
|
|
1400
|
+
reject(this.createHelperError(`Failed to send ${payload.type} request to RDP helper: ${error.message}`));
|
|
1415
1401
|
});
|
|
1416
1402
|
});
|
|
1417
1403
|
}
|
|
@@ -1422,28 +1408,18 @@ class HelperProcessRDPBackendClient {
|
|
|
1422
1408
|
for (const { reject } of this.pending.values())reject(error);
|
|
1423
1409
|
this.pending.clear();
|
|
1424
1410
|
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
createHelperError(message, code, diagnostics) {
|
|
1432
|
-
const diagnosticParts = [
|
|
1433
|
-
diagnostics?.path ? `path=${diagnostics.path}` : void 0,
|
|
1434
|
-
'number' == typeof diagnostics?.pid ? `pid=${diagnostics.pid}` : void 0,
|
|
1435
|
-
diagnostics?.exit ? `exitCode=${diagnostics.exit.code}, signal=${diagnostics.exit.signal}` : void 0
|
|
1436
|
-
].filter(Boolean);
|
|
1437
|
-
const diagnosticsSuffix = diagnosticParts.length > 0 ? `\nHelper diagnostics: ${diagnosticParts.join(', ')}` : '';
|
|
1438
|
-
const stderrSummary = diagnostics?.stderrBuffer.trim();
|
|
1439
|
-
const stderrSuffix = stderrSummary ? `\nHelper stderr:\n${stderrSummary}` : '';
|
|
1440
|
-
const error = new Error(`${message}${diagnosticsSuffix}${stderrSuffix}`);
|
|
1411
|
+
createHelperError(message, code) {
|
|
1412
|
+
const stderrSummary = this.stderrLines.join('\n').trim();
|
|
1413
|
+
const suffix = stderrSummary ? `\nHelper stderr:\n${stderrSummary}` : '';
|
|
1414
|
+
const error = new Error(`${message}${suffix}`);
|
|
1441
1415
|
if (code) error.name = code;
|
|
1442
1416
|
return error;
|
|
1443
1417
|
}
|
|
1444
1418
|
disposeReaders() {
|
|
1445
1419
|
this.stdoutReader?.close();
|
|
1420
|
+
this.stderrReader?.close();
|
|
1446
1421
|
this.stdoutReader = void 0;
|
|
1422
|
+
this.stderrReader = void 0;
|
|
1447
1423
|
}
|
|
1448
1424
|
async shutdownHelper(rootError) {
|
|
1449
1425
|
const child = this.child;
|
|
@@ -1478,8 +1454,9 @@ class HelperProcessRDPBackendClient {
|
|
|
1478
1454
|
backend_client_define_property(this, "resolveHelperPath", void 0);
|
|
1479
1455
|
backend_client_define_property(this, "child", void 0);
|
|
1480
1456
|
backend_client_define_property(this, "stdoutReader", void 0);
|
|
1457
|
+
backend_client_define_property(this, "stderrReader", void 0);
|
|
1481
1458
|
backend_client_define_property(this, "pending", new Map());
|
|
1482
|
-
backend_client_define_property(this, "
|
|
1459
|
+
backend_client_define_property(this, "stderrLines", []);
|
|
1483
1460
|
backend_client_define_property(this, "nextRequestId", 0);
|
|
1484
1461
|
backend_client_define_property(this, "connected", false);
|
|
1485
1462
|
backend_client_define_property(this, "fatalHelperError", void 0);
|
|
@@ -1522,10 +1499,9 @@ const DEFAULT_SCROLL_STEP_AMOUNT = 120;
|
|
|
1522
1499
|
class RDPDevice {
|
|
1523
1500
|
describe() {
|
|
1524
1501
|
const port = this.options.port || 3389;
|
|
1525
|
-
const server = formatRdpServerAddress(this.options.host, port);
|
|
1526
1502
|
const username = this.options.username ? ` as ${this.options.username}` : '';
|
|
1527
1503
|
const session = this.connectionInfo?.sessionId ? ` [session ${this.connectionInfo.sessionId}]` : '';
|
|
1528
|
-
return `RDP Device ${
|
|
1504
|
+
return `RDP Device ${this.options.host}:${port}${username}${session}`;
|
|
1529
1505
|
}
|
|
1530
1506
|
async connect() {
|
|
1531
1507
|
this.throwIfDestroyed();
|
|
@@ -1565,11 +1541,10 @@ class RDPDevice {
|
|
|
1565
1541
|
call: async ()=>{
|
|
1566
1542
|
this.assertConnected();
|
|
1567
1543
|
const size = await this.size();
|
|
1568
|
-
const server = this.connectionInfo?.server || formatRdpServerAddress(this.options.host, this.options.port || 3389);
|
|
1569
1544
|
return [
|
|
1570
1545
|
{
|
|
1571
1546
|
id: this.connectionInfo?.sessionId || this.options.host,
|
|
1572
|
-
name: `RDP ${server} (${size.width}x${size.height})`,
|
|
1547
|
+
name: `RDP ${this.connectionInfo?.server || this.options.host} (${size.width}x${size.height})`,
|
|
1573
1548
|
primary: true
|
|
1574
1549
|
}
|
|
1575
1550
|
];
|
|
@@ -1767,12 +1742,11 @@ class RDPDevice {
|
|
|
1767
1742
|
}
|
|
1768
1743
|
}
|
|
1769
1744
|
});
|
|
1770
|
-
const normalizedOptions = normalizeRdpConnectionConfig(options);
|
|
1771
1745
|
this.options = {
|
|
1772
1746
|
port: 3389,
|
|
1773
1747
|
securityProtocol: 'auto',
|
|
1774
1748
|
ignoreCertificate: false,
|
|
1775
|
-
...
|
|
1749
|
+
...options
|
|
1776
1750
|
};
|
|
1777
1751
|
this.backend = options.backend || createDefaultRDPBackendClient();
|
|
1778
1752
|
}
|
|
@@ -1795,7 +1769,6 @@ function createRDPComputerDevice(opts) {
|
|
|
1795
1769
|
username: opts.username,
|
|
1796
1770
|
password: opts.password,
|
|
1797
1771
|
domain: opts.domain,
|
|
1798
|
-
localAddress: opts.localAddress,
|
|
1799
1772
|
adminSession: opts.adminSession,
|
|
1800
1773
|
ignoreCertificate: opts.ignoreCertificate,
|
|
1801
1774
|
securityProtocol: opts.securityProtocol,
|
|
@@ -1841,7 +1814,6 @@ const computerInitArgShape = {
|
|
|
1841
1814
|
username: z.string().optional().describe('RDP username. Requires host.'),
|
|
1842
1815
|
password: z.string().optional().describe('RDP password. Requires host. Prefer setting via environment or a secrets manager.'),
|
|
1843
1816
|
domain: z.string().optional().describe('RDP domain. Requires host.'),
|
|
1844
|
-
localAddress: z.string().optional().describe('Local source IP address for the RDP TCP connection. Requires host.'),
|
|
1845
1817
|
adminSession: z.boolean().optional().describe('Attach to the RDP admin/console session. Requires host.'),
|
|
1846
1818
|
ignoreCertificate: z.boolean().optional().describe('Skip TLS certificate validation. Requires host.'),
|
|
1847
1819
|
securityProtocol: z["enum"](RDP_SECURITY_PROTOCOLS).optional().describe('RDP security protocol negotiation (default auto). Requires host.'),
|
|
@@ -1852,11 +1824,10 @@ function adaptComputerInitArgs(extracted) {
|
|
|
1852
1824
|
if (!extracted || 0 === Object.keys(extracted).length) return;
|
|
1853
1825
|
if (extracted.host) {
|
|
1854
1826
|
const { displayId: _d, headless: _h, ...rdpFields } = extracted;
|
|
1855
|
-
const host = normalizeRdpHost(extracted.host);
|
|
1856
1827
|
return {
|
|
1857
1828
|
mode: 'rdp',
|
|
1858
1829
|
...rdpFields,
|
|
1859
|
-
host
|
|
1830
|
+
host: extracted.host
|
|
1860
1831
|
};
|
|
1861
1832
|
}
|
|
1862
1833
|
return {
|
|
@@ -1872,15 +1843,15 @@ function shouldRetargetAgent(opts) {
|
|
|
1872
1843
|
}
|
|
1873
1844
|
function describeConnectTarget(opts) {
|
|
1874
1845
|
if (opts?.mode === 'rdp') {
|
|
1875
|
-
const
|
|
1846
|
+
const portSuffix = opts.port ? `:${opts.port}` : '';
|
|
1876
1847
|
const userSuffix = opts.username ? ` as ${opts.username}` : '';
|
|
1877
|
-
return ` via RDP (${
|
|
1848
|
+
return ` via RDP (${opts.host}${portSuffix}${userSuffix})`;
|
|
1878
1849
|
}
|
|
1879
1850
|
if (opts?.mode === 'local' && opts.displayId) return ` (Display: ${opts.displayId})`;
|
|
1880
1851
|
return ' (Primary display)';
|
|
1881
1852
|
}
|
|
1882
1853
|
function getCliReportSessionTarget(opts) {
|
|
1883
|
-
if (opts?.mode === 'rdp') return `rdp:${
|
|
1854
|
+
if (opts?.mode === 'rdp') return `rdp:${opts.host}`;
|
|
1884
1855
|
if (opts?.mode === 'local' && opts.displayId) return opts.displayId;
|
|
1885
1856
|
return 'primary';
|
|
1886
1857
|
}
|
|
@@ -1932,7 +1903,7 @@ class ComputerMidsceneTools extends BaseMidsceneTools {
|
|
|
1932
1903
|
return [
|
|
1933
1904
|
{
|
|
1934
1905
|
name: 'computer_connect',
|
|
1935
|
-
description: "Connect to a computer desktop. Default (local) mode controls the local machine; pass displayId to target a specific local display (see computer_list_displays). Pass host to switch to RDP mode and connect to a remote Windows desktop via the RDP helper binary. RDP-related options (port/username/password/domain/
|
|
1906
|
+
description: "Connect to a computer desktop. Default (local) mode controls the local machine; pass displayId to target a specific local display (see computer_list_displays). Pass host to switch to RDP mode and connect to a remote Windows desktop via the RDP helper binary. RDP-related options (port/username/password/domain/securityProtocol/ignoreCertificate/adminSession/desktopWidth/desktopHeight) only take effect when host is set.",
|
|
1936
1907
|
schema: this.getAgentInitArgSchema(),
|
|
1937
1908
|
cli: this.getAgentInitArgCliMetadata(),
|
|
1938
1909
|
handler: async (args)=>{
|
|
@@ -1998,7 +1969,7 @@ class ComputerMidsceneTools extends BaseMidsceneTools {
|
|
|
1998
1969
|
const tools = new ComputerMidsceneTools();
|
|
1999
1970
|
runToolsCLI(tools, 'midscene-computer', {
|
|
2000
1971
|
stripPrefix: 'computer_',
|
|
2001
|
-
version: "1.9.3-beta-
|
|
1972
|
+
version: "1.9.3-beta-20260610071913.0",
|
|
2002
1973
|
extraCommands: createReportCliCommands()
|
|
2003
1974
|
}).catch((e)=>{
|
|
2004
1975
|
process.exit(reportCLIError(e));
|