@midscene/computer 1.9.2-beta-20260608084200.0 → 1.9.2
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/bin/linux/rdp-helper +0 -0
- package/dist/es/cli.mjs +41 -44
- package/dist/es/index.mjs +41 -44
- package/dist/es/mcp-server.mjs +41 -44
- package/dist/lib/cli.js +40 -43
- package/dist/lib/index.js +40 -43
- package/dist/lib/mcp-server.js +40 -43
- package/dist/types/index.d.ts +6 -0
- package/dist/types/mcp-server.d.ts +6 -0
- package/native/rdp/src/main.cpp +1 -9
- package/native/rdp/src/session.cpp +3 -13
- package/package.json +3 -3
package/dist/lib/cli.js
CHANGED
|
@@ -376,9 +376,13 @@ async function getLibnut() {
|
|
|
376
376
|
}
|
|
377
377
|
}
|
|
378
378
|
const debugDevice = (0, logger_namespaceObject.getDebug)('computer:device');
|
|
379
|
+
const warnDevice = (0, logger_namespaceObject.getDebug)('computer:device', {
|
|
380
|
+
console: true
|
|
381
|
+
});
|
|
379
382
|
const debugComputerInput = (0, logger_namespaceObject.getDebug)('computer:input', {
|
|
380
383
|
console: true
|
|
381
384
|
});
|
|
385
|
+
const WINDOWS_UIPI_DOC_URL = 'https://midscenejs.com/computer-getting-started#windows-clicks-have-no-effect-on-some-apps';
|
|
382
386
|
function resolvePackageRoot(helperName) {
|
|
383
387
|
const require1 = (0, external_node_module_namespaceObject.createRequire)(__rslib_import_meta_url__);
|
|
384
388
|
let pkgRoot = null;
|
|
@@ -418,6 +422,15 @@ function getPhasedScrollBinary() {
|
|
|
418
422
|
phasedScrollBinaryPath = null;
|
|
419
423
|
return null;
|
|
420
424
|
}
|
|
425
|
+
try {
|
|
426
|
+
const st = (0, external_node_fs_namespaceObject.statSync)(binPath);
|
|
427
|
+
if ((73 & st.mode) === 0) {
|
|
428
|
+
(0, external_node_fs_namespaceObject.chmodSync)(binPath, 493);
|
|
429
|
+
debugDevice('phased-scroll: restored executable bit on', binPath);
|
|
430
|
+
}
|
|
431
|
+
} catch (err) {
|
|
432
|
+
debugDevice('phased-scroll: chmod self-heal failed', err);
|
|
433
|
+
}
|
|
421
434
|
phasedScrollBinaryPath = binPath;
|
|
422
435
|
return binPath;
|
|
423
436
|
}
|
|
@@ -545,9 +558,10 @@ function runPhasedScroll(direction, pixels, steps) {
|
|
|
545
558
|
if (0 === res.status) return true;
|
|
546
559
|
if (!phasedScrollExecWarned) {
|
|
547
560
|
phasedScrollExecWarned = true;
|
|
548
|
-
|
|
561
|
+
const hint = null === res.status ? `signal ${res.signal ?? 'unknown'}; the binary may not be executable (npm tarball extraction can drop the +x bit) or may be blocked by quarantine. Try: chmod +x "${bin}"` : 'this usually means Accessibility permission has not been granted to the host process (System Settings → Privacy & Security → Accessibility)';
|
|
562
|
+
console.warn(`[@midscene/computer] phased-scroll helper failed (exit=${res.status}, signal=${res.signal ?? 'none'}); falling back to keyboard/libnut. ${hint}`);
|
|
549
563
|
}
|
|
550
|
-
debugDevice('phased-scroll exited non-zero', res.status, res.error);
|
|
564
|
+
debugDevice('phased-scroll exited non-zero', res.status, res.signal, res.error);
|
|
551
565
|
return false;
|
|
552
566
|
} catch (err) {
|
|
553
567
|
if (!phasedScrollExecWarned) {
|
|
@@ -668,7 +682,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
668
682
|
}
|
|
669
683
|
async healthCheck() {
|
|
670
684
|
console.log('[HealthCheck] Starting health check...');
|
|
671
|
-
console.log("[HealthCheck] @midscene/computer v1.9.2
|
|
685
|
+
console.log("[HealthCheck] @midscene/computer v1.9.2");
|
|
672
686
|
console.log('[HealthCheck] Taking screenshot...');
|
|
673
687
|
const screenshotTimeout = 15000;
|
|
674
688
|
let timeoutId;
|
|
@@ -696,13 +710,15 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
696
710
|
const deltaY = Math.abs(movedPos.y - targetY);
|
|
697
711
|
if (deltaX > 5 || deltaY > 5) {
|
|
698
712
|
const msg = `[HealthCheck] WARNING: Mouse control may not be working. Expected (${targetX}, ${targetY}), got (${movedPos.x}, ${movedPos.y}), delta=(${deltaX}, ${deltaY})`;
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
713
|
+
warnDevice(msg);
|
|
714
|
+
}
|
|
715
|
+
if ('win32' === process.platform && !this.isRunningAsAdmin()) {
|
|
716
|
+
const hint = [
|
|
717
|
+
'Heads-up: Midscene is not running as Administrator.',
|
|
718
|
+
'If clicks or key presses have no effect while the cursor still moves to the right position,',
|
|
719
|
+
`see the Windows permission troubleshooting guide: ${WINDOWS_UIPI_DOC_URL}`
|
|
720
|
+
].join(' ');
|
|
721
|
+
warnDevice(`[HealthCheck] ${hint}`);
|
|
706
722
|
}
|
|
707
723
|
this.inputDriver.moveMouse(startPos.x, startPos.y);
|
|
708
724
|
console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
|
|
@@ -719,14 +735,16 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
719
735
|
}
|
|
720
736
|
isRunningAsAdmin() {
|
|
721
737
|
if ('win32' !== process.platform) return false;
|
|
738
|
+
if (void 0 !== this.adminCheckCache) return this.adminCheckCache;
|
|
722
739
|
try {
|
|
723
740
|
(0, external_node_child_process_namespaceObject.execSync)('net session', {
|
|
724
741
|
stdio: 'pipe'
|
|
725
742
|
});
|
|
726
|
-
|
|
743
|
+
this.adminCheckCache = true;
|
|
727
744
|
} catch {
|
|
728
|
-
|
|
745
|
+
this.adminCheckCache = false;
|
|
729
746
|
}
|
|
747
|
+
return this.adminCheckCache;
|
|
730
748
|
}
|
|
731
749
|
async screenshotBase64() {
|
|
732
750
|
if (this.destroyed) throw new Error('ComputerDevice has been destroyed');
|
|
@@ -971,6 +989,7 @@ Original error: ${lastRawMessage}`);
|
|
|
971
989
|
debug: (message)=>debugDevice(message)
|
|
972
990
|
}));
|
|
973
991
|
device_define_property(this, "useAppleScript", void 0);
|
|
992
|
+
device_define_property(this, "adminCheckCache", void 0);
|
|
974
993
|
device_define_property(this, "uri", void 0);
|
|
975
994
|
device_define_property(this, "inputPrimitives", {
|
|
976
995
|
pointer: {
|
|
@@ -1121,24 +1140,6 @@ function createPlatformActions() {
|
|
|
1121
1140
|
})
|
|
1122
1141
|
};
|
|
1123
1142
|
}
|
|
1124
|
-
function normalizeRdpHost(host) {
|
|
1125
|
-
const trimmed = host.trim();
|
|
1126
|
-
if (trimmed.length >= 2 && trimmed.startsWith('[') && trimmed.endsWith(']') && trimmed.includes(':')) return trimmed.slice(1, -1);
|
|
1127
|
-
return trimmed;
|
|
1128
|
-
}
|
|
1129
|
-
function formatRdpHost(host) {
|
|
1130
|
-
const normalizedHost = normalizeRdpHost(host);
|
|
1131
|
-
return normalizedHost.includes(':') ? `[${normalizedHost}]` : normalizedHost;
|
|
1132
|
-
}
|
|
1133
|
-
function formatRdpServerAddress(host, port) {
|
|
1134
|
-
return `${formatRdpHost(host)}:${port}`;
|
|
1135
|
-
}
|
|
1136
|
-
function normalizeRdpConnectionConfig(config) {
|
|
1137
|
-
return {
|
|
1138
|
-
...config,
|
|
1139
|
-
host: normalizeRdpHost(config.host)
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
1143
|
const external_node_events_namespaceObject = require("node:events");
|
|
1143
1144
|
const external_node_readline_namespaceObject = require("node:readline");
|
|
1144
1145
|
const platformBinaryMap = {
|
|
@@ -1194,7 +1195,7 @@ class HelperProcessRDPBackendClient {
|
|
|
1194
1195
|
await this.ensureHelperStarted();
|
|
1195
1196
|
const response = await this.send({
|
|
1196
1197
|
type: 'connect',
|
|
1197
|
-
config
|
|
1198
|
+
config
|
|
1198
1199
|
});
|
|
1199
1200
|
if ('connected' !== response.type) throw new Error(`Expected connected response, got ${response.type}`);
|
|
1200
1201
|
this.connected = true;
|
|
@@ -1468,10 +1469,9 @@ const DEFAULT_SCROLL_STEP_AMOUNT = 120;
|
|
|
1468
1469
|
class RDPDevice {
|
|
1469
1470
|
describe() {
|
|
1470
1471
|
const port = this.options.port || 3389;
|
|
1471
|
-
const server = formatRdpServerAddress(this.options.host, port);
|
|
1472
1472
|
const username = this.options.username ? ` as ${this.options.username}` : '';
|
|
1473
1473
|
const session = this.connectionInfo?.sessionId ? ` [session ${this.connectionInfo.sessionId}]` : '';
|
|
1474
|
-
return `RDP Device ${
|
|
1474
|
+
return `RDP Device ${this.options.host}:${port}${username}${session}`;
|
|
1475
1475
|
}
|
|
1476
1476
|
async connect() {
|
|
1477
1477
|
this.throwIfDestroyed();
|
|
@@ -1511,11 +1511,10 @@ class RDPDevice {
|
|
|
1511
1511
|
call: async ()=>{
|
|
1512
1512
|
this.assertConnected();
|
|
1513
1513
|
const size = await this.size();
|
|
1514
|
-
const server = this.connectionInfo?.server || formatRdpServerAddress(this.options.host, this.options.port || 3389);
|
|
1515
1514
|
return [
|
|
1516
1515
|
{
|
|
1517
1516
|
id: this.connectionInfo?.sessionId || this.options.host,
|
|
1518
|
-
name: `RDP ${server} (${size.width}x${size.height})`,
|
|
1517
|
+
name: `RDP ${this.connectionInfo?.server || this.options.host} (${size.width}x${size.height})`,
|
|
1519
1518
|
primary: true
|
|
1520
1519
|
}
|
|
1521
1520
|
];
|
|
@@ -1713,12 +1712,11 @@ class RDPDevice {
|
|
|
1713
1712
|
}
|
|
1714
1713
|
}
|
|
1715
1714
|
});
|
|
1716
|
-
const normalizedOptions = normalizeRdpConnectionConfig(options);
|
|
1717
1715
|
this.options = {
|
|
1718
1716
|
port: 3389,
|
|
1719
1717
|
securityProtocol: 'auto',
|
|
1720
1718
|
ignoreCertificate: false,
|
|
1721
|
-
...
|
|
1719
|
+
...options
|
|
1722
1720
|
};
|
|
1723
1721
|
this.backend = options.backend || createDefaultRDPBackendClient();
|
|
1724
1722
|
}
|
|
@@ -1796,11 +1794,10 @@ function adaptComputerInitArgs(extracted) {
|
|
|
1796
1794
|
if (!extracted || 0 === Object.keys(extracted).length) return;
|
|
1797
1795
|
if (extracted.host) {
|
|
1798
1796
|
const { displayId: _d, headless: _h, ...rdpFields } = extracted;
|
|
1799
|
-
const host = normalizeRdpHost(extracted.host);
|
|
1800
1797
|
return {
|
|
1801
1798
|
mode: 'rdp',
|
|
1802
1799
|
...rdpFields,
|
|
1803
|
-
host
|
|
1800
|
+
host: extracted.host
|
|
1804
1801
|
};
|
|
1805
1802
|
}
|
|
1806
1803
|
return {
|
|
@@ -1816,15 +1813,15 @@ function shouldRetargetAgent(opts) {
|
|
|
1816
1813
|
}
|
|
1817
1814
|
function describeConnectTarget(opts) {
|
|
1818
1815
|
if (opts?.mode === 'rdp') {
|
|
1819
|
-
const
|
|
1816
|
+
const portSuffix = opts.port ? `:${opts.port}` : '';
|
|
1820
1817
|
const userSuffix = opts.username ? ` as ${opts.username}` : '';
|
|
1821
|
-
return ` via RDP (${
|
|
1818
|
+
return ` via RDP (${opts.host}${portSuffix}${userSuffix})`;
|
|
1822
1819
|
}
|
|
1823
1820
|
if (opts?.mode === 'local' && opts.displayId) return ` (Display: ${opts.displayId})`;
|
|
1824
1821
|
return ' (Primary display)';
|
|
1825
1822
|
}
|
|
1826
1823
|
function getCliReportSessionTarget(opts) {
|
|
1827
|
-
if (opts?.mode === 'rdp') return `rdp:${
|
|
1824
|
+
if (opts?.mode === 'rdp') return `rdp:${opts.host}`;
|
|
1828
1825
|
if (opts?.mode === 'local' && opts.displayId) return opts.displayId;
|
|
1829
1826
|
return 'primary';
|
|
1830
1827
|
}
|
|
@@ -1942,7 +1939,7 @@ class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
|
|
|
1942
1939
|
const tools = new ComputerMidsceneTools();
|
|
1943
1940
|
(0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-computer', {
|
|
1944
1941
|
stripPrefix: 'computer_',
|
|
1945
|
-
version: "1.9.2
|
|
1942
|
+
version: "1.9.2",
|
|
1946
1943
|
extraCommands: (0, core_namespaceObject.createReportCliCommands)()
|
|
1947
1944
|
}).catch((e)=>{
|
|
1948
1945
|
process.exit((0, cli_namespaceObject.reportCLIError)(e));
|
package/dist/lib/index.js
CHANGED
|
@@ -403,9 +403,13 @@ async function getLibnut() {
|
|
|
403
403
|
}
|
|
404
404
|
}
|
|
405
405
|
const debugDevice = (0, logger_namespaceObject.getDebug)('computer:device');
|
|
406
|
+
const warnDevice = (0, logger_namespaceObject.getDebug)('computer:device', {
|
|
407
|
+
console: true
|
|
408
|
+
});
|
|
406
409
|
const debugComputerInput = (0, logger_namespaceObject.getDebug)('computer:input', {
|
|
407
410
|
console: true
|
|
408
411
|
});
|
|
412
|
+
const WINDOWS_UIPI_DOC_URL = 'https://midscenejs.com/computer-getting-started#windows-clicks-have-no-effect-on-some-apps';
|
|
409
413
|
function resolvePackageRoot(helperName) {
|
|
410
414
|
const require1 = (0, external_node_module_namespaceObject.createRequire)(__rslib_import_meta_url__);
|
|
411
415
|
let pkgRoot = null;
|
|
@@ -445,6 +449,15 @@ function getPhasedScrollBinary() {
|
|
|
445
449
|
phasedScrollBinaryPath = null;
|
|
446
450
|
return null;
|
|
447
451
|
}
|
|
452
|
+
try {
|
|
453
|
+
const st = (0, external_node_fs_namespaceObject.statSync)(binPath);
|
|
454
|
+
if ((73 & st.mode) === 0) {
|
|
455
|
+
(0, external_node_fs_namespaceObject.chmodSync)(binPath, 493);
|
|
456
|
+
debugDevice('phased-scroll: restored executable bit on', binPath);
|
|
457
|
+
}
|
|
458
|
+
} catch (err) {
|
|
459
|
+
debugDevice('phased-scroll: chmod self-heal failed', err);
|
|
460
|
+
}
|
|
448
461
|
phasedScrollBinaryPath = binPath;
|
|
449
462
|
return binPath;
|
|
450
463
|
}
|
|
@@ -572,9 +585,10 @@ function runPhasedScroll(direction, pixels, steps) {
|
|
|
572
585
|
if (0 === res.status) return true;
|
|
573
586
|
if (!phasedScrollExecWarned) {
|
|
574
587
|
phasedScrollExecWarned = true;
|
|
575
|
-
|
|
588
|
+
const hint = null === res.status ? `signal ${res.signal ?? 'unknown'}; the binary may not be executable (npm tarball extraction can drop the +x bit) or may be blocked by quarantine. Try: chmod +x "${bin}"` : 'this usually means Accessibility permission has not been granted to the host process (System Settings → Privacy & Security → Accessibility)';
|
|
589
|
+
console.warn(`[@midscene/computer] phased-scroll helper failed (exit=${res.status}, signal=${res.signal ?? 'none'}); falling back to keyboard/libnut. ${hint}`);
|
|
576
590
|
}
|
|
577
|
-
debugDevice('phased-scroll exited non-zero', res.status, res.error);
|
|
591
|
+
debugDevice('phased-scroll exited non-zero', res.status, res.signal, res.error);
|
|
578
592
|
return false;
|
|
579
593
|
} catch (err) {
|
|
580
594
|
if (!phasedScrollExecWarned) {
|
|
@@ -695,7 +709,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
695
709
|
}
|
|
696
710
|
async healthCheck() {
|
|
697
711
|
console.log('[HealthCheck] Starting health check...');
|
|
698
|
-
console.log("[HealthCheck] @midscene/computer v1.9.2
|
|
712
|
+
console.log("[HealthCheck] @midscene/computer v1.9.2");
|
|
699
713
|
console.log('[HealthCheck] Taking screenshot...');
|
|
700
714
|
const screenshotTimeout = 15000;
|
|
701
715
|
let timeoutId;
|
|
@@ -723,13 +737,15 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
723
737
|
const deltaY = Math.abs(movedPos.y - targetY);
|
|
724
738
|
if (deltaX > 5 || deltaY > 5) {
|
|
725
739
|
const msg = `[HealthCheck] WARNING: Mouse control may not be working. Expected (${targetX}, ${targetY}), got (${movedPos.x}, ${movedPos.y}), delta=(${deltaX}, ${deltaY})`;
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
740
|
+
warnDevice(msg);
|
|
741
|
+
}
|
|
742
|
+
if ('win32' === process.platform && !this.isRunningAsAdmin()) {
|
|
743
|
+
const hint = [
|
|
744
|
+
'Heads-up: Midscene is not running as Administrator.',
|
|
745
|
+
'If clicks or key presses have no effect while the cursor still moves to the right position,',
|
|
746
|
+
`see the Windows permission troubleshooting guide: ${WINDOWS_UIPI_DOC_URL}`
|
|
747
|
+
].join(' ');
|
|
748
|
+
warnDevice(`[HealthCheck] ${hint}`);
|
|
733
749
|
}
|
|
734
750
|
this.inputDriver.moveMouse(startPos.x, startPos.y);
|
|
735
751
|
console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
|
|
@@ -746,14 +762,16 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
746
762
|
}
|
|
747
763
|
isRunningAsAdmin() {
|
|
748
764
|
if ('win32' !== process.platform) return false;
|
|
765
|
+
if (void 0 !== this.adminCheckCache) return this.adminCheckCache;
|
|
749
766
|
try {
|
|
750
767
|
(0, external_node_child_process_namespaceObject.execSync)('net session', {
|
|
751
768
|
stdio: 'pipe'
|
|
752
769
|
});
|
|
753
|
-
|
|
770
|
+
this.adminCheckCache = true;
|
|
754
771
|
} catch {
|
|
755
|
-
|
|
772
|
+
this.adminCheckCache = false;
|
|
756
773
|
}
|
|
774
|
+
return this.adminCheckCache;
|
|
757
775
|
}
|
|
758
776
|
async screenshotBase64() {
|
|
759
777
|
if (this.destroyed) throw new Error('ComputerDevice has been destroyed');
|
|
@@ -998,6 +1016,7 @@ Original error: ${lastRawMessage}`);
|
|
|
998
1016
|
debug: (message)=>debugDevice(message)
|
|
999
1017
|
}));
|
|
1000
1018
|
device_define_property(this, "useAppleScript", void 0);
|
|
1019
|
+
device_define_property(this, "adminCheckCache", void 0);
|
|
1001
1020
|
device_define_property(this, "uri", void 0);
|
|
1002
1021
|
device_define_property(this, "inputPrimitives", {
|
|
1003
1022
|
pointer: {
|
|
@@ -1149,24 +1168,6 @@ function createPlatformActions() {
|
|
|
1149
1168
|
};
|
|
1150
1169
|
}
|
|
1151
1170
|
const agent_namespaceObject = require("@midscene/core/agent");
|
|
1152
|
-
function normalizeRdpHost(host) {
|
|
1153
|
-
const trimmed = host.trim();
|
|
1154
|
-
if (trimmed.length >= 2 && trimmed.startsWith('[') && trimmed.endsWith(']') && trimmed.includes(':')) return trimmed.slice(1, -1);
|
|
1155
|
-
return trimmed;
|
|
1156
|
-
}
|
|
1157
|
-
function formatRdpHost(host) {
|
|
1158
|
-
const normalizedHost = normalizeRdpHost(host);
|
|
1159
|
-
return normalizedHost.includes(':') ? `[${normalizedHost}]` : normalizedHost;
|
|
1160
|
-
}
|
|
1161
|
-
function formatRdpServerAddress(host, port) {
|
|
1162
|
-
return `${formatRdpHost(host)}:${port}`;
|
|
1163
|
-
}
|
|
1164
|
-
function normalizeRdpConnectionConfig(config) {
|
|
1165
|
-
return {
|
|
1166
|
-
...config,
|
|
1167
|
-
host: normalizeRdpHost(config.host)
|
|
1168
|
-
};
|
|
1169
|
-
}
|
|
1170
1171
|
const external_node_events_namespaceObject = require("node:events");
|
|
1171
1172
|
const external_node_readline_namespaceObject = require("node:readline");
|
|
1172
1173
|
const platformBinaryMap = {
|
|
@@ -1265,7 +1266,7 @@ class HelperProcessRDPBackendClient {
|
|
|
1265
1266
|
await this.ensureHelperStarted();
|
|
1266
1267
|
const response = await this.send({
|
|
1267
1268
|
type: 'connect',
|
|
1268
|
-
config
|
|
1269
|
+
config
|
|
1269
1270
|
});
|
|
1270
1271
|
if ('connected' !== response.type) throw new Error(`Expected connected response, got ${response.type}`);
|
|
1271
1272
|
this.connected = true;
|
|
@@ -1539,10 +1540,9 @@ const DEFAULT_SCROLL_STEP_AMOUNT = 120;
|
|
|
1539
1540
|
class RDPDevice {
|
|
1540
1541
|
describe() {
|
|
1541
1542
|
const port = this.options.port || 3389;
|
|
1542
|
-
const server = formatRdpServerAddress(this.options.host, port);
|
|
1543
1543
|
const username = this.options.username ? ` as ${this.options.username}` : '';
|
|
1544
1544
|
const session = this.connectionInfo?.sessionId ? ` [session ${this.connectionInfo.sessionId}]` : '';
|
|
1545
|
-
return `RDP Device ${
|
|
1545
|
+
return `RDP Device ${this.options.host}:${port}${username}${session}`;
|
|
1546
1546
|
}
|
|
1547
1547
|
async connect() {
|
|
1548
1548
|
this.throwIfDestroyed();
|
|
@@ -1582,11 +1582,10 @@ class RDPDevice {
|
|
|
1582
1582
|
call: async ()=>{
|
|
1583
1583
|
this.assertConnected();
|
|
1584
1584
|
const size = await this.size();
|
|
1585
|
-
const server = this.connectionInfo?.server || formatRdpServerAddress(this.options.host, this.options.port || 3389);
|
|
1586
1585
|
return [
|
|
1587
1586
|
{
|
|
1588
1587
|
id: this.connectionInfo?.sessionId || this.options.host,
|
|
1589
|
-
name: `RDP ${server} (${size.width}x${size.height})`,
|
|
1588
|
+
name: `RDP ${this.connectionInfo?.server || this.options.host} (${size.width}x${size.height})`,
|
|
1590
1589
|
primary: true
|
|
1591
1590
|
}
|
|
1592
1591
|
];
|
|
@@ -1784,12 +1783,11 @@ class RDPDevice {
|
|
|
1784
1783
|
}
|
|
1785
1784
|
}
|
|
1786
1785
|
});
|
|
1787
|
-
const normalizedOptions = normalizeRdpConnectionConfig(options);
|
|
1788
1786
|
this.options = {
|
|
1789
1787
|
port: 3389,
|
|
1790
1788
|
securityProtocol: 'auto',
|
|
1791
1789
|
ignoreCertificate: false,
|
|
1792
|
-
...
|
|
1790
|
+
...options
|
|
1793
1791
|
};
|
|
1794
1792
|
this.backend = options.backend || createDefaultRDPBackendClient();
|
|
1795
1793
|
}
|
|
@@ -1869,11 +1867,10 @@ function adaptComputerInitArgs(extracted) {
|
|
|
1869
1867
|
if (!extracted || 0 === Object.keys(extracted).length) return;
|
|
1870
1868
|
if (extracted.host) {
|
|
1871
1869
|
const { displayId: _d, headless: _h, ...rdpFields } = extracted;
|
|
1872
|
-
const host = normalizeRdpHost(extracted.host);
|
|
1873
1870
|
return {
|
|
1874
1871
|
mode: 'rdp',
|
|
1875
1872
|
...rdpFields,
|
|
1876
|
-
host
|
|
1873
|
+
host: extracted.host
|
|
1877
1874
|
};
|
|
1878
1875
|
}
|
|
1879
1876
|
return {
|
|
@@ -1889,15 +1886,15 @@ function shouldRetargetAgent(opts) {
|
|
|
1889
1886
|
}
|
|
1890
1887
|
function describeConnectTarget(opts) {
|
|
1891
1888
|
if (opts?.mode === 'rdp') {
|
|
1892
|
-
const
|
|
1889
|
+
const portSuffix = opts.port ? `:${opts.port}` : '';
|
|
1893
1890
|
const userSuffix = opts.username ? ` as ${opts.username}` : '';
|
|
1894
|
-
return ` via RDP (${
|
|
1891
|
+
return ` via RDP (${opts.host}${portSuffix}${userSuffix})`;
|
|
1895
1892
|
}
|
|
1896
1893
|
if (opts?.mode === 'local' && opts.displayId) return ` (Display: ${opts.displayId})`;
|
|
1897
1894
|
return ' (Primary display)';
|
|
1898
1895
|
}
|
|
1899
1896
|
function getCliReportSessionTarget(opts) {
|
|
1900
|
-
if (opts?.mode === 'rdp') return `rdp:${
|
|
1897
|
+
if (opts?.mode === 'rdp') return `rdp:${opts.host}`;
|
|
1901
1898
|
if (opts?.mode === 'local' && opts.displayId) return opts.displayId;
|
|
1902
1899
|
return 'primary';
|
|
1903
1900
|
}
|
|
@@ -2014,7 +2011,7 @@ class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
|
|
|
2014
2011
|
}
|
|
2015
2012
|
const env_namespaceObject = require("@midscene/shared/env");
|
|
2016
2013
|
function version() {
|
|
2017
|
-
const currentVersion = "1.9.2
|
|
2014
|
+
const currentVersion = "1.9.2";
|
|
2018
2015
|
console.log(`@midscene/computer v${currentVersion}`);
|
|
2019
2016
|
return currentVersion;
|
|
2020
2017
|
}
|
package/dist/lib/mcp-server.js
CHANGED
|
@@ -390,9 +390,13 @@ async function getLibnut() {
|
|
|
390
390
|
}
|
|
391
391
|
}
|
|
392
392
|
const debugDevice = (0, logger_namespaceObject.getDebug)('computer:device');
|
|
393
|
+
const warnDevice = (0, logger_namespaceObject.getDebug)('computer:device', {
|
|
394
|
+
console: true
|
|
395
|
+
});
|
|
393
396
|
const debugComputerInput = (0, logger_namespaceObject.getDebug)('computer:input', {
|
|
394
397
|
console: true
|
|
395
398
|
});
|
|
399
|
+
const WINDOWS_UIPI_DOC_URL = 'https://midscenejs.com/computer-getting-started#windows-clicks-have-no-effect-on-some-apps';
|
|
396
400
|
function resolvePackageRoot(helperName) {
|
|
397
401
|
const require1 = (0, external_node_module_namespaceObject.createRequire)(__rslib_import_meta_url__);
|
|
398
402
|
let pkgRoot = null;
|
|
@@ -432,6 +436,15 @@ function getPhasedScrollBinary() {
|
|
|
432
436
|
phasedScrollBinaryPath = null;
|
|
433
437
|
return null;
|
|
434
438
|
}
|
|
439
|
+
try {
|
|
440
|
+
const st = (0, external_node_fs_namespaceObject.statSync)(binPath);
|
|
441
|
+
if ((73 & st.mode) === 0) {
|
|
442
|
+
(0, external_node_fs_namespaceObject.chmodSync)(binPath, 493);
|
|
443
|
+
debugDevice('phased-scroll: restored executable bit on', binPath);
|
|
444
|
+
}
|
|
445
|
+
} catch (err) {
|
|
446
|
+
debugDevice('phased-scroll: chmod self-heal failed', err);
|
|
447
|
+
}
|
|
435
448
|
phasedScrollBinaryPath = binPath;
|
|
436
449
|
return binPath;
|
|
437
450
|
}
|
|
@@ -559,9 +572,10 @@ function runPhasedScroll(direction, pixels, steps) {
|
|
|
559
572
|
if (0 === res.status) return true;
|
|
560
573
|
if (!phasedScrollExecWarned) {
|
|
561
574
|
phasedScrollExecWarned = true;
|
|
562
|
-
|
|
575
|
+
const hint = null === res.status ? `signal ${res.signal ?? 'unknown'}; the binary may not be executable (npm tarball extraction can drop the +x bit) or may be blocked by quarantine. Try: chmod +x "${bin}"` : 'this usually means Accessibility permission has not been granted to the host process (System Settings → Privacy & Security → Accessibility)';
|
|
576
|
+
console.warn(`[@midscene/computer] phased-scroll helper failed (exit=${res.status}, signal=${res.signal ?? 'none'}); falling back to keyboard/libnut. ${hint}`);
|
|
563
577
|
}
|
|
564
|
-
debugDevice('phased-scroll exited non-zero', res.status, res.error);
|
|
578
|
+
debugDevice('phased-scroll exited non-zero', res.status, res.signal, res.error);
|
|
565
579
|
return false;
|
|
566
580
|
} catch (err) {
|
|
567
581
|
if (!phasedScrollExecWarned) {
|
|
@@ -682,7 +696,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
682
696
|
}
|
|
683
697
|
async healthCheck() {
|
|
684
698
|
console.log('[HealthCheck] Starting health check...');
|
|
685
|
-
console.log("[HealthCheck] @midscene/computer v1.9.2
|
|
699
|
+
console.log("[HealthCheck] @midscene/computer v1.9.2");
|
|
686
700
|
console.log('[HealthCheck] Taking screenshot...');
|
|
687
701
|
const screenshotTimeout = 15000;
|
|
688
702
|
let timeoutId;
|
|
@@ -710,13 +724,15 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
710
724
|
const deltaY = Math.abs(movedPos.y - targetY);
|
|
711
725
|
if (deltaX > 5 || deltaY > 5) {
|
|
712
726
|
const msg = `[HealthCheck] WARNING: Mouse control may not be working. Expected (${targetX}, ${targetY}), got (${movedPos.x}, ${movedPos.y}), delta=(${deltaX}, ${deltaY})`;
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
727
|
+
warnDevice(msg);
|
|
728
|
+
}
|
|
729
|
+
if ('win32' === process.platform && !this.isRunningAsAdmin()) {
|
|
730
|
+
const hint = [
|
|
731
|
+
'Heads-up: Midscene is not running as Administrator.',
|
|
732
|
+
'If clicks or key presses have no effect while the cursor still moves to the right position,',
|
|
733
|
+
`see the Windows permission troubleshooting guide: ${WINDOWS_UIPI_DOC_URL}`
|
|
734
|
+
].join(' ');
|
|
735
|
+
warnDevice(`[HealthCheck] ${hint}`);
|
|
720
736
|
}
|
|
721
737
|
this.inputDriver.moveMouse(startPos.x, startPos.y);
|
|
722
738
|
console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
|
|
@@ -733,14 +749,16 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
733
749
|
}
|
|
734
750
|
isRunningAsAdmin() {
|
|
735
751
|
if ('win32' !== process.platform) return false;
|
|
752
|
+
if (void 0 !== this.adminCheckCache) return this.adminCheckCache;
|
|
736
753
|
try {
|
|
737
754
|
(0, external_node_child_process_namespaceObject.execSync)('net session', {
|
|
738
755
|
stdio: 'pipe'
|
|
739
756
|
});
|
|
740
|
-
|
|
757
|
+
this.adminCheckCache = true;
|
|
741
758
|
} catch {
|
|
742
|
-
|
|
759
|
+
this.adminCheckCache = false;
|
|
743
760
|
}
|
|
761
|
+
return this.adminCheckCache;
|
|
744
762
|
}
|
|
745
763
|
async screenshotBase64() {
|
|
746
764
|
if (this.destroyed) throw new Error('ComputerDevice has been destroyed');
|
|
@@ -985,6 +1003,7 @@ Original error: ${lastRawMessage}`);
|
|
|
985
1003
|
debug: (message)=>debugDevice(message)
|
|
986
1004
|
}));
|
|
987
1005
|
device_define_property(this, "useAppleScript", void 0);
|
|
1006
|
+
device_define_property(this, "adminCheckCache", void 0);
|
|
988
1007
|
device_define_property(this, "uri", void 0);
|
|
989
1008
|
device_define_property(this, "inputPrimitives", {
|
|
990
1009
|
pointer: {
|
|
@@ -1135,24 +1154,6 @@ function createPlatformActions() {
|
|
|
1135
1154
|
})
|
|
1136
1155
|
};
|
|
1137
1156
|
}
|
|
1138
|
-
function normalizeRdpHost(host) {
|
|
1139
|
-
const trimmed = host.trim();
|
|
1140
|
-
if (trimmed.length >= 2 && trimmed.startsWith('[') && trimmed.endsWith(']') && trimmed.includes(':')) return trimmed.slice(1, -1);
|
|
1141
|
-
return trimmed;
|
|
1142
|
-
}
|
|
1143
|
-
function formatRdpHost(host) {
|
|
1144
|
-
const normalizedHost = normalizeRdpHost(host);
|
|
1145
|
-
return normalizedHost.includes(':') ? `[${normalizedHost}]` : normalizedHost;
|
|
1146
|
-
}
|
|
1147
|
-
function formatRdpServerAddress(host, port) {
|
|
1148
|
-
return `${formatRdpHost(host)}:${port}`;
|
|
1149
|
-
}
|
|
1150
|
-
function normalizeRdpConnectionConfig(config) {
|
|
1151
|
-
return {
|
|
1152
|
-
...config,
|
|
1153
|
-
host: normalizeRdpHost(config.host)
|
|
1154
|
-
};
|
|
1155
|
-
}
|
|
1156
1157
|
const external_node_events_namespaceObject = require("node:events");
|
|
1157
1158
|
const external_node_readline_namespaceObject = require("node:readline");
|
|
1158
1159
|
const platformBinaryMap = {
|
|
@@ -1208,7 +1209,7 @@ class HelperProcessRDPBackendClient {
|
|
|
1208
1209
|
await this.ensureHelperStarted();
|
|
1209
1210
|
const response = await this.send({
|
|
1210
1211
|
type: 'connect',
|
|
1211
|
-
config
|
|
1212
|
+
config
|
|
1212
1213
|
});
|
|
1213
1214
|
if ('connected' !== response.type) throw new Error(`Expected connected response, got ${response.type}`);
|
|
1214
1215
|
this.connected = true;
|
|
@@ -1482,10 +1483,9 @@ const DEFAULT_SCROLL_STEP_AMOUNT = 120;
|
|
|
1482
1483
|
class RDPDevice {
|
|
1483
1484
|
describe() {
|
|
1484
1485
|
const port = this.options.port || 3389;
|
|
1485
|
-
const server = formatRdpServerAddress(this.options.host, port);
|
|
1486
1486
|
const username = this.options.username ? ` as ${this.options.username}` : '';
|
|
1487
1487
|
const session = this.connectionInfo?.sessionId ? ` [session ${this.connectionInfo.sessionId}]` : '';
|
|
1488
|
-
return `RDP Device ${
|
|
1488
|
+
return `RDP Device ${this.options.host}:${port}${username}${session}`;
|
|
1489
1489
|
}
|
|
1490
1490
|
async connect() {
|
|
1491
1491
|
this.throwIfDestroyed();
|
|
@@ -1525,11 +1525,10 @@ class RDPDevice {
|
|
|
1525
1525
|
call: async ()=>{
|
|
1526
1526
|
this.assertConnected();
|
|
1527
1527
|
const size = await this.size();
|
|
1528
|
-
const server = this.connectionInfo?.server || formatRdpServerAddress(this.options.host, this.options.port || 3389);
|
|
1529
1528
|
return [
|
|
1530
1529
|
{
|
|
1531
1530
|
id: this.connectionInfo?.sessionId || this.options.host,
|
|
1532
|
-
name: `RDP ${server} (${size.width}x${size.height})`,
|
|
1531
|
+
name: `RDP ${this.connectionInfo?.server || this.options.host} (${size.width}x${size.height})`,
|
|
1533
1532
|
primary: true
|
|
1534
1533
|
}
|
|
1535
1534
|
];
|
|
@@ -1727,12 +1726,11 @@ class RDPDevice {
|
|
|
1727
1726
|
}
|
|
1728
1727
|
}
|
|
1729
1728
|
});
|
|
1730
|
-
const normalizedOptions = normalizeRdpConnectionConfig(options);
|
|
1731
1729
|
this.options = {
|
|
1732
1730
|
port: 3389,
|
|
1733
1731
|
securityProtocol: 'auto',
|
|
1734
1732
|
ignoreCertificate: false,
|
|
1735
|
-
...
|
|
1733
|
+
...options
|
|
1736
1734
|
};
|
|
1737
1735
|
this.backend = options.backend || createDefaultRDPBackendClient();
|
|
1738
1736
|
}
|
|
@@ -1812,11 +1810,10 @@ function adaptComputerInitArgs(extracted) {
|
|
|
1812
1810
|
if (!extracted || 0 === Object.keys(extracted).length) return;
|
|
1813
1811
|
if (extracted.host) {
|
|
1814
1812
|
const { displayId: _d, headless: _h, ...rdpFields } = extracted;
|
|
1815
|
-
const host = normalizeRdpHost(extracted.host);
|
|
1816
1813
|
return {
|
|
1817
1814
|
mode: 'rdp',
|
|
1818
1815
|
...rdpFields,
|
|
1819
|
-
host
|
|
1816
|
+
host: extracted.host
|
|
1820
1817
|
};
|
|
1821
1818
|
}
|
|
1822
1819
|
return {
|
|
@@ -1832,15 +1829,15 @@ function shouldRetargetAgent(opts) {
|
|
|
1832
1829
|
}
|
|
1833
1830
|
function describeConnectTarget(opts) {
|
|
1834
1831
|
if (opts?.mode === 'rdp') {
|
|
1835
|
-
const
|
|
1832
|
+
const portSuffix = opts.port ? `:${opts.port}` : '';
|
|
1836
1833
|
const userSuffix = opts.username ? ` as ${opts.username}` : '';
|
|
1837
|
-
return ` via RDP (${
|
|
1834
|
+
return ` via RDP (${opts.host}${portSuffix}${userSuffix})`;
|
|
1838
1835
|
}
|
|
1839
1836
|
if (opts?.mode === 'local' && opts.displayId) return ` (Display: ${opts.displayId})`;
|
|
1840
1837
|
return ' (Primary display)';
|
|
1841
1838
|
}
|
|
1842
1839
|
function getCliReportSessionTarget(opts) {
|
|
1843
|
-
if (opts?.mode === 'rdp') return `rdp:${
|
|
1840
|
+
if (opts?.mode === 'rdp') return `rdp:${opts.host}`;
|
|
1844
1841
|
if (opts?.mode === 'local' && opts.displayId) return opts.displayId;
|
|
1845
1842
|
return 'primary';
|
|
1846
1843
|
}
|
|
@@ -1962,7 +1959,7 @@ class ComputerMCPServer extends mcp_namespaceObject.BaseMCPServer {
|
|
|
1962
1959
|
constructor(toolsManager){
|
|
1963
1960
|
super({
|
|
1964
1961
|
name: '@midscene/computer-mcp',
|
|
1965
|
-
version: "1.9.2
|
|
1962
|
+
version: "1.9.2",
|
|
1966
1963
|
description: 'Control the computer desktop using natural language commands'
|
|
1967
1964
|
}, toolsManager);
|
|
1968
1965
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -78,6 +78,8 @@ export declare class ComputerDevice implements AbstractInterface {
|
|
|
78
78
|
* to avoid focus issues with system overlays (e.g. Spotlight).
|
|
79
79
|
*/
|
|
80
80
|
private useAppleScript;
|
|
81
|
+
/** Cached result of the elevation check; see isRunningAsAdmin(). */
|
|
82
|
+
private adminCheckCache?;
|
|
81
83
|
uri?: string;
|
|
82
84
|
readonly inputPrimitives: ComputerInputPrimitives;
|
|
83
85
|
constructor(options?: ComputerDeviceOpt);
|
|
@@ -91,6 +93,10 @@ export declare class ComputerDevice implements AbstractInterface {
|
|
|
91
93
|
/**
|
|
92
94
|
* Check if the current process is running with Administrator privileges.
|
|
93
95
|
* Uses "net session" which succeeds only when elevated.
|
|
96
|
+
*
|
|
97
|
+
* The result is cached because elevation cannot change during the process
|
|
98
|
+
* lifetime, and the underlying `execSync('net session')` is a blocking
|
|
99
|
+
* subprocess spawn that should not run on every connect / health check.
|
|
94
100
|
*/
|
|
95
101
|
private isRunningAsAdmin;
|
|
96
102
|
screenshotBase64(): Promise<string>;
|