@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/bin/linux/rdp-helper
CHANGED
|
Binary file
|
package/dist/es/cli.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { getDebug } from "@midscene/shared/logger";
|
|
|
4
4
|
import { BaseMidsceneTools } from "@midscene/shared/mcp/base-tools";
|
|
5
5
|
import { Agent } from "@midscene/core/agent";
|
|
6
6
|
import { execFileSync, execSync, spawn, spawnSync } from "node:child_process";
|
|
7
|
-
import { existsSync } from "node:fs";
|
|
7
|
+
import { chmodSync, existsSync, statSync } from "node:fs";
|
|
8
8
|
import { createRequire } from "node:module";
|
|
9
9
|
import { dirname, resolve as external_node_path_resolve } from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
@@ -350,9 +350,13 @@ async function getLibnut() {
|
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
352
|
const debugDevice = getDebug('computer:device');
|
|
353
|
+
const warnDevice = getDebug('computer:device', {
|
|
354
|
+
console: true
|
|
355
|
+
});
|
|
353
356
|
const debugComputerInput = getDebug('computer:input', {
|
|
354
357
|
console: true
|
|
355
358
|
});
|
|
359
|
+
const WINDOWS_UIPI_DOC_URL = 'https://midscenejs.com/computer-getting-started#windows-clicks-have-no-effect-on-some-apps';
|
|
356
360
|
function resolvePackageRoot(helperName) {
|
|
357
361
|
const require = createRequire(import.meta.url);
|
|
358
362
|
let pkgRoot = null;
|
|
@@ -392,6 +396,15 @@ function getPhasedScrollBinary() {
|
|
|
392
396
|
phasedScrollBinaryPath = null;
|
|
393
397
|
return null;
|
|
394
398
|
}
|
|
399
|
+
try {
|
|
400
|
+
const st = statSync(binPath);
|
|
401
|
+
if ((73 & st.mode) === 0) {
|
|
402
|
+
chmodSync(binPath, 493);
|
|
403
|
+
debugDevice('phased-scroll: restored executable bit on', binPath);
|
|
404
|
+
}
|
|
405
|
+
} catch (err) {
|
|
406
|
+
debugDevice('phased-scroll: chmod self-heal failed', err);
|
|
407
|
+
}
|
|
395
408
|
phasedScrollBinaryPath = binPath;
|
|
396
409
|
return binPath;
|
|
397
410
|
}
|
|
@@ -519,9 +532,10 @@ function runPhasedScroll(direction, pixels, steps) {
|
|
|
519
532
|
if (0 === res.status) return true;
|
|
520
533
|
if (!phasedScrollExecWarned) {
|
|
521
534
|
phasedScrollExecWarned = true;
|
|
522
|
-
|
|
535
|
+
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)';
|
|
536
|
+
console.warn(`[@midscene/computer] phased-scroll helper failed (exit=${res.status}, signal=${res.signal ?? 'none'}); falling back to keyboard/libnut. ${hint}`);
|
|
523
537
|
}
|
|
524
|
-
debugDevice('phased-scroll exited non-zero', res.status, res.error);
|
|
538
|
+
debugDevice('phased-scroll exited non-zero', res.status, res.signal, res.error);
|
|
525
539
|
return false;
|
|
526
540
|
} catch (err) {
|
|
527
541
|
if (!phasedScrollExecWarned) {
|
|
@@ -642,7 +656,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
642
656
|
}
|
|
643
657
|
async healthCheck() {
|
|
644
658
|
console.log('[HealthCheck] Starting health check...');
|
|
645
|
-
console.log("[HealthCheck] @midscene/computer v1.9.2
|
|
659
|
+
console.log("[HealthCheck] @midscene/computer v1.9.2");
|
|
646
660
|
console.log('[HealthCheck] Taking screenshot...');
|
|
647
661
|
const screenshotTimeout = 15000;
|
|
648
662
|
let timeoutId;
|
|
@@ -670,13 +684,15 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
670
684
|
const deltaY = Math.abs(movedPos.y - targetY);
|
|
671
685
|
if (deltaX > 5 || deltaY > 5) {
|
|
672
686
|
const msg = `[HealthCheck] WARNING: Mouse control may not be working. Expected (${targetX}, ${targetY}), got (${movedPos.x}, ${movedPos.y}), delta=(${deltaX}, ${deltaY})`;
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
687
|
+
warnDevice(msg);
|
|
688
|
+
}
|
|
689
|
+
if ('win32' === process.platform && !this.isRunningAsAdmin()) {
|
|
690
|
+
const hint = [
|
|
691
|
+
'Heads-up: Midscene is not running as Administrator.',
|
|
692
|
+
'If clicks or key presses have no effect while the cursor still moves to the right position,',
|
|
693
|
+
`see the Windows permission troubleshooting guide: ${WINDOWS_UIPI_DOC_URL}`
|
|
694
|
+
].join(' ');
|
|
695
|
+
warnDevice(`[HealthCheck] ${hint}`);
|
|
680
696
|
}
|
|
681
697
|
this.inputDriver.moveMouse(startPos.x, startPos.y);
|
|
682
698
|
console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
|
|
@@ -693,14 +709,16 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
693
709
|
}
|
|
694
710
|
isRunningAsAdmin() {
|
|
695
711
|
if ('win32' !== process.platform) return false;
|
|
712
|
+
if (void 0 !== this.adminCheckCache) return this.adminCheckCache;
|
|
696
713
|
try {
|
|
697
714
|
execSync('net session', {
|
|
698
715
|
stdio: 'pipe'
|
|
699
716
|
});
|
|
700
|
-
|
|
717
|
+
this.adminCheckCache = true;
|
|
701
718
|
} catch {
|
|
702
|
-
|
|
719
|
+
this.adminCheckCache = false;
|
|
703
720
|
}
|
|
721
|
+
return this.adminCheckCache;
|
|
704
722
|
}
|
|
705
723
|
async screenshotBase64() {
|
|
706
724
|
if (this.destroyed) throw new Error('ComputerDevice has been destroyed');
|
|
@@ -945,6 +963,7 @@ Original error: ${lastRawMessage}`);
|
|
|
945
963
|
debug: (message)=>debugDevice(message)
|
|
946
964
|
}));
|
|
947
965
|
device_define_property(this, "useAppleScript", void 0);
|
|
966
|
+
device_define_property(this, "adminCheckCache", void 0);
|
|
948
967
|
device_define_property(this, "uri", void 0);
|
|
949
968
|
device_define_property(this, "inputPrimitives", {
|
|
950
969
|
pointer: {
|
|
@@ -1095,24 +1114,6 @@ function createPlatformActions() {
|
|
|
1095
1114
|
})
|
|
1096
1115
|
};
|
|
1097
1116
|
}
|
|
1098
|
-
function normalizeRdpHost(host) {
|
|
1099
|
-
const trimmed = host.trim();
|
|
1100
|
-
if (trimmed.length >= 2 && trimmed.startsWith('[') && trimmed.endsWith(']') && trimmed.includes(':')) return trimmed.slice(1, -1);
|
|
1101
|
-
return trimmed;
|
|
1102
|
-
}
|
|
1103
|
-
function formatRdpHost(host) {
|
|
1104
|
-
const normalizedHost = normalizeRdpHost(host);
|
|
1105
|
-
return normalizedHost.includes(':') ? `[${normalizedHost}]` : normalizedHost;
|
|
1106
|
-
}
|
|
1107
|
-
function formatRdpServerAddress(host, port) {
|
|
1108
|
-
return `${formatRdpHost(host)}:${port}`;
|
|
1109
|
-
}
|
|
1110
|
-
function normalizeRdpConnectionConfig(config) {
|
|
1111
|
-
return {
|
|
1112
|
-
...config,
|
|
1113
|
-
host: normalizeRdpHost(config.host)
|
|
1114
|
-
};
|
|
1115
|
-
}
|
|
1116
1117
|
const platformBinaryMap = {
|
|
1117
1118
|
darwin: {
|
|
1118
1119
|
directory: 'darwin',
|
|
@@ -1167,7 +1168,7 @@ class HelperProcessRDPBackendClient {
|
|
|
1167
1168
|
await this.ensureHelperStarted();
|
|
1168
1169
|
const response = await this.send({
|
|
1169
1170
|
type: 'connect',
|
|
1170
|
-
config
|
|
1171
|
+
config
|
|
1171
1172
|
});
|
|
1172
1173
|
if ('connected' !== response.type) throw new Error(`Expected connected response, got ${response.type}`);
|
|
1173
1174
|
this.connected = true;
|
|
@@ -1441,10 +1442,9 @@ const DEFAULT_SCROLL_STEP_AMOUNT = 120;
|
|
|
1441
1442
|
class RDPDevice {
|
|
1442
1443
|
describe() {
|
|
1443
1444
|
const port = this.options.port || 3389;
|
|
1444
|
-
const server = formatRdpServerAddress(this.options.host, port);
|
|
1445
1445
|
const username = this.options.username ? ` as ${this.options.username}` : '';
|
|
1446
1446
|
const session = this.connectionInfo?.sessionId ? ` [session ${this.connectionInfo.sessionId}]` : '';
|
|
1447
|
-
return `RDP Device ${
|
|
1447
|
+
return `RDP Device ${this.options.host}:${port}${username}${session}`;
|
|
1448
1448
|
}
|
|
1449
1449
|
async connect() {
|
|
1450
1450
|
this.throwIfDestroyed();
|
|
@@ -1484,11 +1484,10 @@ class RDPDevice {
|
|
|
1484
1484
|
call: async ()=>{
|
|
1485
1485
|
this.assertConnected();
|
|
1486
1486
|
const size = await this.size();
|
|
1487
|
-
const server = this.connectionInfo?.server || formatRdpServerAddress(this.options.host, this.options.port || 3389);
|
|
1488
1487
|
return [
|
|
1489
1488
|
{
|
|
1490
1489
|
id: this.connectionInfo?.sessionId || this.options.host,
|
|
1491
|
-
name: `RDP ${server} (${size.width}x${size.height})`,
|
|
1490
|
+
name: `RDP ${this.connectionInfo?.server || this.options.host} (${size.width}x${size.height})`,
|
|
1492
1491
|
primary: true
|
|
1493
1492
|
}
|
|
1494
1493
|
];
|
|
@@ -1686,12 +1685,11 @@ class RDPDevice {
|
|
|
1686
1685
|
}
|
|
1687
1686
|
}
|
|
1688
1687
|
});
|
|
1689
|
-
const normalizedOptions = normalizeRdpConnectionConfig(options);
|
|
1690
1688
|
this.options = {
|
|
1691
1689
|
port: 3389,
|
|
1692
1690
|
securityProtocol: 'auto',
|
|
1693
1691
|
ignoreCertificate: false,
|
|
1694
|
-
...
|
|
1692
|
+
...options
|
|
1695
1693
|
};
|
|
1696
1694
|
this.backend = options.backend || createDefaultRDPBackendClient();
|
|
1697
1695
|
}
|
|
@@ -1769,11 +1767,10 @@ function adaptComputerInitArgs(extracted) {
|
|
|
1769
1767
|
if (!extracted || 0 === Object.keys(extracted).length) return;
|
|
1770
1768
|
if (extracted.host) {
|
|
1771
1769
|
const { displayId: _d, headless: _h, ...rdpFields } = extracted;
|
|
1772
|
-
const host = normalizeRdpHost(extracted.host);
|
|
1773
1770
|
return {
|
|
1774
1771
|
mode: 'rdp',
|
|
1775
1772
|
...rdpFields,
|
|
1776
|
-
host
|
|
1773
|
+
host: extracted.host
|
|
1777
1774
|
};
|
|
1778
1775
|
}
|
|
1779
1776
|
return {
|
|
@@ -1789,15 +1786,15 @@ function shouldRetargetAgent(opts) {
|
|
|
1789
1786
|
}
|
|
1790
1787
|
function describeConnectTarget(opts) {
|
|
1791
1788
|
if (opts?.mode === 'rdp') {
|
|
1792
|
-
const
|
|
1789
|
+
const portSuffix = opts.port ? `:${opts.port}` : '';
|
|
1793
1790
|
const userSuffix = opts.username ? ` as ${opts.username}` : '';
|
|
1794
|
-
return ` via RDP (${
|
|
1791
|
+
return ` via RDP (${opts.host}${portSuffix}${userSuffix})`;
|
|
1795
1792
|
}
|
|
1796
1793
|
if (opts?.mode === 'local' && opts.displayId) return ` (Display: ${opts.displayId})`;
|
|
1797
1794
|
return ' (Primary display)';
|
|
1798
1795
|
}
|
|
1799
1796
|
function getCliReportSessionTarget(opts) {
|
|
1800
|
-
if (opts?.mode === 'rdp') return `rdp:${
|
|
1797
|
+
if (opts?.mode === 'rdp') return `rdp:${opts.host}`;
|
|
1801
1798
|
if (opts?.mode === 'local' && opts.displayId) return opts.displayId;
|
|
1802
1799
|
return 'primary';
|
|
1803
1800
|
}
|
|
@@ -1915,7 +1912,7 @@ class ComputerMidsceneTools extends BaseMidsceneTools {
|
|
|
1915
1912
|
const tools = new ComputerMidsceneTools();
|
|
1916
1913
|
runToolsCLI(tools, 'midscene-computer', {
|
|
1917
1914
|
stripPrefix: 'computer_',
|
|
1918
|
-
version: "1.9.2
|
|
1915
|
+
version: "1.9.2",
|
|
1919
1916
|
extraCommands: createReportCliCommands()
|
|
1920
1917
|
}).catch((e)=>{
|
|
1921
1918
|
process.exit(reportCLIError(e));
|
package/dist/es/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync, execSync, spawn, spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { chmodSync, existsSync, statSync } from "node:fs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { dirname, resolve as external_node_path_resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -350,9 +350,13 @@ async function getLibnut() {
|
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
352
|
const debugDevice = getDebug('computer:device');
|
|
353
|
+
const warnDevice = getDebug('computer:device', {
|
|
354
|
+
console: true
|
|
355
|
+
});
|
|
353
356
|
const debugComputerInput = getDebug('computer:input', {
|
|
354
357
|
console: true
|
|
355
358
|
});
|
|
359
|
+
const WINDOWS_UIPI_DOC_URL = 'https://midscenejs.com/computer-getting-started#windows-clicks-have-no-effect-on-some-apps';
|
|
356
360
|
function resolvePackageRoot(helperName) {
|
|
357
361
|
const require = createRequire(import.meta.url);
|
|
358
362
|
let pkgRoot = null;
|
|
@@ -392,6 +396,15 @@ function getPhasedScrollBinary() {
|
|
|
392
396
|
phasedScrollBinaryPath = null;
|
|
393
397
|
return null;
|
|
394
398
|
}
|
|
399
|
+
try {
|
|
400
|
+
const st = statSync(binPath);
|
|
401
|
+
if ((73 & st.mode) === 0) {
|
|
402
|
+
chmodSync(binPath, 493);
|
|
403
|
+
debugDevice('phased-scroll: restored executable bit on', binPath);
|
|
404
|
+
}
|
|
405
|
+
} catch (err) {
|
|
406
|
+
debugDevice('phased-scroll: chmod self-heal failed', err);
|
|
407
|
+
}
|
|
395
408
|
phasedScrollBinaryPath = binPath;
|
|
396
409
|
return binPath;
|
|
397
410
|
}
|
|
@@ -519,9 +532,10 @@ function runPhasedScroll(direction, pixels, steps) {
|
|
|
519
532
|
if (0 === res.status) return true;
|
|
520
533
|
if (!phasedScrollExecWarned) {
|
|
521
534
|
phasedScrollExecWarned = true;
|
|
522
|
-
|
|
535
|
+
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)';
|
|
536
|
+
console.warn(`[@midscene/computer] phased-scroll helper failed (exit=${res.status}, signal=${res.signal ?? 'none'}); falling back to keyboard/libnut. ${hint}`);
|
|
523
537
|
}
|
|
524
|
-
debugDevice('phased-scroll exited non-zero', res.status, res.error);
|
|
538
|
+
debugDevice('phased-scroll exited non-zero', res.status, res.signal, res.error);
|
|
525
539
|
return false;
|
|
526
540
|
} catch (err) {
|
|
527
541
|
if (!phasedScrollExecWarned) {
|
|
@@ -642,7 +656,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
642
656
|
}
|
|
643
657
|
async healthCheck() {
|
|
644
658
|
console.log('[HealthCheck] Starting health check...');
|
|
645
|
-
console.log("[HealthCheck] @midscene/computer v1.9.2
|
|
659
|
+
console.log("[HealthCheck] @midscene/computer v1.9.2");
|
|
646
660
|
console.log('[HealthCheck] Taking screenshot...');
|
|
647
661
|
const screenshotTimeout = 15000;
|
|
648
662
|
let timeoutId;
|
|
@@ -670,13 +684,15 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
670
684
|
const deltaY = Math.abs(movedPos.y - targetY);
|
|
671
685
|
if (deltaX > 5 || deltaY > 5) {
|
|
672
686
|
const msg = `[HealthCheck] WARNING: Mouse control may not be working. Expected (${targetX}, ${targetY}), got (${movedPos.x}, ${movedPos.y}), delta=(${deltaX}, ${deltaY})`;
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
687
|
+
warnDevice(msg);
|
|
688
|
+
}
|
|
689
|
+
if ('win32' === process.platform && !this.isRunningAsAdmin()) {
|
|
690
|
+
const hint = [
|
|
691
|
+
'Heads-up: Midscene is not running as Administrator.',
|
|
692
|
+
'If clicks or key presses have no effect while the cursor still moves to the right position,',
|
|
693
|
+
`see the Windows permission troubleshooting guide: ${WINDOWS_UIPI_DOC_URL}`
|
|
694
|
+
].join(' ');
|
|
695
|
+
warnDevice(`[HealthCheck] ${hint}`);
|
|
680
696
|
}
|
|
681
697
|
this.inputDriver.moveMouse(startPos.x, startPos.y);
|
|
682
698
|
console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
|
|
@@ -693,14 +709,16 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
693
709
|
}
|
|
694
710
|
isRunningAsAdmin() {
|
|
695
711
|
if ('win32' !== process.platform) return false;
|
|
712
|
+
if (void 0 !== this.adminCheckCache) return this.adminCheckCache;
|
|
696
713
|
try {
|
|
697
714
|
execSync('net session', {
|
|
698
715
|
stdio: 'pipe'
|
|
699
716
|
});
|
|
700
|
-
|
|
717
|
+
this.adminCheckCache = true;
|
|
701
718
|
} catch {
|
|
702
|
-
|
|
719
|
+
this.adminCheckCache = false;
|
|
703
720
|
}
|
|
721
|
+
return this.adminCheckCache;
|
|
704
722
|
}
|
|
705
723
|
async screenshotBase64() {
|
|
706
724
|
if (this.destroyed) throw new Error('ComputerDevice has been destroyed');
|
|
@@ -945,6 +963,7 @@ Original error: ${lastRawMessage}`);
|
|
|
945
963
|
debug: (message)=>debugDevice(message)
|
|
946
964
|
}));
|
|
947
965
|
device_define_property(this, "useAppleScript", void 0);
|
|
966
|
+
device_define_property(this, "adminCheckCache", void 0);
|
|
948
967
|
device_define_property(this, "uri", void 0);
|
|
949
968
|
device_define_property(this, "inputPrimitives", {
|
|
950
969
|
pointer: {
|
|
@@ -1095,24 +1114,6 @@ function createPlatformActions() {
|
|
|
1095
1114
|
})
|
|
1096
1115
|
};
|
|
1097
1116
|
}
|
|
1098
|
-
function normalizeRdpHost(host) {
|
|
1099
|
-
const trimmed = host.trim();
|
|
1100
|
-
if (trimmed.length >= 2 && trimmed.startsWith('[') && trimmed.endsWith(']') && trimmed.includes(':')) return trimmed.slice(1, -1);
|
|
1101
|
-
return trimmed;
|
|
1102
|
-
}
|
|
1103
|
-
function formatRdpHost(host) {
|
|
1104
|
-
const normalizedHost = normalizeRdpHost(host);
|
|
1105
|
-
return normalizedHost.includes(':') ? `[${normalizedHost}]` : normalizedHost;
|
|
1106
|
-
}
|
|
1107
|
-
function formatRdpServerAddress(host, port) {
|
|
1108
|
-
return `${formatRdpHost(host)}:${port}`;
|
|
1109
|
-
}
|
|
1110
|
-
function normalizeRdpConnectionConfig(config) {
|
|
1111
|
-
return {
|
|
1112
|
-
...config,
|
|
1113
|
-
host: normalizeRdpHost(config.host)
|
|
1114
|
-
};
|
|
1115
|
-
}
|
|
1116
1117
|
const platformBinaryMap = {
|
|
1117
1118
|
darwin: {
|
|
1118
1119
|
directory: 'darwin',
|
|
@@ -1210,7 +1211,7 @@ class HelperProcessRDPBackendClient {
|
|
|
1210
1211
|
await this.ensureHelperStarted();
|
|
1211
1212
|
const response = await this.send({
|
|
1212
1213
|
type: 'connect',
|
|
1213
|
-
config
|
|
1214
|
+
config
|
|
1214
1215
|
});
|
|
1215
1216
|
if ('connected' !== response.type) throw new Error(`Expected connected response, got ${response.type}`);
|
|
1216
1217
|
this.connected = true;
|
|
@@ -1484,10 +1485,9 @@ const DEFAULT_SCROLL_STEP_AMOUNT = 120;
|
|
|
1484
1485
|
class RDPDevice {
|
|
1485
1486
|
describe() {
|
|
1486
1487
|
const port = this.options.port || 3389;
|
|
1487
|
-
const server = formatRdpServerAddress(this.options.host, port);
|
|
1488
1488
|
const username = this.options.username ? ` as ${this.options.username}` : '';
|
|
1489
1489
|
const session = this.connectionInfo?.sessionId ? ` [session ${this.connectionInfo.sessionId}]` : '';
|
|
1490
|
-
return `RDP Device ${
|
|
1490
|
+
return `RDP Device ${this.options.host}:${port}${username}${session}`;
|
|
1491
1491
|
}
|
|
1492
1492
|
async connect() {
|
|
1493
1493
|
this.throwIfDestroyed();
|
|
@@ -1527,11 +1527,10 @@ class RDPDevice {
|
|
|
1527
1527
|
call: async ()=>{
|
|
1528
1528
|
this.assertConnected();
|
|
1529
1529
|
const size = await this.size();
|
|
1530
|
-
const server = this.connectionInfo?.server || formatRdpServerAddress(this.options.host, this.options.port || 3389);
|
|
1531
1530
|
return [
|
|
1532
1531
|
{
|
|
1533
1532
|
id: this.connectionInfo?.sessionId || this.options.host,
|
|
1534
|
-
name: `RDP ${server} (${size.width}x${size.height})`,
|
|
1533
|
+
name: `RDP ${this.connectionInfo?.server || this.options.host} (${size.width}x${size.height})`,
|
|
1535
1534
|
primary: true
|
|
1536
1535
|
}
|
|
1537
1536
|
];
|
|
@@ -1729,12 +1728,11 @@ class RDPDevice {
|
|
|
1729
1728
|
}
|
|
1730
1729
|
}
|
|
1731
1730
|
});
|
|
1732
|
-
const normalizedOptions = normalizeRdpConnectionConfig(options);
|
|
1733
1731
|
this.options = {
|
|
1734
1732
|
port: 3389,
|
|
1735
1733
|
securityProtocol: 'auto',
|
|
1736
1734
|
ignoreCertificate: false,
|
|
1737
|
-
...
|
|
1735
|
+
...options
|
|
1738
1736
|
};
|
|
1739
1737
|
this.backend = options.backend || createDefaultRDPBackendClient();
|
|
1740
1738
|
}
|
|
@@ -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
|
}
|
|
@@ -1956,7 +1953,7 @@ class ComputerMidsceneTools extends BaseMidsceneTools {
|
|
|
1956
1953
|
}
|
|
1957
1954
|
}
|
|
1958
1955
|
function version() {
|
|
1959
|
-
const currentVersion = "1.9.2
|
|
1956
|
+
const currentVersion = "1.9.2";
|
|
1960
1957
|
console.log(`@midscene/computer v${currentVersion}`);
|
|
1961
1958
|
return currentVersion;
|
|
1962
1959
|
}
|
package/dist/es/mcp-server.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BaseMCPServer, createMCPServerLauncher } from "@midscene/shared/mcp";
|
|
2
2
|
import { Agent } from "@midscene/core/agent";
|
|
3
3
|
import { execFileSync, execSync, spawn, spawnSync } from "node:child_process";
|
|
4
|
-
import { existsSync } from "node:fs";
|
|
4
|
+
import { chmodSync, existsSync, statSync } from "node:fs";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { dirname, resolve as external_node_path_resolve } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
@@ -350,9 +350,13 @@ async function getLibnut() {
|
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
352
|
const debugDevice = getDebug('computer:device');
|
|
353
|
+
const warnDevice = getDebug('computer:device', {
|
|
354
|
+
console: true
|
|
355
|
+
});
|
|
353
356
|
const debugComputerInput = getDebug('computer:input', {
|
|
354
357
|
console: true
|
|
355
358
|
});
|
|
359
|
+
const WINDOWS_UIPI_DOC_URL = 'https://midscenejs.com/computer-getting-started#windows-clicks-have-no-effect-on-some-apps';
|
|
356
360
|
function resolvePackageRoot(helperName) {
|
|
357
361
|
const require = createRequire(import.meta.url);
|
|
358
362
|
let pkgRoot = null;
|
|
@@ -392,6 +396,15 @@ function getPhasedScrollBinary() {
|
|
|
392
396
|
phasedScrollBinaryPath = null;
|
|
393
397
|
return null;
|
|
394
398
|
}
|
|
399
|
+
try {
|
|
400
|
+
const st = statSync(binPath);
|
|
401
|
+
if ((73 & st.mode) === 0) {
|
|
402
|
+
chmodSync(binPath, 493);
|
|
403
|
+
debugDevice('phased-scroll: restored executable bit on', binPath);
|
|
404
|
+
}
|
|
405
|
+
} catch (err) {
|
|
406
|
+
debugDevice('phased-scroll: chmod self-heal failed', err);
|
|
407
|
+
}
|
|
395
408
|
phasedScrollBinaryPath = binPath;
|
|
396
409
|
return binPath;
|
|
397
410
|
}
|
|
@@ -519,9 +532,10 @@ function runPhasedScroll(direction, pixels, steps) {
|
|
|
519
532
|
if (0 === res.status) return true;
|
|
520
533
|
if (!phasedScrollExecWarned) {
|
|
521
534
|
phasedScrollExecWarned = true;
|
|
522
|
-
|
|
535
|
+
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)';
|
|
536
|
+
console.warn(`[@midscene/computer] phased-scroll helper failed (exit=${res.status}, signal=${res.signal ?? 'none'}); falling back to keyboard/libnut. ${hint}`);
|
|
523
537
|
}
|
|
524
|
-
debugDevice('phased-scroll exited non-zero', res.status, res.error);
|
|
538
|
+
debugDevice('phased-scroll exited non-zero', res.status, res.signal, res.error);
|
|
525
539
|
return false;
|
|
526
540
|
} catch (err) {
|
|
527
541
|
if (!phasedScrollExecWarned) {
|
|
@@ -642,7 +656,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
642
656
|
}
|
|
643
657
|
async healthCheck() {
|
|
644
658
|
console.log('[HealthCheck] Starting health check...');
|
|
645
|
-
console.log("[HealthCheck] @midscene/computer v1.9.2
|
|
659
|
+
console.log("[HealthCheck] @midscene/computer v1.9.2");
|
|
646
660
|
console.log('[HealthCheck] Taking screenshot...');
|
|
647
661
|
const screenshotTimeout = 15000;
|
|
648
662
|
let timeoutId;
|
|
@@ -670,13 +684,15 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
670
684
|
const deltaY = Math.abs(movedPos.y - targetY);
|
|
671
685
|
if (deltaX > 5 || deltaY > 5) {
|
|
672
686
|
const msg = `[HealthCheck] WARNING: Mouse control may not be working. Expected (${targetX}, ${targetY}), got (${movedPos.x}, ${movedPos.y}), delta=(${deltaX}, ${deltaY})`;
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
687
|
+
warnDevice(msg);
|
|
688
|
+
}
|
|
689
|
+
if ('win32' === process.platform && !this.isRunningAsAdmin()) {
|
|
690
|
+
const hint = [
|
|
691
|
+
'Heads-up: Midscene is not running as Administrator.',
|
|
692
|
+
'If clicks or key presses have no effect while the cursor still moves to the right position,',
|
|
693
|
+
`see the Windows permission troubleshooting guide: ${WINDOWS_UIPI_DOC_URL}`
|
|
694
|
+
].join(' ');
|
|
695
|
+
warnDevice(`[HealthCheck] ${hint}`);
|
|
680
696
|
}
|
|
681
697
|
this.inputDriver.moveMouse(startPos.x, startPos.y);
|
|
682
698
|
console.log(`[HealthCheck] Mouse restored to (${startPos.x}, ${startPos.y})`);
|
|
@@ -693,14 +709,16 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
|
|
|
693
709
|
}
|
|
694
710
|
isRunningAsAdmin() {
|
|
695
711
|
if ('win32' !== process.platform) return false;
|
|
712
|
+
if (void 0 !== this.adminCheckCache) return this.adminCheckCache;
|
|
696
713
|
try {
|
|
697
714
|
execSync('net session', {
|
|
698
715
|
stdio: 'pipe'
|
|
699
716
|
});
|
|
700
|
-
|
|
717
|
+
this.adminCheckCache = true;
|
|
701
718
|
} catch {
|
|
702
|
-
|
|
719
|
+
this.adminCheckCache = false;
|
|
703
720
|
}
|
|
721
|
+
return this.adminCheckCache;
|
|
704
722
|
}
|
|
705
723
|
async screenshotBase64() {
|
|
706
724
|
if (this.destroyed) throw new Error('ComputerDevice has been destroyed');
|
|
@@ -945,6 +963,7 @@ Original error: ${lastRawMessage}`);
|
|
|
945
963
|
debug: (message)=>debugDevice(message)
|
|
946
964
|
}));
|
|
947
965
|
device_define_property(this, "useAppleScript", void 0);
|
|
966
|
+
device_define_property(this, "adminCheckCache", void 0);
|
|
948
967
|
device_define_property(this, "uri", void 0);
|
|
949
968
|
device_define_property(this, "inputPrimitives", {
|
|
950
969
|
pointer: {
|
|
@@ -1095,24 +1114,6 @@ function createPlatformActions() {
|
|
|
1095
1114
|
})
|
|
1096
1115
|
};
|
|
1097
1116
|
}
|
|
1098
|
-
function normalizeRdpHost(host) {
|
|
1099
|
-
const trimmed = host.trim();
|
|
1100
|
-
if (trimmed.length >= 2 && trimmed.startsWith('[') && trimmed.endsWith(']') && trimmed.includes(':')) return trimmed.slice(1, -1);
|
|
1101
|
-
return trimmed;
|
|
1102
|
-
}
|
|
1103
|
-
function formatRdpHost(host) {
|
|
1104
|
-
const normalizedHost = normalizeRdpHost(host);
|
|
1105
|
-
return normalizedHost.includes(':') ? `[${normalizedHost}]` : normalizedHost;
|
|
1106
|
-
}
|
|
1107
|
-
function formatRdpServerAddress(host, port) {
|
|
1108
|
-
return `${formatRdpHost(host)}:${port}`;
|
|
1109
|
-
}
|
|
1110
|
-
function normalizeRdpConnectionConfig(config) {
|
|
1111
|
-
return {
|
|
1112
|
-
...config,
|
|
1113
|
-
host: normalizeRdpHost(config.host)
|
|
1114
|
-
};
|
|
1115
|
-
}
|
|
1116
1117
|
const platformBinaryMap = {
|
|
1117
1118
|
darwin: {
|
|
1118
1119
|
directory: 'darwin',
|
|
@@ -1167,7 +1168,7 @@ class HelperProcessRDPBackendClient {
|
|
|
1167
1168
|
await this.ensureHelperStarted();
|
|
1168
1169
|
const response = await this.send({
|
|
1169
1170
|
type: 'connect',
|
|
1170
|
-
config
|
|
1171
|
+
config
|
|
1171
1172
|
});
|
|
1172
1173
|
if ('connected' !== response.type) throw new Error(`Expected connected response, got ${response.type}`);
|
|
1173
1174
|
this.connected = true;
|
|
@@ -1441,10 +1442,9 @@ const DEFAULT_SCROLL_STEP_AMOUNT = 120;
|
|
|
1441
1442
|
class RDPDevice {
|
|
1442
1443
|
describe() {
|
|
1443
1444
|
const port = this.options.port || 3389;
|
|
1444
|
-
const server = formatRdpServerAddress(this.options.host, port);
|
|
1445
1445
|
const username = this.options.username ? ` as ${this.options.username}` : '';
|
|
1446
1446
|
const session = this.connectionInfo?.sessionId ? ` [session ${this.connectionInfo.sessionId}]` : '';
|
|
1447
|
-
return `RDP Device ${
|
|
1447
|
+
return `RDP Device ${this.options.host}:${port}${username}${session}`;
|
|
1448
1448
|
}
|
|
1449
1449
|
async connect() {
|
|
1450
1450
|
this.throwIfDestroyed();
|
|
@@ -1484,11 +1484,10 @@ class RDPDevice {
|
|
|
1484
1484
|
call: async ()=>{
|
|
1485
1485
|
this.assertConnected();
|
|
1486
1486
|
const size = await this.size();
|
|
1487
|
-
const server = this.connectionInfo?.server || formatRdpServerAddress(this.options.host, this.options.port || 3389);
|
|
1488
1487
|
return [
|
|
1489
1488
|
{
|
|
1490
1489
|
id: this.connectionInfo?.sessionId || this.options.host,
|
|
1491
|
-
name: `RDP ${server} (${size.width}x${size.height})`,
|
|
1490
|
+
name: `RDP ${this.connectionInfo?.server || this.options.host} (${size.width}x${size.height})`,
|
|
1492
1491
|
primary: true
|
|
1493
1492
|
}
|
|
1494
1493
|
];
|
|
@@ -1686,12 +1685,11 @@ class RDPDevice {
|
|
|
1686
1685
|
}
|
|
1687
1686
|
}
|
|
1688
1687
|
});
|
|
1689
|
-
const normalizedOptions = normalizeRdpConnectionConfig(options);
|
|
1690
1688
|
this.options = {
|
|
1691
1689
|
port: 3389,
|
|
1692
1690
|
securityProtocol: 'auto',
|
|
1693
1691
|
ignoreCertificate: false,
|
|
1694
|
-
...
|
|
1692
|
+
...options
|
|
1695
1693
|
};
|
|
1696
1694
|
this.backend = options.backend || createDefaultRDPBackendClient();
|
|
1697
1695
|
}
|
|
@@ -1769,11 +1767,10 @@ function adaptComputerInitArgs(extracted) {
|
|
|
1769
1767
|
if (!extracted || 0 === Object.keys(extracted).length) return;
|
|
1770
1768
|
if (extracted.host) {
|
|
1771
1769
|
const { displayId: _d, headless: _h, ...rdpFields } = extracted;
|
|
1772
|
-
const host = normalizeRdpHost(extracted.host);
|
|
1773
1770
|
return {
|
|
1774
1771
|
mode: 'rdp',
|
|
1775
1772
|
...rdpFields,
|
|
1776
|
-
host
|
|
1773
|
+
host: extracted.host
|
|
1777
1774
|
};
|
|
1778
1775
|
}
|
|
1779
1776
|
return {
|
|
@@ -1789,15 +1786,15 @@ function shouldRetargetAgent(opts) {
|
|
|
1789
1786
|
}
|
|
1790
1787
|
function describeConnectTarget(opts) {
|
|
1791
1788
|
if (opts?.mode === 'rdp') {
|
|
1792
|
-
const
|
|
1789
|
+
const portSuffix = opts.port ? `:${opts.port}` : '';
|
|
1793
1790
|
const userSuffix = opts.username ? ` as ${opts.username}` : '';
|
|
1794
|
-
return ` via RDP (${
|
|
1791
|
+
return ` via RDP (${opts.host}${portSuffix}${userSuffix})`;
|
|
1795
1792
|
}
|
|
1796
1793
|
if (opts?.mode === 'local' && opts.displayId) return ` (Display: ${opts.displayId})`;
|
|
1797
1794
|
return ' (Primary display)';
|
|
1798
1795
|
}
|
|
1799
1796
|
function getCliReportSessionTarget(opts) {
|
|
1800
|
-
if (opts?.mode === 'rdp') return `rdp:${
|
|
1797
|
+
if (opts?.mode === 'rdp') return `rdp:${opts.host}`;
|
|
1801
1798
|
if (opts?.mode === 'local' && opts.displayId) return opts.displayId;
|
|
1802
1799
|
return 'primary';
|
|
1803
1800
|
}
|
|
@@ -1919,7 +1916,7 @@ class ComputerMCPServer extends BaseMCPServer {
|
|
|
1919
1916
|
constructor(toolsManager){
|
|
1920
1917
|
super({
|
|
1921
1918
|
name: '@midscene/computer-mcp',
|
|
1922
|
-
version: "1.9.2
|
|
1919
|
+
version: "1.9.2",
|
|
1923
1920
|
description: 'Control the computer desktop using natural language commands'
|
|
1924
1921
|
}, toolsManager);
|
|
1925
1922
|
}
|