@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/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
- console.warn(`[@midscene/computer] phased-scroll helper exited with status ${res.status}; falling back to keyboard/libnut. This usually means Accessibility permission has not been granted to the host process.`);
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-beta-20260608084200.0");
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
- console.warn(msg);
700
- debugDevice(msg);
701
- if ('win32' === process.platform && !this.isRunningAsAdmin()) {
702
- const hint = "Midscene is NOT running as Administrator. Windows blocks mouse/keyboard input to elevated (admin) applications from non-admin processes (UIPI). Please run your terminal or Node.js as Administrator and try again.";
703
- console.error(`\n[HealthCheck] ${hint}\n`);
704
- debugDevice(hint);
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
- return true;
743
+ this.adminCheckCache = true;
727
744
  } catch {
728
- return false;
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: normalizeRdpConnectionConfig(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 ${server}${username}${session}`;
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
- ...normalizedOptions
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 target = opts.port ? formatRdpServerAddress(opts.host, opts.port) : formatRdpHost(opts.host);
1816
+ const portSuffix = opts.port ? `:${opts.port}` : '';
1820
1817
  const userSuffix = opts.username ? ` as ${opts.username}` : '';
1821
- return ` via RDP (${target}${userSuffix})`;
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:${formatRdpHost(opts.host)}`;
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-beta-20260608084200.0",
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
- console.warn(`[@midscene/computer] phased-scroll helper exited with status ${res.status}; falling back to keyboard/libnut. This usually means Accessibility permission has not been granted to the host process.`);
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-beta-20260608084200.0");
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
- console.warn(msg);
727
- debugDevice(msg);
728
- if ('win32' === process.platform && !this.isRunningAsAdmin()) {
729
- const hint = "Midscene is NOT running as Administrator. Windows blocks mouse/keyboard input to elevated (admin) applications from non-admin processes (UIPI). Please run your terminal or Node.js as Administrator and try again.";
730
- console.error(`\n[HealthCheck] ${hint}\n`);
731
- debugDevice(hint);
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
- return true;
770
+ this.adminCheckCache = true;
754
771
  } catch {
755
- return false;
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: normalizeRdpConnectionConfig(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 ${server}${username}${session}`;
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
- ...normalizedOptions
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 target = opts.port ? formatRdpServerAddress(opts.host, opts.port) : formatRdpHost(opts.host);
1889
+ const portSuffix = opts.port ? `:${opts.port}` : '';
1893
1890
  const userSuffix = opts.username ? ` as ${opts.username}` : '';
1894
- return ` via RDP (${target}${userSuffix})`;
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:${formatRdpHost(opts.host)}`;
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-beta-20260608084200.0";
2014
+ const currentVersion = "1.9.2";
2018
2015
  console.log(`@midscene/computer v${currentVersion}`);
2019
2016
  return currentVersion;
2020
2017
  }
@@ -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
- console.warn(`[@midscene/computer] phased-scroll helper exited with status ${res.status}; falling back to keyboard/libnut. This usually means Accessibility permission has not been granted to the host process.`);
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-beta-20260608084200.0");
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
- console.warn(msg);
714
- debugDevice(msg);
715
- if ('win32' === process.platform && !this.isRunningAsAdmin()) {
716
- const hint = "Midscene is NOT running as Administrator. Windows blocks mouse/keyboard input to elevated (admin) applications from non-admin processes (UIPI). Please run your terminal or Node.js as Administrator and try again.";
717
- console.error(`\n[HealthCheck] ${hint}\n`);
718
- debugDevice(hint);
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
- return true;
757
+ this.adminCheckCache = true;
741
758
  } catch {
742
- return false;
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: normalizeRdpConnectionConfig(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 ${server}${username}${session}`;
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
- ...normalizedOptions
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 target = opts.port ? formatRdpServerAddress(opts.host, opts.port) : formatRdpHost(opts.host);
1832
+ const portSuffix = opts.port ? `:${opts.port}` : '';
1836
1833
  const userSuffix = opts.username ? ` as ${opts.username}` : '';
1837
- return ` via RDP (${target}${userSuffix})`;
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:${formatRdpHost(opts.host)}`;
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-beta-20260608084200.0",
1962
+ version: "1.9.2",
1966
1963
  description: 'Control the computer desktop using natural language commands'
1967
1964
  }, toolsManager);
1968
1965
  }
@@ -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>;