@midscene/computer 1.8.0 → 1.8.1-beta-20260513084557.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/dist/lib/cli.js CHANGED
@@ -437,7 +437,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
437
437
  }
438
438
  async healthCheck() {
439
439
  console.log('[HealthCheck] Starting health check...');
440
- console.log("[HealthCheck] @midscene/computer v1.8.0");
440
+ console.log("[HealthCheck] @midscene/computer v1.8.1-beta-20260513084557.0");
441
441
  console.log('[HealthCheck] Taking screenshot...');
442
442
  const screenshotTimeout = 15000;
443
443
  let timeoutId;
@@ -836,10 +836,600 @@ function createPlatformActions() {
836
836
  })
837
837
  };
838
838
  }
839
- require("node:events");
840
- require("node:readline");
841
- (0, logger_namespaceObject.getDebug)('rdp:backend');
842
- (0, logger_namespaceObject.getDebug)('rdp:device');
839
+ const external_node_events_namespaceObject = require("node:events");
840
+ const external_node_readline_namespaceObject = require("node:readline");
841
+ const platformBinaryMap = {
842
+ darwin: {
843
+ directory: 'darwin',
844
+ fileName: 'rdp-helper'
845
+ },
846
+ linux: {
847
+ directory: 'linux',
848
+ fileName: 'rdp-helper'
849
+ },
850
+ win32: {
851
+ directory: 'win32',
852
+ fileName: 'rdp-helper.exe'
853
+ }
854
+ };
855
+ function getPlatformBinary(platform) {
856
+ if (platform in platformBinaryMap) return platformBinaryMap[platform];
857
+ }
858
+ function currentDirname() {
859
+ return __dirname;
860
+ }
861
+ function getRdpHelperBinaryPath() {
862
+ const platformBinary = getPlatformBinary(process.platform);
863
+ if (!platformBinary) throw new Error(`@midscene/computer RDP helper does not support platform ${process.platform}`);
864
+ const hereDir = currentDirname();
865
+ const candidateRoots = [
866
+ (0, external_node_path_namespaceObject.resolve)(hereDir, '../..'),
867
+ (0, external_node_path_namespaceObject.resolve)(hereDir, '../../..')
868
+ ];
869
+ for (const root of candidateRoots){
870
+ const binaryPath = (0, external_node_path_namespaceObject.resolve)(root, 'bin', platformBinary.directory, platformBinary.fileName);
871
+ if ((0, external_node_fs_namespaceObject.existsSync)(binaryPath)) return binaryPath;
872
+ }
873
+ throw new Error(`RDP helper binary not found for ${process.platform}. Run \`pnpm --filter @midscene/computer run build:native\` first.`);
874
+ }
875
+ function backend_client_define_property(obj, key, value) {
876
+ if (key in obj) Object.defineProperty(obj, key, {
877
+ value: value,
878
+ enumerable: true,
879
+ configurable: true,
880
+ writable: true
881
+ });
882
+ else obj[key] = value;
883
+ return obj;
884
+ }
885
+ const debug = (0, logger_namespaceObject.getDebug)('rdp:backend');
886
+ const HELPER_SHUTDOWN_TIMEOUT_MS = 3000;
887
+ const MAX_STDERR_LINES = 40;
888
+ class HelperProcessRDPBackendClient {
889
+ async connect(config) {
890
+ this.fatalHelperError = void 0;
891
+ await this.ensureHelperStarted();
892
+ const response = await this.send({
893
+ type: 'connect',
894
+ config
895
+ });
896
+ if ('connected' !== response.type) throw new Error(`Expected connected response, got ${response.type}`);
897
+ this.connected = true;
898
+ this.fatalHelperError = void 0;
899
+ return response.info;
900
+ }
901
+ async disconnect() {
902
+ const child = this.child;
903
+ if (!child) return;
904
+ let disconnectError;
905
+ if (this.connected && null === child.exitCode) try {
906
+ const response = await this.send({
907
+ type: 'disconnect'
908
+ });
909
+ this.expectOk(response, 'disconnect');
910
+ } catch (error) {
911
+ disconnectError = error instanceof Error ? error : new Error(String(error));
912
+ }
913
+ this.connected = false;
914
+ this.fatalHelperError = void 0;
915
+ await this.shutdownHelper();
916
+ if (disconnectError && !/RDP helper exited unexpectedly|RDP helper is not running|RDP helper shut down/u.test(disconnectError.message)) throw disconnectError;
917
+ }
918
+ async screenshotBase64() {
919
+ const response = await this.send({
920
+ type: 'screenshot'
921
+ });
922
+ if ('screenshot' !== response.type) throw new Error(`Expected screenshot response, got ${response.type}`);
923
+ return response.base64;
924
+ }
925
+ async size() {
926
+ const response = await this.send({
927
+ type: 'size'
928
+ });
929
+ if ('size' !== response.type) throw new Error(`Expected size response, got ${response.type}`);
930
+ return response.size;
931
+ }
932
+ async mouseMove(x, y) {
933
+ const response = await this.send({
934
+ type: 'mouseMove',
935
+ x,
936
+ y
937
+ });
938
+ this.expectOk(response, 'mouseMove');
939
+ }
940
+ async mouseButton(button, action) {
941
+ const response = await this.send({
942
+ type: 'mouseButton',
943
+ button,
944
+ action
945
+ });
946
+ this.expectOk(response, 'mouseButton');
947
+ }
948
+ async wheel(direction, amount, x, y) {
949
+ const response = await this.send({
950
+ type: 'wheel',
951
+ direction,
952
+ amount,
953
+ x,
954
+ y
955
+ });
956
+ this.expectOk(response, 'wheel');
957
+ }
958
+ async keyPress(keyName) {
959
+ const response = await this.send({
960
+ type: 'keyPress',
961
+ keyName
962
+ });
963
+ this.expectOk(response, 'keyPress');
964
+ }
965
+ async typeText(text) {
966
+ const response = await this.send({
967
+ type: 'typeText',
968
+ text
969
+ });
970
+ this.expectOk(response, 'typeText');
971
+ }
972
+ async clearInput() {
973
+ const response = await this.send({
974
+ type: 'clearInput'
975
+ });
976
+ this.expectOk(response, 'clearInput');
977
+ }
978
+ async ensureHelperStarted() {
979
+ if (this.child && null === this.child.exitCode) return;
980
+ const helperPath = this.resolveHelperPath();
981
+ debug('starting rdp helper', {
982
+ helperPath
983
+ });
984
+ const child = this.spawnFn(helperPath, [], {
985
+ stdio: [
986
+ 'pipe',
987
+ 'pipe',
988
+ 'pipe'
989
+ ]
990
+ });
991
+ child.stdout.setEncoding('utf8');
992
+ child.stderr.setEncoding('utf8');
993
+ this.child = child;
994
+ this.stderrLines.length = 0;
995
+ this.stdoutReader = (0, external_node_readline_namespaceObject.createInterface)({
996
+ input: child.stdout,
997
+ crlfDelay: 1 / 0
998
+ });
999
+ this.stderrReader = (0, external_node_readline_namespaceObject.createInterface)({
1000
+ input: child.stderr,
1001
+ crlfDelay: 1 / 0
1002
+ });
1003
+ this.stdoutReader.on('line', (line)=>{
1004
+ this.handleStdoutLine(line);
1005
+ });
1006
+ this.stderrReader.on('line', (line)=>{
1007
+ this.captureStderrLine(line);
1008
+ });
1009
+ child.on('exit', (code, signal)=>{
1010
+ this.connected = false;
1011
+ const error = this.createHelperError(`RDP helper exited unexpectedly (code=${code}, signal=${signal})`);
1012
+ this.fatalHelperError = error;
1013
+ this.rejectPending(error);
1014
+ this.disposeReaders();
1015
+ this.child = void 0;
1016
+ });
1017
+ child.on('error', (error)=>{
1018
+ this.connected = false;
1019
+ const helperError = this.createHelperError(`Failed to start RDP helper: ${error.message}`);
1020
+ this.fatalHelperError = helperError;
1021
+ this.rejectPending(helperError);
1022
+ this.disposeReaders();
1023
+ this.child = void 0;
1024
+ });
1025
+ }
1026
+ handleStdoutLine(line) {
1027
+ if (!line.trim()) return;
1028
+ let parsed;
1029
+ try {
1030
+ parsed = JSON.parse(line);
1031
+ } catch (error) {
1032
+ const protocolError = this.createHelperError(`RDP helper emitted malformed JSON: ${line}`);
1033
+ this.rejectPending(protocolError);
1034
+ this.shutdownHelper(protocolError);
1035
+ return;
1036
+ }
1037
+ const pending = this.pending.get(parsed.id);
1038
+ if (!pending) return void debug('dropping response for unknown request id', parsed);
1039
+ this.pending.delete(parsed.id);
1040
+ if (parsed.ok) return void pending.resolve(parsed.payload);
1041
+ pending.reject(this.createHelperError(parsed.error.message, parsed.error.code));
1042
+ }
1043
+ captureStderrLine(line) {
1044
+ if (!line.trim()) return;
1045
+ this.stderrLines.push(line);
1046
+ if (this.stderrLines.length > MAX_STDERR_LINES) this.stderrLines.shift();
1047
+ }
1048
+ async send(payload) {
1049
+ if ('connect' !== payload.type && this.fatalHelperError && (!this.child || null !== this.child.exitCode)) throw this.fatalHelperError;
1050
+ await this.ensureHelperStarted();
1051
+ const child = this.child;
1052
+ if (!child || null !== child.exitCode) throw this.createHelperError('RDP helper is not running');
1053
+ const id = `req-${++this.nextRequestId}`;
1054
+ const request = {
1055
+ id,
1056
+ payload
1057
+ };
1058
+ return new Promise((resolve, reject)=>{
1059
+ this.pending.set(id, {
1060
+ resolve,
1061
+ reject
1062
+ });
1063
+ child.stdin.write(`${JSON.stringify(request)}\n`, (error)=>{
1064
+ if (!error) return;
1065
+ this.pending.delete(id);
1066
+ reject(this.createHelperError(`Failed to send ${payload.type} request to RDP helper: ${error.message}`));
1067
+ });
1068
+ });
1069
+ }
1070
+ expectOk(response, actionName) {
1071
+ if ('ok' !== response.type) throw new Error(`Expected ok response for ${actionName}, got ${response.type}`);
1072
+ }
1073
+ rejectPending(error) {
1074
+ for (const { reject } of this.pending.values())reject(error);
1075
+ this.pending.clear();
1076
+ }
1077
+ createHelperError(message, code) {
1078
+ const stderrSummary = this.stderrLines.join('\n').trim();
1079
+ const suffix = stderrSummary ? `\nHelper stderr:\n${stderrSummary}` : '';
1080
+ const error = new Error(`${message}${suffix}`);
1081
+ if (code) error.name = code;
1082
+ return error;
1083
+ }
1084
+ disposeReaders() {
1085
+ this.stdoutReader?.close();
1086
+ this.stderrReader?.close();
1087
+ this.stdoutReader = void 0;
1088
+ this.stderrReader = void 0;
1089
+ }
1090
+ async shutdownHelper(rootError) {
1091
+ const child = this.child;
1092
+ this.child = void 0;
1093
+ this.disposeReaders();
1094
+ if (!child) return;
1095
+ this.rejectPending(rootError || this.createHelperError('RDP helper shut down'));
1096
+ if (null !== child.exitCode) return;
1097
+ child.stdin.end();
1098
+ const exited = Promise.race([
1099
+ (0, external_node_events_namespaceObject.once)(child, 'exit'),
1100
+ new Promise((resolve)=>{
1101
+ setTimeout(()=>resolve('timeout'), HELPER_SHUTDOWN_TIMEOUT_MS);
1102
+ })
1103
+ ]);
1104
+ const result = await exited;
1105
+ if ('timeout' !== result) return;
1106
+ child.kill('SIGTERM');
1107
+ const terminated = Promise.race([
1108
+ (0, external_node_events_namespaceObject.once)(child, 'exit'),
1109
+ new Promise((resolve)=>{
1110
+ setTimeout(()=>resolve('timeout'), HELPER_SHUTDOWN_TIMEOUT_MS);
1111
+ })
1112
+ ]);
1113
+ const terminateResult = await terminated;
1114
+ if ('timeout' !== terminateResult) return;
1115
+ child.kill('SIGKILL');
1116
+ await (0, external_node_events_namespaceObject.once)(child, 'exit');
1117
+ }
1118
+ constructor(options){
1119
+ backend_client_define_property(this, "spawnFn", void 0);
1120
+ backend_client_define_property(this, "resolveHelperPath", void 0);
1121
+ backend_client_define_property(this, "child", void 0);
1122
+ backend_client_define_property(this, "stdoutReader", void 0);
1123
+ backend_client_define_property(this, "stderrReader", void 0);
1124
+ backend_client_define_property(this, "pending", new Map());
1125
+ backend_client_define_property(this, "stderrLines", []);
1126
+ backend_client_define_property(this, "nextRequestId", 0);
1127
+ backend_client_define_property(this, "connected", false);
1128
+ backend_client_define_property(this, "fatalHelperError", void 0);
1129
+ this.spawnFn = options?.spawnFn || external_node_child_process_namespaceObject.spawn;
1130
+ const overridePath = options?.helperPath;
1131
+ this.resolveHelperPath = overridePath ? ()=>overridePath : getRdpHelperBinaryPath;
1132
+ }
1133
+ }
1134
+ function createDefaultRDPBackendClient() {
1135
+ return new HelperProcessRDPBackendClient();
1136
+ }
1137
+ function device_define_property(obj, key, value) {
1138
+ if (key in obj) Object.defineProperty(obj, key, {
1139
+ value: value,
1140
+ enumerable: true,
1141
+ configurable: true,
1142
+ writable: true
1143
+ });
1144
+ else obj[key] = value;
1145
+ return obj;
1146
+ }
1147
+ const device_debug = (0, logger_namespaceObject.getDebug)('rdp:device');
1148
+ const device_SMOOTH_MOVE_STEPS_TAP = 8;
1149
+ const device_SMOOTH_MOVE_STEPS_MOUSE_MOVE = 10;
1150
+ const SMOOTH_MOVE_STEPS_DRAG = 12;
1151
+ const device_SMOOTH_MOVE_DELAY_TAP = 8;
1152
+ const device_SMOOTH_MOVE_DELAY_MOUSE_MOVE = 10;
1153
+ const SMOOTH_MOVE_DELAY_DRAG = 10;
1154
+ const device_MOUSE_MOVE_EFFECT_WAIT = 300;
1155
+ const device_CLICK_HOLD_DURATION = 50;
1156
+ const DRAG_HOLD_DURATION = 100;
1157
+ const device_INPUT_FOCUS_DELAY = 300;
1158
+ const device_INPUT_CLEAR_DELAY = 150;
1159
+ const device_SCROLL_STEP_DELAY = 100;
1160
+ const device_SCROLL_COMPLETE_DELAY = 500;
1161
+ const DEFAULT_SCROLL_DISTANCE = 480;
1162
+ const device_EDGE_SCROLL_STEPS = 10;
1163
+ const DEFAULT_SCROLL_STEP_AMOUNT = 120;
1164
+ class RDPDevice {
1165
+ describe() {
1166
+ const port = this.options.port || 3389;
1167
+ const username = this.options.username ? ` as ${this.options.username}` : '';
1168
+ const session = this.connectionInfo?.sessionId ? ` [session ${this.connectionInfo.sessionId}]` : '';
1169
+ return `RDP Device ${this.options.host}:${port}${username}${session}`;
1170
+ }
1171
+ async connect() {
1172
+ this.throwIfDestroyed();
1173
+ device_debug('connecting to rdp backend', {
1174
+ host: this.options.host,
1175
+ port: this.options.port,
1176
+ username: this.options.username
1177
+ });
1178
+ this.connectionInfo = await this.backend.connect(this.options);
1179
+ this.cursorPosition = [
1180
+ Math.round(this.connectionInfo.size.width / 2),
1181
+ Math.round(this.connectionInfo.size.height / 2)
1182
+ ];
1183
+ }
1184
+ async screenshotBase64() {
1185
+ this.assertConnected();
1186
+ return this.backend.screenshotBase64();
1187
+ }
1188
+ async size() {
1189
+ this.assertConnected();
1190
+ return this.backend.size();
1191
+ }
1192
+ async destroy() {
1193
+ if (this.destroyed) return;
1194
+ this.destroyed = true;
1195
+ this.connectionInfo = void 0;
1196
+ this.cursorPosition = void 0;
1197
+ await this.backend.disconnect();
1198
+ }
1199
+ actionSpace() {
1200
+ const defaultActions = [
1201
+ (0, device_namespaceObject.defineActionTap)(async ({ locate })=>{
1202
+ const element = this.requireLocate(locate, 'tap');
1203
+ await this.moveToElement(element, {
1204
+ steps: device_SMOOTH_MOVE_STEPS_TAP,
1205
+ stepDelayMs: device_SMOOTH_MOVE_DELAY_TAP
1206
+ });
1207
+ await this.backend.mouseButton('left', 'down');
1208
+ await (0, utils_namespaceObject.sleep)(device_CLICK_HOLD_DURATION);
1209
+ await this.backend.mouseButton('left', 'up');
1210
+ }),
1211
+ (0, device_namespaceObject.defineActionDoubleClick)(async ({ locate })=>{
1212
+ const element = this.requireLocate(locate, 'double click');
1213
+ await this.moveToElement(element, {
1214
+ steps: device_SMOOTH_MOVE_STEPS_TAP,
1215
+ stepDelayMs: device_SMOOTH_MOVE_DELAY_TAP
1216
+ });
1217
+ await this.backend.mouseButton('left', 'doubleClick');
1218
+ }),
1219
+ (0, device_namespaceObject.defineActionRightClick)(async ({ locate })=>{
1220
+ const element = this.requireLocate(locate, 'right click');
1221
+ await this.moveToElement(element, {
1222
+ steps: device_SMOOTH_MOVE_STEPS_TAP,
1223
+ stepDelayMs: device_SMOOTH_MOVE_DELAY_TAP
1224
+ });
1225
+ await this.backend.mouseButton('right', 'click');
1226
+ }),
1227
+ (0, device_namespaceObject.defineActionHover)(async ({ locate })=>{
1228
+ const element = this.requireLocate(locate, 'hover');
1229
+ await this.moveToElement(element, {
1230
+ steps: device_SMOOTH_MOVE_STEPS_MOUSE_MOVE,
1231
+ stepDelayMs: device_SMOOTH_MOVE_DELAY_MOUSE_MOVE,
1232
+ settleDelayMs: device_MOUSE_MOVE_EFFECT_WAIT
1233
+ });
1234
+ }),
1235
+ (0, device_namespaceObject.defineActionInput)(async (param)=>{
1236
+ this.assertConnected();
1237
+ if (param.locate) {
1238
+ await this.moveToElement(param.locate, {
1239
+ steps: device_SMOOTH_MOVE_STEPS_TAP,
1240
+ stepDelayMs: device_SMOOTH_MOVE_DELAY_TAP
1241
+ });
1242
+ await this.backend.mouseButton('left', 'click');
1243
+ await (0, utils_namespaceObject.sleep)(device_INPUT_FOCUS_DELAY);
1244
+ }
1245
+ if ('typeOnly' !== param.mode) {
1246
+ await this.clearInput();
1247
+ await (0, utils_namespaceObject.sleep)(device_INPUT_CLEAR_DELAY);
1248
+ }
1249
+ if ('clear' === param.mode) return;
1250
+ if (param.value) await this.backend.typeText(param.value);
1251
+ }),
1252
+ (0, device_namespaceObject.defineActionClearInput)(async ({ locate })=>{
1253
+ this.assertConnected();
1254
+ if (locate) {
1255
+ await this.moveToElement(locate, {
1256
+ steps: device_SMOOTH_MOVE_STEPS_TAP,
1257
+ stepDelayMs: device_SMOOTH_MOVE_DELAY_TAP
1258
+ });
1259
+ await this.backend.mouseButton('left', 'click');
1260
+ await (0, utils_namespaceObject.sleep)(device_INPUT_FOCUS_DELAY);
1261
+ }
1262
+ await this.clearInput();
1263
+ await (0, utils_namespaceObject.sleep)(device_INPUT_CLEAR_DELAY);
1264
+ }),
1265
+ (0, device_namespaceObject.defineActionKeyboardPress)(async ({ locate, keyName })=>{
1266
+ this.assertConnected();
1267
+ if (locate) {
1268
+ await this.moveToElement(locate, {
1269
+ steps: device_SMOOTH_MOVE_STEPS_TAP,
1270
+ stepDelayMs: device_SMOOTH_MOVE_DELAY_TAP
1271
+ });
1272
+ await this.backend.mouseButton('left', 'click');
1273
+ }
1274
+ await this.backend.keyPress(keyName);
1275
+ }),
1276
+ (0, device_namespaceObject.defineActionScroll)(async (param)=>{
1277
+ this.assertConnected();
1278
+ const target = param.locate;
1279
+ if (target) await this.moveToElement(target, {
1280
+ steps: device_SMOOTH_MOVE_STEPS_MOUSE_MOVE,
1281
+ stepDelayMs: device_SMOOTH_MOVE_DELAY_MOUSE_MOVE
1282
+ });
1283
+ if (param.scrollType && 'singleAction' !== param.scrollType) {
1284
+ const direction = this.edgeScrollDirection(param.scrollType);
1285
+ for(let i = 0; i < device_EDGE_SCROLL_STEPS; i++)await this.performWheel(direction, DEFAULT_SCROLL_DISTANCE, target?.center[0], target?.center[1]);
1286
+ await (0, utils_namespaceObject.sleep)(device_SCROLL_COMPLETE_DELAY);
1287
+ return;
1288
+ }
1289
+ await this.performWheel(param.direction || 'down', param.distance || DEFAULT_SCROLL_DISTANCE, target?.center[0], target?.center[1]);
1290
+ await (0, utils_namespaceObject.sleep)(device_SCROLL_COMPLETE_DELAY);
1291
+ }),
1292
+ (0, device_namespaceObject.defineActionDragAndDrop)(async ({ from, to })=>{
1293
+ this.assertConnected();
1294
+ const source = this.requireLocate(from, 'drag source');
1295
+ const target = this.requireLocate(to, 'drag target');
1296
+ await this.moveToElement(source, {
1297
+ steps: device_SMOOTH_MOVE_STEPS_TAP,
1298
+ stepDelayMs: device_SMOOTH_MOVE_DELAY_TAP
1299
+ });
1300
+ await this.backend.mouseButton('left', 'down');
1301
+ await (0, utils_namespaceObject.sleep)(DRAG_HOLD_DURATION);
1302
+ await this.moveToElement(target, {
1303
+ steps: SMOOTH_MOVE_STEPS_DRAG,
1304
+ stepDelayMs: SMOOTH_MOVE_DELAY_DRAG
1305
+ });
1306
+ await (0, utils_namespaceObject.sleep)(DRAG_HOLD_DURATION);
1307
+ await this.backend.mouseButton('left', 'up');
1308
+ }),
1309
+ (0, device_namespaceObject.defineAction)({
1310
+ name: 'MiddleClick',
1311
+ description: 'Middle click the element',
1312
+ sample: {
1313
+ locate: {
1314
+ prompt: 'the browser tab close target'
1315
+ }
1316
+ },
1317
+ paramSchema: device_namespaceObject.actionTapParamSchema,
1318
+ call: async ({ locate })=>{
1319
+ const element = this.requireLocate(locate, 'middle click');
1320
+ await this.moveToElement(element, {
1321
+ steps: device_SMOOTH_MOVE_STEPS_TAP,
1322
+ stepDelayMs: device_SMOOTH_MOVE_DELAY_TAP
1323
+ });
1324
+ await this.backend.mouseButton('middle', 'click');
1325
+ }
1326
+ }),
1327
+ (0, device_namespaceObject.defineAction)({
1328
+ name: 'ListDisplays',
1329
+ description: 'List all available displays/monitors',
1330
+ call: async ()=>{
1331
+ this.assertConnected();
1332
+ const size = await this.size();
1333
+ return [
1334
+ {
1335
+ id: this.connectionInfo?.sessionId || this.options.host,
1336
+ name: `RDP ${this.connectionInfo?.server || this.options.host} (${size.width}x${size.height})`,
1337
+ primary: true
1338
+ }
1339
+ ];
1340
+ }
1341
+ })
1342
+ ];
1343
+ return [
1344
+ ...defaultActions,
1345
+ ...this.options.customActions || []
1346
+ ];
1347
+ }
1348
+ assertConnected() {
1349
+ this.throwIfDestroyed();
1350
+ if (!this.connectionInfo) throw new Error('RDPDevice is not connected');
1351
+ }
1352
+ throwIfDestroyed() {
1353
+ if (this.destroyed) throw new Error('RDPDevice has been destroyed');
1354
+ }
1355
+ requireLocate(locate, actionName) {
1356
+ if (!locate) throw new Error(`Missing target element for ${actionName}`);
1357
+ return locate;
1358
+ }
1359
+ async moveToElement(element, options) {
1360
+ this.assertConnected();
1361
+ const targetX = Math.round(element.center[0]);
1362
+ const targetY = Math.round(element.center[1]);
1363
+ await this.movePointer(targetX, targetY, options);
1364
+ }
1365
+ async clearInput() {
1366
+ if (this.backend.clearInput) return void await this.backend.clearInput();
1367
+ await this.backend.keyPress('Control+A');
1368
+ await this.backend.keyPress('Backspace');
1369
+ }
1370
+ edgeScrollDirection(scrollType) {
1371
+ switch(scrollType){
1372
+ case 'scrollToTop':
1373
+ return 'up';
1374
+ case 'scrollToBottom':
1375
+ return 'down';
1376
+ case 'scrollToLeft':
1377
+ return 'left';
1378
+ case 'scrollToRight':
1379
+ return 'right';
1380
+ case 'singleAction':
1381
+ return 'down';
1382
+ default:
1383
+ throw new Error(`Unsupported scroll type: ${scrollType}`);
1384
+ }
1385
+ }
1386
+ async movePointer(targetX, targetY, options) {
1387
+ this.assertConnected();
1388
+ const start = this.cursorPosition || [
1389
+ targetX,
1390
+ targetY
1391
+ ];
1392
+ const steps = Math.max(1, options?.steps || 1);
1393
+ const stepDelayMs = options?.stepDelayMs || 0;
1394
+ for(let step = 1; step <= steps; step++){
1395
+ const x = Math.round(start[0] + (targetX - start[0]) * step / steps);
1396
+ const y = Math.round(start[1] + (targetY - start[1]) * step / steps);
1397
+ await this.backend.mouseMove(x, y);
1398
+ this.cursorPosition = [
1399
+ x,
1400
+ y
1401
+ ];
1402
+ if (stepDelayMs > 0 && step < steps) await (0, utils_namespaceObject.sleep)(stepDelayMs);
1403
+ }
1404
+ if (options?.settleDelayMs) await (0, utils_namespaceObject.sleep)(options.settleDelayMs);
1405
+ }
1406
+ async performWheel(direction, amount, x, y) {
1407
+ let remaining = Math.abs(amount);
1408
+ if (0 === remaining) remaining = DEFAULT_SCROLL_STEP_AMOUNT;
1409
+ while(remaining > 0){
1410
+ const chunk = Math.min(remaining, DEFAULT_SCROLL_STEP_AMOUNT);
1411
+ await this.backend.wheel(direction, chunk, x, y);
1412
+ remaining -= chunk;
1413
+ if (remaining > 0) await (0, utils_namespaceObject.sleep)(device_SCROLL_STEP_DELAY);
1414
+ }
1415
+ }
1416
+ constructor(options){
1417
+ device_define_property(this, "interfaceType", 'rdp');
1418
+ device_define_property(this, "options", void 0);
1419
+ device_define_property(this, "backend", void 0);
1420
+ device_define_property(this, "connectionInfo", void 0);
1421
+ device_define_property(this, "destroyed", false);
1422
+ device_define_property(this, "cursorPosition", void 0);
1423
+ device_define_property(this, "uri", void 0);
1424
+ this.options = {
1425
+ port: 3389,
1426
+ securityProtocol: 'auto',
1427
+ ignoreCertificate: false,
1428
+ ...options
1429
+ };
1430
+ this.backend = options.backend || createDefaultRDPBackendClient();
1431
+ }
1432
+ }
843
1433
  class ComputerAgent extends agent_namespaceObject.Agent {
844
1434
  }
