@midscene/computer 1.12.2-beta-20260828074555.0 → 1.12.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/es/cli.mjs CHANGED
@@ -185,7 +185,50 @@ class ComputerInputDriver {
185
185
  }
186
186
  }
187
187
  const debugXvfb = getDebug('computer:xvfb');
188
- function createXvfbSigintCleanup(cleanup, source = process) {
188
+ const xvfbCleanupMonitorScript = String.raw`
189
+ const parentPid = Number(process.argv[1]);
190
+ const xvfbPid = Number(process.argv[2]);
191
+ const timer = setInterval(() => {
192
+ try {
193
+ process.kill(xvfbPid, 0);
194
+ } catch {
195
+ clearInterval(timer);
196
+ process.exit(0);
197
+ }
198
+ try {
199
+ process.kill(parentPid, 0);
200
+ return;
201
+ } catch {
202
+ // The owner is gone, so its X11 clients can no longer receive XIO errors.
203
+ }
204
+ try {
205
+ process.kill(xvfbPid, 'SIGTERM');
206
+ } catch {
207
+ // Xvfb may have already exited.
208
+ }
209
+ clearInterval(timer);
210
+ }, 100);
211
+ `;
212
+ function scheduleXvfbStopAfterProcessExit(instance, parentPid = process.pid) {
213
+ const xvfbPid = instance.process.pid;
214
+ if (!xvfbPid) throw new Error('Cannot schedule Xvfb cleanup before its process starts');
215
+ const monitor = spawn(process.execPath, [
216
+ '-e',
217
+ xvfbCleanupMonitorScript,
218
+ String(parentPid),
219
+ String(xvfbPid)
220
+ ], {
221
+ detached: true,
222
+ stdio: 'ignore'
223
+ });
224
+ monitor.on('error', (error)=>{
225
+ debugXvfb(`Xvfb cleanup monitor failed: ${error.message}`);
226
+ });
227
+ instance.process.unref();
228
+ monitor.unref();
229
+ return monitor;
230
+ }
231
+ function createXvfbSignalCleanup(cleanup, source = process) {
189
232
  return ()=>{
190
233
  if (!hasActiveCliInterruptWaiter(source)) cleanup();
191
234
  };
@@ -736,18 +779,21 @@ class ComputerDevice {
736
779
  this.xvfbInstance = await startXvfb({
737
780
  resolution: this.options?.xvfbResolution
738
781
  });
782
+ if (this.options?.keepXvfbAliveUntilProcessExit) scheduleXvfbStopAfterProcessExit(this.xvfbInstance);
739
783
  process.env.DISPLAY = this.xvfbInstance.display;
740
784
  debugDevice(`Xvfb started on display ${this.xvfbInstance.display}`);
741
- this.xvfbCleanup = ()=>{
742
- if (this.xvfbInstance) {
743
- this.xvfbInstance.stop();
744
- this.xvfbInstance = void 0;
745
- }
746
- };
747
- this.xvfbSigintCleanup = createXvfbSigintCleanup(()=>this.xvfbCleanup?.());
748
- process.on('exit', this.xvfbCleanup);
749
- process.on('SIGINT', this.xvfbSigintCleanup);
750
- process.on('SIGTERM', this.xvfbCleanup);
785
+ if (!this.options?.keepXvfbAliveUntilProcessExit) {
786
+ this.xvfbCleanup = ()=>{
787
+ if (this.xvfbInstance) {
788
+ this.xvfbInstance.stop();
789
+ this.xvfbInstance = void 0;
790
+ }
791
+ };
792
+ this.xvfbSignalCleanup = createXvfbSignalCleanup(()=>this.xvfbCleanup?.());
793
+ process.on('exit', this.xvfbCleanup);
794
+ process.on('SIGINT', this.xvfbSignalCleanup);
795
+ process.on('SIGTERM', this.xvfbSignalCleanup);
796
+ }
751
797
  }
752
798
  device_libnut = await getLibnut();
753
799
  this.displayGeometry = resolveDisplayGeometry(this.displayId);
@@ -765,17 +811,17 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
765
811
  await this.healthCheck();
766
812
  } catch (error) {
767
813
  if (this.xvfbInstance) {
768
- this.xvfbInstance.stop();
814
+ if (!this.options?.keepXvfbAliveUntilProcessExit) this.xvfbInstance.stop();
769
815
  this.xvfbInstance = void 0;
770
816
  }
771
817
  if (this.xvfbCleanup) {
772
818
  process.removeListener('exit', this.xvfbCleanup);
773
- process.removeListener('SIGTERM', this.xvfbCleanup);
774
819
  this.xvfbCleanup = void 0;
775
820
  }
776
- if (this.xvfbSigintCleanup) {
777
- process.removeListener('SIGINT', this.xvfbSigintCleanup);
778
- this.xvfbSigintCleanup = void 0;
821
+ if (this.xvfbSignalCleanup) {
822
+ process.removeListener('SIGINT', this.xvfbSignalCleanup);
823
+ process.removeListener('SIGTERM', this.xvfbSignalCleanup);
824
+ this.xvfbSignalCleanup = void 0;
779
825
  }
780
826
  debugDevice(`Failed to connect: ${error}`);
781
827
  throw new Error(`Unable to connect to computer device: ${error}`);
@@ -783,7 +829,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
783
829
  }
784
830
  async healthCheck() {
785
831
  console.log('[HealthCheck] Starting health check...');
786
- console.log("[HealthCheck] @midscene/computer v1.12.2-beta-20260828074555.0");
832
+ console.log("[HealthCheck] @midscene/computer v1.12.2");
787
833
  console.log('[HealthCheck] Taking screenshot...');
788
834
  const screenshotTimeout = 15000;
789
835
  let timeoutId;
@@ -1120,18 +1166,19 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1120
1166
  if (this.destroyed) return;
1121
1167
  this.destroyed = true;
1122
1168
  this.inputDriver.destroy();
1169
+ const keepXvfbAliveUntilProcessExit = this.options?.keepXvfbAliveUntilProcessExit === true;
1123
1170
  if (this.xvfbInstance) {
1124
- this.xvfbInstance.stop();
1171
+ if (!keepXvfbAliveUntilProcessExit) this.xvfbInstance.stop();
1125
1172
  this.xvfbInstance = void 0;
1126
1173
  }
1127
- if (this.xvfbCleanup) {
1174
+ if (this.xvfbCleanup && !keepXvfbAliveUntilProcessExit) {
1128
1175
  process.removeListener('exit', this.xvfbCleanup);
1129
- process.removeListener('SIGTERM', this.xvfbCleanup);
1130
1176
  this.xvfbCleanup = void 0;
1131
1177
  }
1132
- if (this.xvfbSigintCleanup) {
1133
- process.removeListener('SIGINT', this.xvfbSigintCleanup);
1134
- this.xvfbSigintCleanup = void 0;
1178
+ if (this.xvfbSignalCleanup) {
1179
+ process.removeListener('SIGINT', this.xvfbSignalCleanup);
1180
+ process.removeListener('SIGTERM', this.xvfbSignalCleanup);
1181
+ this.xvfbSignalCleanup = void 0;
1135
1182
  }
1136
1183
  debugDevice('Computer device destroyed');
1137
1184
  }
@@ -1147,7 +1194,7 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1147
1194
  device_define_property(this, "destroyed", false);
1148
1195
  device_define_property(this, "xvfbInstance", void 0);
1149
1196
  device_define_property(this, "xvfbCleanup", void 0);
1150
- device_define_property(this, "xvfbSigintCleanup", void 0);
1197
+ device_define_property(this, "xvfbSignalCleanup", void 0);
1151
1198
  device_define_property(this, "inputDriver", new ComputerInputDriver({
1152
1199
  getLibnut: ()=>device_libnut,
1153
1200
  useAppleScript: ()=>this.useAppleScript,
@@ -1963,7 +2010,8 @@ function createLocalComputerDevice(opts) {
1963
2010
  keyboardTypeDelay: opts?.keyboardTypeDelay,
1964
2011
  keyboardDriver: opts?.keyboardDriver,
1965
2012
  headless: opts?.headless,
1966
- xvfbResolution: opts?.xvfbResolution
2013
+ xvfbResolution: opts?.xvfbResolution,
2014
+ keepXvfbAliveUntilProcessExit: opts?.keepXvfbAliveUntilProcessExit
1967
2015
  });
1968
2016
  }
1969
2017
  function createRDPComputerDevice(opts) {
@@ -2106,6 +2154,9 @@ class ComputerMidsceneTools extends BaseMidsceneTools {
2106
2154
  ...void 0 !== keyboardTypeDelay ? {
2107
2155
  keyboardTypeDelay
2108
2156
  } : {},
2157
+ ...this.options.keepXvfbAliveUntilProcessExit ? {
2158
+ keepXvfbAliveUntilProcessExit: true
2159
+ } : {},
2109
2160
  ...extractAgentBehaviorInitArgs(opts) ?? {},
2110
2161
  ...reportOptions ?? {}
2111
2162
  };
@@ -2171,21 +2222,23 @@ class ComputerMidsceneTools extends BaseMidsceneTools {
2171
2222
  }
2172
2223
  ];
2173
2224
  }
2174
- constructor(...args){
2175
- super(...args), agent_tools_define_property(this, "lastInitArgsSignature", void 0), agent_tools_define_property(this, "initArgSpec", {
2225
+ constructor(options = {}){
2226
+ super(), agent_tools_define_property(this, "options", void 0), agent_tools_define_property(this, "lastInitArgsSignature", void 0), agent_tools_define_property(this, "initArgSpec", void 0), this.options = options, this.initArgSpec = {
2176
2227
  namespace: 'computer',
2177
2228
  shape: computerInitArgShape,
2178
2229
  cli: {
2179
2230
  preferBareKeys: true
2180
2231
  },
2181
2232
  adapt: (extracted)=>adaptComputerInitArgs(extracted)
2182
- });
2233
+ };
2183
2234
  }
2184
2235
  }
2185
- const tools = new ComputerMidsceneTools();
2236
+ const tools = new ComputerMidsceneTools({
2237
+ keepXvfbAliveUntilProcessExit: true
2238
+ });
2186
2239
  runToolsCLI(tools, 'midscene-computer', {
2187
2240
  stripPrefix: 'computer_',
2188
- version: "1.12.2-beta-20260828074555.0",
2241
+ version: "1.12.2",
2189
2242
  extraCommands: createReportCliCommands()
2190
2243
  }).catch((e)=>{
2191
2244
  process.exit(reportCLIError(e));
package/dist/es/index.mjs CHANGED
@@ -185,7 +185,50 @@ class ComputerInputDriver {
185
185
  }
186
186
  }
187
187
  const debugXvfb = getDebug('computer:xvfb');
188
- function createXvfbSigintCleanup(cleanup, source = process) {
188
+ const xvfbCleanupMonitorScript = String.raw`
189
+ const parentPid = Number(process.argv[1]);
190
+ const xvfbPid = Number(process.argv[2]);
191
+ const timer = setInterval(() => {
192
+ try {
193
+ process.kill(xvfbPid, 0);
194
+ } catch {
195
+ clearInterval(timer);
196
+ process.exit(0);
197
+ }
198
+ try {
199
+ process.kill(parentPid, 0);
200
+ return;
201
+ } catch {
202
+ // The owner is gone, so its X11 clients can no longer receive XIO errors.
203
+ }
204
+ try {
205
+ process.kill(xvfbPid, 'SIGTERM');
206
+ } catch {
207
+ // Xvfb may have already exited.
208
+ }
209
+ clearInterval(timer);
210
+ }, 100);
211
+ `;
212
+ function scheduleXvfbStopAfterProcessExit(instance, parentPid = process.pid) {
213
+ const xvfbPid = instance.process.pid;
214
+ if (!xvfbPid) throw new Error('Cannot schedule Xvfb cleanup before its process starts');
215
+ const monitor = spawn(process.execPath, [
216
+ '-e',
217
+ xvfbCleanupMonitorScript,
218
+ String(parentPid),
219
+ String(xvfbPid)
220
+ ], {
221
+ detached: true,
222
+ stdio: 'ignore'
223
+ });
224
+ monitor.on('error', (error)=>{
225
+ debugXvfb(`Xvfb cleanup monitor failed: ${error.message}`);
226
+ });
227
+ instance.process.unref();
228
+ monitor.unref();
229
+ return monitor;
230
+ }
231
+ function createXvfbSignalCleanup(cleanup, source = process) {
189
232
  return ()=>{
190
233
  if (!hasActiveCliInterruptWaiter(source)) cleanup();
191
234
  };
@@ -736,18 +779,21 @@ class ComputerDevice {
736
779
  this.xvfbInstance = await startXvfb({
737
780
  resolution: this.options?.xvfbResolution
738
781
  });
782
+ if (this.options?.keepXvfbAliveUntilProcessExit) scheduleXvfbStopAfterProcessExit(this.xvfbInstance);
739
783
  process.env.DISPLAY = this.xvfbInstance.display;
740
784
  debugDevice(`Xvfb started on display ${this.xvfbInstance.display}`);
741
- this.xvfbCleanup = ()=>{
742
- if (this.xvfbInstance) {
743
- this.xvfbInstance.stop();
744
- this.xvfbInstance = void 0;
745
- }
746
- };
747
- this.xvfbSigintCleanup = createXvfbSigintCleanup(()=>this.xvfbCleanup?.());
748
- process.on('exit', this.xvfbCleanup);
749
- process.on('SIGINT', this.xvfbSigintCleanup);
750
- process.on('SIGTERM', this.xvfbCleanup);
785
+ if (!this.options?.keepXvfbAliveUntilProcessExit) {
786
+ this.xvfbCleanup = ()=>{
787
+ if (this.xvfbInstance) {
788
+ this.xvfbInstance.stop();
789
+ this.xvfbInstance = void 0;
790
+ }
791
+ };
792
+ this.xvfbSignalCleanup = createXvfbSignalCleanup(()=>this.xvfbCleanup?.());
793
+ process.on('exit', this.xvfbCleanup);
794
+ process.on('SIGINT', this.xvfbSignalCleanup);
795
+ process.on('SIGTERM', this.xvfbSignalCleanup);
796
+ }
751
797
  }
752
798
  device_libnut = await getLibnut();
753
799
  this.displayGeometry = resolveDisplayGeometry(this.displayId);
@@ -765,17 +811,17 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
765
811
  await this.healthCheck();
766
812
  } catch (error) {
767
813
  if (this.xvfbInstance) {
768
- this.xvfbInstance.stop();
814
+ if (!this.options?.keepXvfbAliveUntilProcessExit) this.xvfbInstance.stop();
769
815
  this.xvfbInstance = void 0;
770
816
  }
771
817
  if (this.xvfbCleanup) {
772
818
  process.removeListener('exit', this.xvfbCleanup);
773
- process.removeListener('SIGTERM', this.xvfbCleanup);
774
819
  this.xvfbCleanup = void 0;
775
820
  }
776
- if (this.xvfbSigintCleanup) {
777
- process.removeListener('SIGINT', this.xvfbSigintCleanup);
778
- this.xvfbSigintCleanup = void 0;
821
+ if (this.xvfbSignalCleanup) {
822
+ process.removeListener('SIGINT', this.xvfbSignalCleanup);
823
+ process.removeListener('SIGTERM', this.xvfbSignalCleanup);
824
+ this.xvfbSignalCleanup = void 0;
779
825
  }
780
826
  debugDevice(`Failed to connect: ${error}`);
781
827
  throw new Error(`Unable to connect to computer device: ${error}`);
@@ -783,7 +829,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
783
829
  }
784
830
  async healthCheck() {
785
831
  console.log('[HealthCheck] Starting health check...');
786
- console.log("[HealthCheck] @midscene/computer v1.12.2-beta-20260828074555.0");
832
+ console.log("[HealthCheck] @midscene/computer v1.12.2");
787
833
  console.log('[HealthCheck] Taking screenshot...');
788
834
  const screenshotTimeout = 15000;
789
835
  let timeoutId;
@@ -1120,18 +1166,19 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1120
1166
  if (this.destroyed) return;
1121
1167
  this.destroyed = true;
1122
1168
  this.inputDriver.destroy();
1169
+ const keepXvfbAliveUntilProcessExit = this.options?.keepXvfbAliveUntilProcessExit === true;
1123
1170
  if (this.xvfbInstance) {
1124
- this.xvfbInstance.stop();
1171
+ if (!keepXvfbAliveUntilProcessExit) this.xvfbInstance.stop();
1125
1172
  this.xvfbInstance = void 0;
1126
1173
  }
1127
- if (this.xvfbCleanup) {
1174
+ if (this.xvfbCleanup && !keepXvfbAliveUntilProcessExit) {
1128
1175
  process.removeListener('exit', this.xvfbCleanup);
1129
- process.removeListener('SIGTERM', this.xvfbCleanup);
1130
1176
  this.xvfbCleanup = void 0;
1131
1177
  }
1132
- if (this.xvfbSigintCleanup) {
1133
- process.removeListener('SIGINT', this.xvfbSigintCleanup);
1134
- this.xvfbSigintCleanup = void 0;
1178
+ if (this.xvfbSignalCleanup) {
1179
+ process.removeListener('SIGINT', this.xvfbSignalCleanup);
1180
+ process.removeListener('SIGTERM', this.xvfbSignalCleanup);
1181
+ this.xvfbSignalCleanup = void 0;
1135
1182
  }
1136
1183
  debugDevice('Computer device destroyed');
1137
1184
  }
@@ -1147,7 +1194,7 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1147
1194
  device_define_property(this, "destroyed", false);
1148
1195
  device_define_property(this, "xvfbInstance", void 0);
1149
1196
  device_define_property(this, "xvfbCleanup", void 0);
1150
- device_define_property(this, "xvfbSigintCleanup", void 0);
1197
+ device_define_property(this, "xvfbSignalCleanup", void 0);
1151
1198
  device_define_property(this, "inputDriver", new ComputerInputDriver({
1152
1199
  getLibnut: ()=>device_libnut,
1153
1200
  useAppleScript: ()=>this.useAppleScript,
@@ -2006,7 +2053,8 @@ function createLocalComputerDevice(opts) {
2006
2053
  keyboardTypeDelay: opts?.keyboardTypeDelay,
2007
2054
  keyboardDriver: opts?.keyboardDriver,
2008
2055
  headless: opts?.headless,
2009
- xvfbResolution: opts?.xvfbResolution
2056
+ xvfbResolution: opts?.xvfbResolution,
2057
+ keepXvfbAliveUntilProcessExit: opts?.keepXvfbAliveUntilProcessExit
2010
2058
  });
2011
2059
  }
2012
2060
  function createRDPComputerDevice(opts) {
@@ -2149,6 +2197,9 @@ class ComputerMidsceneTools extends BaseMidsceneTools {
2149
2197
  ...void 0 !== keyboardTypeDelay ? {
2150
2198
  keyboardTypeDelay
2151
2199
  } : {},
2200
+ ...this.options.keepXvfbAliveUntilProcessExit ? {
2201
+ keepXvfbAliveUntilProcessExit: true
2202
+ } : {},
2152
2203
  ...extractAgentBehaviorInitArgs(opts) ?? {},
2153
2204
  ...reportOptions ?? {}
2154
2205
  };
@@ -2214,19 +2265,19 @@ class ComputerMidsceneTools extends BaseMidsceneTools {
2214
2265
  }
2215
2266
  ];
2216
2267
  }
2217
- constructor(...args){
2218
- super(...args), agent_tools_define_property(this, "lastInitArgsSignature", void 0), agent_tools_define_property(this, "initArgSpec", {
2268
+ constructor(options = {}){
2269
+ super(), agent_tools_define_property(this, "options", void 0), agent_tools_define_property(this, "lastInitArgsSignature", void 0), agent_tools_define_property(this, "initArgSpec", void 0), this.options = options, this.initArgSpec = {
2219
2270
  namespace: 'computer',
2220
2271
  shape: computerInitArgShape,
2221
2272
  cli: {
2222
2273
  preferBareKeys: true
2223
2274
  },
2224
2275
  adapt: (extracted)=>adaptComputerInitArgs(extracted)
2225
- });
2276
+ };
2226
2277
  }
2227
2278
  }
2228
2279
  function version() {
2229
- const currentVersion = "1.12.2-beta-20260828074555.0";
2280
+ const currentVersion = "1.12.2";
2230
2281
  console.log(`@midscene/computer v${currentVersion}`);
2231
2282
  return currentVersion;
2232
2283
  }
package/dist/lib/cli.js CHANGED
@@ -211,7 +211,50 @@ class ComputerInputDriver {
211
211
  }
212
212
  const interrupt_namespaceObject = require("@midscene/shared/cli/interrupt");
213
213
  const debugXvfb = (0, logger_namespaceObject.getDebug)('computer:xvfb');
214
- function createXvfbSigintCleanup(cleanup, source = process) {
214
+ const xvfbCleanupMonitorScript = String.raw`
215
+ const parentPid = Number(process.argv[1]);
216
+ const xvfbPid = Number(process.argv[2]);
217
+ const timer = setInterval(() => {
218
+ try {
219
+ process.kill(xvfbPid, 0);
220
+ } catch {
221
+ clearInterval(timer);
222
+ process.exit(0);
223
+ }
224
+ try {
225
+ process.kill(parentPid, 0);
226
+ return;
227
+ } catch {
228
+ // The owner is gone, so its X11 clients can no longer receive XIO errors.
229
+ }
230
+ try {
231
+ process.kill(xvfbPid, 'SIGTERM');
232
+ } catch {
233
+ // Xvfb may have already exited.
234
+ }
235
+ clearInterval(timer);
236
+ }, 100);
237
+ `;
238
+ function scheduleXvfbStopAfterProcessExit(instance, parentPid = process.pid) {
239
+ const xvfbPid = instance.process.pid;
240
+ if (!xvfbPid) throw new Error('Cannot schedule Xvfb cleanup before its process starts');
241
+ const monitor = (0, external_node_child_process_namespaceObject.spawn)(process.execPath, [
242
+ '-e',
243
+ xvfbCleanupMonitorScript,
244
+ String(parentPid),
245
+ String(xvfbPid)
246
+ ], {
247
+ detached: true,
248
+ stdio: 'ignore'
249
+ });
250
+ monitor.on('error', (error)=>{
251
+ debugXvfb(`Xvfb cleanup monitor failed: ${error.message}`);
252
+ });
253
+ instance.process.unref();
254
+ monitor.unref();
255
+ return monitor;
256
+ }
257
+ function createXvfbSignalCleanup(cleanup, source = process) {
215
258
  return ()=>{
216
259
  if (!(0, interrupt_namespaceObject.hasActiveCliInterruptWaiter)(source)) cleanup();
217
260
  };
@@ -762,18 +805,21 @@ class ComputerDevice {
762
805
  this.xvfbInstance = await startXvfb({
763
806
  resolution: this.options?.xvfbResolution
764
807
  });
808
+ if (this.options?.keepXvfbAliveUntilProcessExit) scheduleXvfbStopAfterProcessExit(this.xvfbInstance);
765
809
  process.env.DISPLAY = this.xvfbInstance.display;
766
810
  debugDevice(`Xvfb started on display ${this.xvfbInstance.display}`);
767
- this.xvfbCleanup = ()=>{
768
- if (this.xvfbInstance) {
769
- this.xvfbInstance.stop();
770
- this.xvfbInstance = void 0;
771
- }
772
- };
773
- this.xvfbSigintCleanup = createXvfbSigintCleanup(()=>this.xvfbCleanup?.());
774
- process.on('exit', this.xvfbCleanup);
775
- process.on('SIGINT', this.xvfbSigintCleanup);
776
- process.on('SIGTERM', this.xvfbCleanup);
811
+ if (!this.options?.keepXvfbAliveUntilProcessExit) {
812
+ this.xvfbCleanup = ()=>{
813
+ if (this.xvfbInstance) {
814
+ this.xvfbInstance.stop();
815
+ this.xvfbInstance = void 0;
816
+ }
817
+ };
818
+ this.xvfbSignalCleanup = createXvfbSignalCleanup(()=>this.xvfbCleanup?.());
819
+ process.on('exit', this.xvfbCleanup);
820
+ process.on('SIGINT', this.xvfbSignalCleanup);
821
+ process.on('SIGTERM', this.xvfbSignalCleanup);
822
+ }
777
823
  }
778
824
  device_libnut = await getLibnut();
779
825
  this.displayGeometry = resolveDisplayGeometry(this.displayId);
@@ -791,17 +837,17 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
791
837
  await this.healthCheck();
792
838
  } catch (error) {
793
839
  if (this.xvfbInstance) {
794
- this.xvfbInstance.stop();
840
+ if (!this.options?.keepXvfbAliveUntilProcessExit) this.xvfbInstance.stop();
795
841
  this.xvfbInstance = void 0;
796
842
  }
797
843
  if (this.xvfbCleanup) {
798
844
  process.removeListener('exit', this.xvfbCleanup);
799
- process.removeListener('SIGTERM', this.xvfbCleanup);
800
845
  this.xvfbCleanup = void 0;
801
846
  }
802
- if (this.xvfbSigintCleanup) {
803
- process.removeListener('SIGINT', this.xvfbSigintCleanup);
804
- this.xvfbSigintCleanup = void 0;
847
+ if (this.xvfbSignalCleanup) {
848
+ process.removeListener('SIGINT', this.xvfbSignalCleanup);
849
+ process.removeListener('SIGTERM', this.xvfbSignalCleanup);
850
+ this.xvfbSignalCleanup = void 0;
805
851
  }
806
852
  debugDevice(`Failed to connect: ${error}`);
807
853
  throw new Error(`Unable to connect to computer device: ${error}`);
@@ -809,7 +855,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
809
855
  }
810
856
  async healthCheck() {
811
857
  console.log('[HealthCheck] Starting health check...');
812
- console.log("[HealthCheck] @midscene/computer v1.12.2-beta-20260828074555.0");
858
+ console.log("[HealthCheck] @midscene/computer v1.12.2");
813
859
  console.log('[HealthCheck] Taking screenshot...');
814
860
  const screenshotTimeout = 15000;
815
861
  let timeoutId;
@@ -1146,18 +1192,19 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1146
1192
  if (this.destroyed) return;
1147
1193
  this.destroyed = true;
1148
1194
  this.inputDriver.destroy();
1195
+ const keepXvfbAliveUntilProcessExit = this.options?.keepXvfbAliveUntilProcessExit === true;
1149
1196
  if (this.xvfbInstance) {
1150
- this.xvfbInstance.stop();
1197
+ if (!keepXvfbAliveUntilProcessExit) this.xvfbInstance.stop();
1151
1198
  this.xvfbInstance = void 0;
1152
1199
  }
1153
- if (this.xvfbCleanup) {
1200
+ if (this.xvfbCleanup && !keepXvfbAliveUntilProcessExit) {
1154
1201
  process.removeListener('exit', this.xvfbCleanup);
1155
- process.removeListener('SIGTERM', this.xvfbCleanup);
1156
1202
  this.xvfbCleanup = void 0;
1157
1203
  }
1158
- if (this.xvfbSigintCleanup) {
1159
- process.removeListener('SIGINT', this.xvfbSigintCleanup);
1160
- this.xvfbSigintCleanup = void 0;
1204
+ if (this.xvfbSignalCleanup) {
1205
+ process.removeListener('SIGINT', this.xvfbSignalCleanup);
1206
+ process.removeListener('SIGTERM', this.xvfbSignalCleanup);
1207
+ this.xvfbSignalCleanup = void 0;
1161
1208
  }
1162
1209
  debugDevice('Computer device destroyed');
1163
1210
  }
@@ -1173,7 +1220,7 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1173
1220
  device_define_property(this, "destroyed", false);
1174
1221
  device_define_property(this, "xvfbInstance", void 0);
1175
1222
  device_define_property(this, "xvfbCleanup", void 0);
1176
- device_define_property(this, "xvfbSigintCleanup", void 0);
1223
+ device_define_property(this, "xvfbSignalCleanup", void 0);
1177
1224
  device_define_property(this, "inputDriver", new ComputerInputDriver({
1178
1225
  getLibnut: ()=>device_libnut,
1179
1226
  useAppleScript: ()=>this.useAppleScript,
@@ -1990,7 +2037,8 @@ function createLocalComputerDevice(opts) {
1990
2037
  keyboardTypeDelay: opts?.keyboardTypeDelay,
1991
2038
  keyboardDriver: opts?.keyboardDriver,
1992
2039
  headless: opts?.headless,
1993
- xvfbResolution: opts?.xvfbResolution
2040
+ xvfbResolution: opts?.xvfbResolution,
2041
+ keepXvfbAliveUntilProcessExit: opts?.keepXvfbAliveUntilProcessExit
1994
2042
  });
1995
2043
  }
1996
2044
  function createRDPComputerDevice(opts) {
@@ -2133,6 +2181,9 @@ class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
2133
2181
  ...void 0 !== keyboardTypeDelay ? {
2134
2182
  keyboardTypeDelay
2135
2183
  } : {},
2184
+ ...this.options.keepXvfbAliveUntilProcessExit ? {
2185
+ keepXvfbAliveUntilProcessExit: true
2186
+ } : {},
2136
2187
  ...(0, agent_behavior_init_args_namespaceObject.extractAgentBehaviorInitArgs)(opts) ?? {},
2137
2188
  ...reportOptions ?? {}
2138
2189
  };
@@ -2198,21 +2249,23 @@ class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
2198
2249
  }
2199
2250
  ];
2200
2251
  }
2201
- constructor(...args){
2202
- super(...args), agent_tools_define_property(this, "lastInitArgsSignature", void 0), agent_tools_define_property(this, "initArgSpec", {
2252
+ constructor(options = {}){
2253
+ super(), agent_tools_define_property(this, "options", void 0), agent_tools_define_property(this, "lastInitArgsSignature", void 0), agent_tools_define_property(this, "initArgSpec", void 0), this.options = options, this.initArgSpec = {
2203
2254
  namespace: 'computer',
2204
2255
  shape: computerInitArgShape,
2205
2256
  cli: {
2206
2257
  preferBareKeys: true
2207
2258
  },
2208
2259
  adapt: (extracted)=>adaptComputerInitArgs(extracted)
2209
- });
2260
+ };
2210
2261
  }
2211
2262
  }
2212
- const tools = new ComputerMidsceneTools();
2263
+ const tools = new ComputerMidsceneTools({
2264
+ keepXvfbAliveUntilProcessExit: true
2265
+ });
2213
2266
  (0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-computer', {
2214
2267
  stripPrefix: 'computer_',
2215
- version: "1.12.2-beta-20260828074555.0",
2268
+ version: "1.12.2",
2216
2269
  extraCommands: (0, core_namespaceObject.createReportCliCommands)()
2217
2270
  }).catch((e)=>{
2218
2271
  process.exit((0, cli_namespaceObject.reportCLIError)(e));
package/dist/lib/index.js CHANGED
@@ -237,7 +237,50 @@ class ComputerInputDriver {
237
237
  }
238
238
  const interrupt_namespaceObject = require("@midscene/shared/cli/interrupt");
239
239
  const debugXvfb = (0, logger_namespaceObject.getDebug)('computer:xvfb');
240
- function createXvfbSigintCleanup(cleanup, source = process) {
240
+ const xvfbCleanupMonitorScript = String.raw`
241
+ const parentPid = Number(process.argv[1]);
242
+ const xvfbPid = Number(process.argv[2]);
243
+ const timer = setInterval(() => {
244
+ try {
245
+ process.kill(xvfbPid, 0);
246
+ } catch {
247
+ clearInterval(timer);
248
+ process.exit(0);
249
+ }
250
+ try {
251
+ process.kill(parentPid, 0);
252
+ return;
253
+ } catch {
254
+ // The owner is gone, so its X11 clients can no longer receive XIO errors.
255
+ }
256
+ try {
257
+ process.kill(xvfbPid, 'SIGTERM');
258
+ } catch {
259
+ // Xvfb may have already exited.
260
+ }
261
+ clearInterval(timer);
262
+ }, 100);
263
+ `;
264
+ function scheduleXvfbStopAfterProcessExit(instance, parentPid = process.pid) {
265
+ const xvfbPid = instance.process.pid;
266
+ if (!xvfbPid) throw new Error('Cannot schedule Xvfb cleanup before its process starts');
267
+ const monitor = (0, external_node_child_process_namespaceObject.spawn)(process.execPath, [
268
+ '-e',
269
+ xvfbCleanupMonitorScript,
270
+ String(parentPid),
271
+ String(xvfbPid)
272
+ ], {
273
+ detached: true,
274
+ stdio: 'ignore'
275
+ });
276
+ monitor.on('error', (error)=>{
277
+ debugXvfb(`Xvfb cleanup monitor failed: ${error.message}`);
278
+ });
279
+ instance.process.unref();
280
+ monitor.unref();
281
+ return monitor;
282
+ }
283
+ function createXvfbSignalCleanup(cleanup, source = process) {
241
284
  return ()=>{
242
285
  if (!(0, interrupt_namespaceObject.hasActiveCliInterruptWaiter)(source)) cleanup();
243
286
  };
@@ -788,18 +831,21 @@ class ComputerDevice {
788
831
  this.xvfbInstance = await startXvfb({
789
832
  resolution: this.options?.xvfbResolution
790
833
  });
834
+ if (this.options?.keepXvfbAliveUntilProcessExit) scheduleXvfbStopAfterProcessExit(this.xvfbInstance);
791
835
  process.env.DISPLAY = this.xvfbInstance.display;
792
836
  debugDevice(`Xvfb started on display ${this.xvfbInstance.display}`);
793
- this.xvfbCleanup = ()=>{
794
- if (this.xvfbInstance) {
795
- this.xvfbInstance.stop();
796
- this.xvfbInstance = void 0;
797
- }
798
- };
799
- this.xvfbSigintCleanup = createXvfbSigintCleanup(()=>this.xvfbCleanup?.());
800
- process.on('exit', this.xvfbCleanup);
801
- process.on('SIGINT', this.xvfbSigintCleanup);
802
- process.on('SIGTERM', this.xvfbCleanup);
837
+ if (!this.options?.keepXvfbAliveUntilProcessExit) {
838
+ this.xvfbCleanup = ()=>{
839
+ if (this.xvfbInstance) {
840
+ this.xvfbInstance.stop();
841
+ this.xvfbInstance = void 0;
842
+ }
843
+ };
844
+ this.xvfbSignalCleanup = createXvfbSignalCleanup(()=>this.xvfbCleanup?.());
845
+ process.on('exit', this.xvfbCleanup);
846
+ process.on('SIGINT', this.xvfbSignalCleanup);
847
+ process.on('SIGTERM', this.xvfbSignalCleanup);
848
+ }
803
849
  }
804
850
  device_libnut = await getLibnut();
805
851
  this.displayGeometry = resolveDisplayGeometry(this.displayId);
@@ -817,17 +863,17 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
817
863
  await this.healthCheck();
818
864
  } catch (error) {
819
865
  if (this.xvfbInstance) {
820
- this.xvfbInstance.stop();
866
+ if (!this.options?.keepXvfbAliveUntilProcessExit) this.xvfbInstance.stop();
821
867
  this.xvfbInstance = void 0;
822
868
  }
823
869
  if (this.xvfbCleanup) {
824
870
  process.removeListener('exit', this.xvfbCleanup);
825
- process.removeListener('SIGTERM', this.xvfbCleanup);
826
871
  this.xvfbCleanup = void 0;
827
872
  }
828
- if (this.xvfbSigintCleanup) {
829
- process.removeListener('SIGINT', this.xvfbSigintCleanup);
830
- this.xvfbSigintCleanup = void 0;
873
+ if (this.xvfbSignalCleanup) {
874
+ process.removeListener('SIGINT', this.xvfbSignalCleanup);
875
+ process.removeListener('SIGTERM', this.xvfbSignalCleanup);
876
+ this.xvfbSignalCleanup = void 0;
831
877
  }
832
878
  debugDevice(`Failed to connect: ${error}`);
833
879
  throw new Error(`Unable to connect to computer device: ${error}`);
@@ -835,7 +881,7 @@ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ')
835
881
  }
836
882
  async healthCheck() {
837
883
  console.log('[HealthCheck] Starting health check...');
838
- console.log("[HealthCheck] @midscene/computer v1.12.2-beta-20260828074555.0");
884
+ console.log("[HealthCheck] @midscene/computer v1.12.2");
839
885
  console.log('[HealthCheck] Taking screenshot...');
840
886
  const screenshotTimeout = 15000;
841
887
  let timeoutId;
@@ -1172,18 +1218,19 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1172
1218
  if (this.destroyed) return;
1173
1219
  this.destroyed = true;
1174
1220
  this.inputDriver.destroy();
1221
+ const keepXvfbAliveUntilProcessExit = this.options?.keepXvfbAliveUntilProcessExit === true;
1175
1222
  if (this.xvfbInstance) {
1176
- this.xvfbInstance.stop();
1223
+ if (!keepXvfbAliveUntilProcessExit) this.xvfbInstance.stop();
1177
1224
  this.xvfbInstance = void 0;
1178
1225
  }
1179
- if (this.xvfbCleanup) {
1226
+ if (this.xvfbCleanup && !keepXvfbAliveUntilProcessExit) {
1180
1227
  process.removeListener('exit', this.xvfbCleanup);
1181
- process.removeListener('SIGTERM', this.xvfbCleanup);
1182
1228
  this.xvfbCleanup = void 0;
1183
1229
  }
1184
- if (this.xvfbSigintCleanup) {
1185
- process.removeListener('SIGINT', this.xvfbSigintCleanup);
1186
- this.xvfbSigintCleanup = void 0;
1230
+ if (this.xvfbSignalCleanup) {
1231
+ process.removeListener('SIGINT', this.xvfbSignalCleanup);
1232
+ process.removeListener('SIGTERM', this.xvfbSignalCleanup);
1233
+ this.xvfbSignalCleanup = void 0;
1187
1234
  }
1188
1235
  debugDevice('Computer device destroyed');
1189
1236
  }
@@ -1199,7 +1246,7 @@ $g.Dispose(); $bmp.Dispose(); $ms.Dispose()
1199
1246
  device_define_property(this, "destroyed", false);
1200
1247
  device_define_property(this, "xvfbInstance", void 0);
1201
1248
  device_define_property(this, "xvfbCleanup", void 0);
1202
- device_define_property(this, "xvfbSigintCleanup", void 0);
1249
+ device_define_property(this, "xvfbSignalCleanup", void 0);
1203
1250
  device_define_property(this, "inputDriver", new ComputerInputDriver({
1204
1251
  getLibnut: ()=>device_libnut,
1205
1252
  useAppleScript: ()=>this.useAppleScript,
@@ -2060,7 +2107,8 @@ function createLocalComputerDevice(opts) {
2060
2107
  keyboardTypeDelay: opts?.keyboardTypeDelay,
2061
2108
  keyboardDriver: opts?.keyboardDriver,
2062
2109
  headless: opts?.headless,
2063
- xvfbResolution: opts?.xvfbResolution
2110
+ xvfbResolution: opts?.xvfbResolution,
2111
+ keepXvfbAliveUntilProcessExit: opts?.keepXvfbAliveUntilProcessExit
2064
2112
  });
2065
2113
  }
2066
2114
  function createRDPComputerDevice(opts) {
@@ -2206,6 +2254,9 @@ class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
2206
2254
  ...void 0 !== keyboardTypeDelay ? {
2207
2255
  keyboardTypeDelay
2208
2256
  } : {},
2257
+ ...this.options.keepXvfbAliveUntilProcessExit ? {
2258
+ keepXvfbAliveUntilProcessExit: true
2259
+ } : {},
2209
2260
  ...(0, agent_behavior_init_args_namespaceObject.extractAgentBehaviorInitArgs)(opts) ?? {},
2210
2261
  ...reportOptions ?? {}
2211
2262
  };
@@ -2271,20 +2322,20 @@ class ComputerMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
2271
2322
  }
2272
2323
  ];
2273
2324
  }
2274
- constructor(...args){
2275
- super(...args), agent_tools_define_property(this, "lastInitArgsSignature", void 0), agent_tools_define_property(this, "initArgSpec", {
2325
+ constructor(options = {}){
2326
+ super(), agent_tools_define_property(this, "options", void 0), agent_tools_define_property(this, "lastInitArgsSignature", void 0), agent_tools_define_property(this, "initArgSpec", void 0), this.options = options, this.initArgSpec = {
2276
2327
  namespace: 'computer',
2277
2328
  shape: computerInitArgShape,
2278
2329
  cli: {
2279
2330
  preferBareKeys: true
2280
2331
  },
2281
2332
  adapt: (extracted)=>adaptComputerInitArgs(extracted)
2282
- });
2333
+ };
2283
2334
  }
2284
2335
  }
2285
2336
  const env_namespaceObject = require("@midscene/shared/env");
2286
2337
  function version() {
2287
- const currentVersion = "1.12.2-beta-20260828074555.0";
2338
+ const currentVersion = "1.12.2";
2288
2339
  console.log(`@midscene/computer v${currentVersion}`);
2289
2340
  return currentVersion;
2290
2341
  }
@@ -73,7 +73,7 @@ export declare class ComputerDevice implements AbstractInterface {
73
73
  private destroyed;
74
74
  private xvfbInstance?;
75
75
  private xvfbCleanup?;
76
- private xvfbSigintCleanup?;
76
+ private xvfbSignalCleanup?;
77
77
  private readonly inputDriver;
78
78
  /**
79
79
  * On macOS, use AppleScript for keyboard operations by default
@@ -181,6 +181,14 @@ export declare interface ComputerDeviceOpt extends ComputerDeviceInputOpt {
181
181
  * Resolution for Xvfb virtual display (default '1920x1080x24')
182
182
  */
183
183
  xvfbResolution?: string;
184
+ /**
185
+ * Keep a managed Xvfb server alive until process exit.
186
+ *
187
+ * @internal The foreground CLI uses this because libnut keeps a process-wide
188
+ * X11 connection open. Stopping Xvfb during normal CLI teardown would make
189
+ * Xlib terminate an otherwise successful command with exit code 1.
190
+ */
191
+ keepXvfbAliveUntilProcessExit?: boolean;
184
192
  }
185
193
 
186
194
  /**
@@ -202,7 +210,9 @@ declare type ComputerLocalInitArgs = {
202
210
  * Extends BaseMidsceneTools to provide desktop automation tools
203
211
  */
204
212
  export declare class ComputerMidsceneTools extends BaseMidsceneTools<ComputerAgent, ComputerInitArgs> {
213
+ private readonly options;
205
214
  private lastInitArgsSignature?;
215
+ constructor(options?: ComputerMidsceneToolsOptions);
206
216
  protected getCliReportSessionName(): string;
207
217
  protected readonly initArgSpec: InitArgSpec<ComputerInitArgs>;
208
218
  protected createTemporaryDevice(): ComputerDevice;
@@ -213,6 +223,11 @@ export declare class ComputerMidsceneTools extends BaseMidsceneTools<ComputerAge
213
223
  protected preparePlatformTools(): ToolDefinition[];
214
224
  }
215
225
 
226
+ declare interface ComputerMidsceneToolsOptions {
227
+ /** Keep CLI-owned Xvfb alive until process exit so Xlib clients stay valid. */
228
+ keepXvfbAliveUntilProcessExit?: boolean;
229
+ }
230
+
216
231
  /** Init args for the RDP remote-desktop agent. */
217
232
  declare type ComputerRDPInitArgs = {
218
233
  mode: 'rdp';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midscene/computer",
3
- "version": "1.12.2-beta-20260828074555.0",
3
+ "version": "1.12.2",
4
4
  "description": "Midscene.js Computer Desktop Automation",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,8 +33,8 @@
33
33
  "@computer-use/libnut": "^4.2.0",
34
34
  "clipboardy": "^4.0.0",
35
35
  "screenshot-desktop": "^1.15.3",
36
- "@midscene/core": "1.12.2-beta-20260828074555.0",
37
- "@midscene/shared": "1.12.2-beta-20260828074555.0"
36
+ "@midscene/core": "1.12.2",
37
+ "@midscene/shared": "1.12.2"
38
38
  },
39
39
  "optionalDependencies": {
40
40
  "node-mac-permissions": "2.5.0"