845
1435
  function createLocalComputerDevice(opts) {
@@ -851,12 +1441,33 @@ function createLocalComputerDevice(opts) {
851
1441
  xvfbResolution: opts?.xvfbResolution
852
1442
  });
853
1443
  }
1444
+ function createRDPComputerDevice(opts) {
1445
+ return new RDPDevice({
1446
+ host: opts.host,
1447
+ port: opts.port,
1448
+ username: opts.username,
1449
+ password: opts.password,
1450
+ domain: opts.domain,
1451
+ adminSession: opts.adminSession,
1452
+ ignoreCertificate: opts.ignoreCertificate,
1453
+ securityProtocol: opts.securityProtocol,
1454
+ desktopWidth: opts.desktopWidth,
1455
+ desktopHeight: opts.desktopHeight,
1456
+ backend: opts.backend,
1457
+ customActions: opts.customActions
1458
+ });
1459
+ }
854
1460
  async function agentForComputer(opts) {
855
1461
  const device = createLocalComputerDevice(opts);
856
1462
  await device.connect();
857
1463
  return new ComputerAgent(device, opts);
858
1464
  }
859
1465
  const agentFromComputer = agentForComputer;
1466
+ async function agentForRDPComputer(opts) {
1467
+ const device = createRDPComputerDevice(opts);
1468
+ await device.connect();
1469
+ return new ComputerAgent(device, opts);
1470
+ }
860
1471
  function mcp_tools_define_property(obj, key, value) {
861
1472
  if (key in obj) Object.defineProperty(obj, key, {
862
1473
  value: value,
@@ -868,10 +1479,61 @@ function mcp_tools_define_property(obj, key, value) {
868
1479
  return obj;
869
1480
  }
870
1481
  const mcp_tools_debug = (0, logger_namespaceObject.getDebug)('mcp:computer-tools');
1482
+ const RDP_SECURITY_PROTOCOLS = [
1483
+ 'auto',
1484
+ 'tls',
1485
+ 'nla',
1486
+ 'rdp'
1487
+ ];
871
1488
  const computerInitArgShape = {
872
- displayId: core_namespaceObject.z.string().optional().describe('Display ID (from computer_list_displays)'),
873
- headless: core_namespaceObject.z.boolean().optional().describe('Start virtual display via Xvfb (Linux only)')
1489
+ displayId: core_namespaceObject.z.string().optional().describe('Display ID for local mode (from computer_list_displays). Ignored when host is set.'),
1490
+ headless: core_namespaceObject.z.boolean().optional().describe('Start virtual display via Xvfb (Linux local mode only). Ignored when host is set.'),
1491
+ host: core_namespaceObject.z.string().optional().describe('RDP host (FQDN or IP). Set this to switch into RDP mode.'),
1492
+ port: core_namespaceObject.z.number().optional().describe('RDP port (default 3389). Requires host.'),
1493
+ username: core_namespaceObject.z.string().optional().describe('RDP username. Requires host.'),
1494
+ password: core_namespaceObject.z.string().optional().describe('RDP password. Requires host. Prefer setting via environment or a secrets manager.'),
1495
+ domain: core_namespaceObject.z.string().optional().describe('RDP domain. Requires host.'),
1496
+ adminSession: core_namespaceObject.z.boolean().optional().describe('Attach to the RDP admin/console session. Requires host.'),
1497
+ ignoreCertificate: core_namespaceObject.z.boolean().optional().describe('Skip TLS certificate validation. Requires host.'),
1498
+ securityProtocol: core_namespaceObject.z["enum"](RDP_SECURITY_PROTOCOLS).optional().describe('RDP security protocol negotiation (default auto). Requires host.'),
1499
+ desktopWidth: core_namespaceObject.z.number().optional().describe('Remote desktop width in pixels. Requires host.'),
1500
+ desktopHeight: core_namespaceObject.z.number().optional().describe('Remote desktop height in pixels. Requires host.')
874
1501
  };
1502
+ function adaptComputerInitArgs(extracted) {
1503
+ if (!extracted || 0 === Object.keys(extracted).length) return;
1504
+ if (extracted.host) {
1505
+ const { displayId: _d, headless: _h, ...rdpFields } = extracted;
1506
+ return {
1507
+ mode: 'rdp',
1508
+ ...rdpFields,
1509
+ host: extracted.host
1510
+ };
1511
+ }
1512
+ return {
1513
+ mode: 'local',
1514
+ displayId: extracted.displayId,
1515
+ headless: extracted.headless
1516
+ };
1517
+ }
1518
+ function shouldRetargetAgent(opts) {
1519
+ if (!opts) return false;
1520
+ if ('rdp' === opts.mode) return true;
1521
+ return void 0 !== opts.displayId || void 0 !== opts.headless;
1522
+ }
1523
+ function describeConnectTarget(opts) {
1524
+ if (opts?.mode === 'rdp') {
1525
+ const portSuffix = opts.port ? `:${opts.port}` : '';
1526
+ const userSuffix = opts.username ? ` as ${opts.username}` : '';
1527
+ return ` via RDP (${opts.host}${portSuffix}${userSuffix})`;
1528
+ }
1529
+ if (opts?.mode === 'local' && opts.displayId) return ` (Display: ${opts.displayId})`;
1530
+ return ' (Primary display)';
1531
+ }
1532
+ function getCliReportSessionTarget(opts) {
1533
+ if (opts?.mode === 'rdp') return `rdp:${opts.host}`;
1534
+ if (opts?.mode === 'local' && opts.displayId) return opts.displayId;
1535
+ return 'primary';
1536
+ }
875
1537
  class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools {
876
1538
  getCliReportSessionName() {
877
1539
  return 'midscene-computer';
@@ -880,9 +1542,7 @@ class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
880
1542
  return new ComputerDevice({});
881
1543
  }
882
1544
  async ensureAgent(opts) {
883
- const displayId = opts?.displayId;
884
- const headless = opts?.headless;
885
- if (this.agent && (void 0 !== displayId || void 0 !== headless)) {
1545
+ if (this.agent && shouldRetargetAgent(opts)) {
886
1546
  try {
887
1547
  await this.agent.destroy?.();
888
1548
  } catch (error) {
@@ -891,8 +1551,20 @@ class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
891
1551
  this.agent = void 0;
892
1552
  }
893
1553
  if (this.agent) return this.agent;
894
- mcp_tools_debug('Creating Computer agent with displayId:', displayId || 'primary');
895
1554
  const reportOptions = this.readCliReportAgentOptions();
1555
+ if (opts?.mode === 'rdp') {
1556
+ mcp_tools_debug('Creating RDP Computer agent for host:', opts.host);
1557
+ const { mode: _mode, ...rdpFields } = opts;
1558
+ const agent = await agentForRDPComputer({
1559
+ ...rdpFields,
1560
+ ...reportOptions ?? {}
1561
+ });
1562
+ this.agent = agent;
1563
+ return agent;
1564
+ }
1565
+ const displayId = opts?.mode === 'local' ? opts.displayId : void 0;
1566
+ const headless = opts?.mode === 'local' ? opts.headless : void 0;
1567
+ mcp_tools_debug('Creating Computer agent with displayId:', displayId || 'primary');
896
1568
  const agentOpts = {
897
1569
  ...displayId ? {
898
1570
  displayId
@@ -910,12 +1582,12 @@ class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
910
1582
  return [
911
1583
  {
912
1584
  name: 'computer_connect',
913
- description: 'Connect to computer desktop. Provide displayId to connect to a specific display (use computer_list_displays to get available IDs). If not provided, uses the primary display.',
1585
+ 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.",
914
1586
  schema: this.getAgentInitArgSchema(),
915
1587
  cli: this.getAgentInitArgCliMetadata(),
916
1588
  handler: async (args)=>{
917
1589
  const initArgs = this.extractAgentInitParam(args);
918
- const reportSession = this.createNewCliReportSession(initArgs?.displayId ?? 'primary');
1590
+ const reportSession = this.createNewCliReportSession(getCliReportSessionTarget(initArgs));
919
1591
  this.commitCliReportSession(reportSession);
920
1592
  if (this.agent) {
921
1593
  try {
@@ -931,7 +1603,7 @@ class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
931
1603
  content: [
932
1604
  {
933
1605
  type: 'text',
934
- text: `Connected to computer${initArgs?.displayId ? ` (Display: ${initArgs.displayId})` : ' (Primary display)'}`
1606
+ text: `Connected to computer${describeConnectTarget(initArgs)}`
935
1607
  },
936
1608
  ...this.buildScreenshotContent(screenshot)
937
1609
  ]
@@ -969,14 +1641,14 @@ class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
969
1641
  cli: {
970
1642
  preferBareKeys: true
971
1643
  },
972
- adapt: (extracted)=>extracted
1644
+ adapt: (extracted)=>adaptComputerInitArgs(extracted)
973
1645
  });
974
1646
  }
975
1647
  }
976
1648
  const tools = new ComputerMidsceneTools();
977
1649
  (0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-computer', {
978
1650
  stripPrefix: 'computer_',
979
- version: "1.8.0",
1651
+ version: "1.8.1-beta-20260513084557.0",
980
1652
  extraCommands: (0, core_namespaceObject.createReportCliCommands)()
981
1653
  }).catch((e)=>{
982
1654
  process.exit((0, cli_namespaceObject.reportCLIError)(e));