@mediar-ai/terminator 0.23.44 → 0.23.45

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/Cargo.toml CHANGED
@@ -16,6 +16,7 @@ napi = { version = "2.12.2", default-features = false, features = [
16
16
  ] }
17
17
  napi-derive = "2.12.2"
18
18
  terminator = { workspace = true }
19
+ tracing = { workspace = true }
19
20
  tracing-subscriber = { workspace = true }
20
21
  serde = { version = "1.0", features = ["derive"] }
21
22
  serde_json = "1.0"
package/index.d.ts CHANGED
@@ -32,6 +32,11 @@ export interface ActionOptions {
32
32
  clickType?: ClickType
33
33
  /** Whether to restore cursor to original position after click. Defaults to false. */
34
34
  restoreCursor?: boolean
35
+ /**
36
+ * Whether to restore the original focus and caret position after the action. Defaults to false.
37
+ * When true, saves the currently focused element and caret position before the action, then restores them after.
38
+ */
39
+ restoreFocus?: boolean
35
40
  }
36
41
  /** Options for typeText method */
37
42
  export interface TypeTextOptions {
@@ -52,6 +57,11 @@ export interface TypeTextOptions {
52
57
  tryFocusBefore?: boolean
53
58
  /** Whether to try clicking the element if focus fails. Defaults to true. */
54
59
  tryClickBefore?: boolean
60
+ /**
61
+ * Whether to restore the original focus and caret position after typing. Defaults to false.
62
+ * When true, saves the currently focused element and caret position before typing, then restores them after.
63
+ */
64
+ restoreFocus?: boolean
55
65
  /** Whether to capture UI tree before/after action and compute diff. Defaults to false. */
56
66
  uiDiffBeforeAfter?: boolean
57
67
  /** Max depth for tree capture when doing UI diff. */
package/package.json CHANGED
@@ -41,11 +41,11 @@
41
41
  }
42
42
  },
43
43
  "optionalDependencies": {
44
- "@mediar-ai/terminator-darwin-arm64": "0.23.44",
45
- "@mediar-ai/terminator-darwin-x64": "0.23.44",
46
- "@mediar-ai/terminator-linux-x64-gnu": "0.23.44",
47
- "@mediar-ai/terminator-win32-arm64-msvc": "0.23.44",
48
- "@mediar-ai/terminator-win32-x64-msvc": "0.23.44"
44
+ "@mediar-ai/terminator-darwin-arm64": "0.23.45",
45
+ "@mediar-ai/terminator-darwin-x64": "0.23.45",
46
+ "@mediar-ai/terminator-linux-x64-gnu": "0.23.45",
47
+ "@mediar-ai/terminator-win32-arm64-msvc": "0.23.45",
48
+ "@mediar-ai/terminator-win32-x64-msvc": "0.23.45"
49
49
  },
50
50
  "repository": {
51
51
  "type": "git",
@@ -63,5 +63,5 @@
63
63
  "test-hook": "powershell.exe -ExecutionPolicy Bypass -File \"../../.git/hooks/pre-push.ps1\""
64
64
  },
65
65
  "types": "wrapper.d.ts",
66
- "version": "0.23.44"
66
+ "version": "0.23.45"
67
67
  }
package/src/element.rs CHANGED
@@ -47,6 +47,9 @@ pub struct ActionOptions {
47
47
  pub click_type: Option<ClickType>,
48
48
  /// Whether to restore cursor to original position after click. Defaults to false.
49
49
  pub restore_cursor: Option<bool>,
50
+ /// Whether to restore the original focus and caret position after the action. Defaults to false.
51
+ /// When true, saves the currently focused element and caret position before the action, then restores them after.
52
+ pub restore_focus: Option<bool>,
50
53
  }
51
54
 
52
55
  /// Options for typeText method
@@ -68,6 +71,9 @@ pub struct TypeTextOptions {
68
71
  pub try_focus_before: Option<bool>,
69
72
  /// Whether to try clicking the element if focus fails. Defaults to true.
70
73
  pub try_click_before: Option<bool>,
74
+ /// Whether to restore the original focus and caret position after typing. Defaults to false.
75
+ /// When true, saves the currently focused element and caret position before typing, then restores them after.
76
+ pub restore_focus: Option<bool>,
71
77
  /// Whether to capture UI tree before/after action and compute diff. Defaults to false.
72
78
  pub ui_diff_before_after: Option<bool>,
73
79
  /// Max depth for tree capture when doing UI diff.
@@ -253,6 +259,17 @@ impl Element {
253
259
  #[napi]
254
260
  pub async fn click(&self, options: Option<ActionOptions>) -> napi::Result<ClickResult> {
255
261
  let opts = options.unwrap_or_default();
262
+ let restore_focus = opts.restore_focus.unwrap_or(true);
263
+
264
+ // FOCUS RESTORATION: Save focus state BEFORE any window operations
265
+ #[cfg(target_os = "windows")]
266
+ let saved_focus = if restore_focus {
267
+ tracing::debug!("[TS SDK] click: saving focus state BEFORE activate_window");
268
+ terminator::platforms::windows::save_focus_state()
269
+ } else {
270
+ None
271
+ };
272
+
256
273
  if opts.highlight_before_action.unwrap_or(false) {
257
274
  let _ = self.inner.highlight_before_action("click");
258
275
  }
@@ -353,6 +370,13 @@ impl Element {
353
370
  result.window_screenshot_path = screenshots.window_path;
354
371
  result.monitor_screenshot_paths = screenshots.monitor_paths;
355
372
 
373
+ // FOCUS RESTORATION: Restore focus state after action if we saved it
374
+ #[cfg(target_os = "windows")]
375
+ if let Some(state) = saved_focus {
376
+ tracing::debug!("[TS SDK] click: restoring focus state after action");
377
+ terminator::platforms::windows::restore_focus_state(state);
378
+ }
379
+
356
380
  Ok(result)
357
381
  }
358
382
 
@@ -363,6 +387,17 @@ impl Element {
363
387
  #[napi]
364
388
  pub fn double_click(&self, options: Option<ActionOptions>) -> napi::Result<ClickResult> {
365
389
  let opts = options.unwrap_or_default();
390
+ let restore_focus = opts.restore_focus.unwrap_or(true);
391
+
392
+ // FOCUS RESTORATION: Save focus state BEFORE any window operations
393
+ #[cfg(target_os = "windows")]
394
+ let saved_focus = if restore_focus {
395
+ tracing::debug!("[TS SDK] double_click: saving focus state BEFORE activate_window");
396
+ terminator::platforms::windows::save_focus_state()
397
+ } else {
398
+ None
399
+ };
400
+
366
401
  if opts.highlight_before_action.unwrap_or(false) {
367
402
  let _ = self.inner.highlight_before_action("double_click");
368
403
  }
@@ -383,6 +418,13 @@ impl Element {
383
418
  result.window_screenshot_path = screenshots.window_path;
384
419
  result.monitor_screenshot_paths = screenshots.monitor_paths;
385
420
 
421
+ // FOCUS RESTORATION: Restore focus state after action if we saved it
422
+ #[cfg(target_os = "windows")]
423
+ if let Some(state) = saved_focus {
424
+ tracing::debug!("[TS SDK] double_click: restoring focus state after action");
425
+ terminator::platforms::windows::restore_focus_state(state);
426
+ }
427
+
386
428
  Ok(result)
387
429
  }
388
430
 
@@ -392,11 +434,31 @@ impl Element {
392
434
  #[napi]
393
435
  pub fn right_click(&self, options: Option<ActionOptions>) -> napi::Result<()> {
394
436
  let opts = options.unwrap_or_default();
437
+ let restore_focus = opts.restore_focus.unwrap_or(true);
438
+
439
+ // FOCUS RESTORATION: Save focus state BEFORE any window operations
440
+ #[cfg(target_os = "windows")]
441
+ let saved_focus = if restore_focus {
442
+ tracing::debug!("[TS SDK] right_click: saving focus state BEFORE activate_window");
443
+ terminator::platforms::windows::save_focus_state()
444
+ } else {
445
+ None
446
+ };
447
+
395
448
  if opts.highlight_before_action.unwrap_or(false) {
396
449
  let _ = self.inner.highlight_before_action("right_click");
397
450
  }
398
451
  let _ = self.inner.activate_window();
399
- self.inner.right_click().map_err(map_error)
452
+ let result = self.inner.right_click().map_err(map_error);
453
+
454
+ // FOCUS RESTORATION: Restore focus state after action if we saved it
455
+ #[cfg(target_os = "windows")]
456
+ if let Some(state) = saved_focus {
457
+ tracing::debug!("[TS SDK] right_click: restoring focus state after action");
458
+ terminator::platforms::windows::restore_focus_state(state);
459
+ }
460
+
461
+ result
400
462
  }
401
463
 
402
464
  /// Hover over this element.
@@ -405,11 +467,31 @@ impl Element {
405
467
  #[napi]
406
468
  pub fn hover(&self, options: Option<ActionOptions>) -> napi::Result<()> {
407
469
  let opts = options.unwrap_or_default();
470
+ let restore_focus = opts.restore_focus.unwrap_or(true);
471
+
472
+ // FOCUS RESTORATION: Save focus state BEFORE any window operations
473
+ #[cfg(target_os = "windows")]
474
+ let saved_focus = if restore_focus {
475
+ tracing::debug!("[TS SDK] hover: saving focus state BEFORE activate_window");
476
+ terminator::platforms::windows::save_focus_state()
477
+ } else {
478
+ None
479
+ };
480
+
408
481
  if opts.highlight_before_action.unwrap_or(false) {
409
482
  let _ = self.inner.highlight_before_action("hover");
410
483
  }
411
484
  let _ = self.inner.activate_window();
412
- self.inner.hover().map_err(map_error)
485
+ let result = self.inner.hover().map_err(map_error);
486
+
487
+ // FOCUS RESTORATION: Restore focus state after action if we saved it
488
+ #[cfg(target_os = "windows")]
489
+ if let Some(state) = saved_focus {
490
+ tracing::debug!("[TS SDK] hover: restoring focus state after action");
491
+ terminator::platforms::windows::restore_focus_state(state);
492
+ }
493
+
494
+ result
413
495
  }
414
496
 
415
497
  /// Check if element is visible.
@@ -457,6 +539,18 @@ impl Element {
457
539
  options: Option<TypeTextOptions>,
458
540
  ) -> napi::Result<ActionResult> {
459
541
  let opts = options.unwrap_or_default();
542
+ let restore_focus = opts.restore_focus.unwrap_or(true);
543
+
544
+ // CRITICAL: Save focus state BEFORE activate_window() if restore is requested
545
+ // activate_window() steals focus, so we must save first
546
+ #[cfg(target_os = "windows")]
547
+ let saved_focus = if restore_focus {
548
+ tracing::debug!("[TS SDK] type_text: saving focus state BEFORE activate_window");
549
+ terminator::platforms::windows::save_focus_state()
550
+ } else {
551
+ None
552
+ };
553
+
460
554
  if opts.highlight_before_action.unwrap_or(false) {
461
555
  let _ = self.inner.highlight_before_action("type");
462
556
  }
@@ -469,15 +563,24 @@ impl Element {
469
563
 
470
564
  let try_focus_before = opts.try_focus_before.unwrap_or(true);
471
565
  let try_click_before = opts.try_click_before.unwrap_or(true);
566
+ // Pass restore_focus=false to platform layer since we handle it ourselves
472
567
  self.inner
473
- .type_text_with_state_and_focus(
568
+ .type_text_with_state_and_focus_restore(
474
569
  &text,
475
570
  opts.use_clipboard.unwrap_or(false),
476
571
  try_focus_before,
477
572
  try_click_before,
573
+ false, // We handle focus restore ourselves since we saved BEFORE activate_window
478
574
  )
479
575
  .map_err(map_error)?;
480
576
 
577
+ // Restore focus state if we saved it
578
+ #[cfg(target_os = "windows")]
579
+ if let Some(state) = saved_focus {
580
+ tracing::debug!("[TS SDK] type_text: restoring focus state after typing");
581
+ terminator::platforms::windows::restore_focus_state(state);
582
+ }
583
+
481
584
  // Capture screenshots if requested
482
585
  let screenshots = capture_element_screenshots(
483
586
  &self.inner,
@@ -506,6 +609,17 @@ impl Element {
506
609
  options: Option<ActionOptions>,
507
610
  ) -> napi::Result<ActionResult> {
508
611
  let opts = options.unwrap_or_default();
612
+ let restore_focus = opts.restore_focus.unwrap_or(true);
613
+
614
+ // FOCUS RESTORATION: Save focus state BEFORE any window operations
615
+ #[cfg(target_os = "windows")]
616
+ let saved_focus = if restore_focus {
617
+ tracing::debug!("[TS SDK] press_key: saving focus state BEFORE activate_window");
618
+ terminator::platforms::windows::save_focus_state()
619
+ } else {
620
+ None
621
+ };
622
+
509
623
  if opts.highlight_before_action.unwrap_or(false) {
510
624
  let _ = self.inner.highlight_before_action("key");
511
625
  }
@@ -524,6 +638,13 @@ impl Element {
524
638
  "pressKey",
525
639
  );
526
640
 
641
+ // FOCUS RESTORATION: Restore focus state after action if we saved it
642
+ #[cfg(target_os = "windows")]
643
+ if let Some(state) = saved_focus {
644
+ tracing::debug!("[TS SDK] press_key: restoring focus state after action");
645
+ terminator::platforms::windows::restore_focus_state(state);
646
+ }
647
+
527
648
  Ok(ActionResult {
528
649
  success: true,
529
650
  window_screenshot_path: screenshots.window_path,
@@ -544,6 +665,17 @@ impl Element {
544
665
  options: Option<ActionOptions>,
545
666
  ) -> napi::Result<ActionResult> {
546
667
  let opts = options.unwrap_or_default();
668
+ let restore_focus = opts.restore_focus.unwrap_or(true);
669
+
670
+ // FOCUS RESTORATION: Save focus state BEFORE any window operations
671
+ #[cfg(target_os = "windows")]
672
+ let saved_focus = if restore_focus {
673
+ tracing::debug!("[TS SDK] set_value: saving focus state BEFORE action");
674
+ terminator::platforms::windows::save_focus_state()
675
+ } else {
676
+ None
677
+ };
678
+
547
679
  if opts.highlight_before_action.unwrap_or(false) {
548
680
  let _ = self.inner.highlight_before_action("set_value");
549
681
  }
@@ -557,6 +689,13 @@ impl Element {
557
689
  "setValue",
558
690
  );
559
691
 
692
+ // FOCUS RESTORATION: Restore focus state after action if we saved it
693
+ #[cfg(target_os = "windows")]
694
+ if let Some(state) = saved_focus {
695
+ tracing::debug!("[TS SDK] set_value: restoring focus state after action");
696
+ terminator::platforms::windows::restore_focus_state(state);
697
+ }
698
+
560
699
  Ok(ActionResult {
561
700
  success: true,
562
701
  window_screenshot_path: screenshots.window_path,
@@ -581,6 +720,17 @@ impl Element {
581
720
  #[napi]
582
721
  pub fn invoke(&self, options: Option<ActionOptions>) -> napi::Result<ActionResult> {
583
722
  let opts = options.unwrap_or_default();
723
+ let restore_focus = opts.restore_focus.unwrap_or(true);
724
+
725
+ // FOCUS RESTORATION: Save focus state BEFORE any window operations
726
+ #[cfg(target_os = "windows")]
727
+ let saved_focus = if restore_focus {
728
+ tracing::debug!("[TS SDK] invoke: saving focus state BEFORE action");
729
+ terminator::platforms::windows::save_focus_state()
730
+ } else {
731
+ None
732
+ };
733
+
584
734
  if opts.highlight_before_action.unwrap_or(false) {
585
735
  let _ = self.inner.highlight_before_action("invoke");
586
736
  }
@@ -594,6 +744,13 @@ impl Element {
594
744
  "invoke",
595
745
  );
596
746
 
747
+ // FOCUS RESTORATION: Restore focus state after action if we saved it
748
+ #[cfg(target_os = "windows")]
749
+ if let Some(state) = saved_focus {
750
+ tracing::debug!("[TS SDK] invoke: restoring focus state after action");
751
+ terminator::platforms::windows::restore_focus_state(state);
752
+ }
753
+
597
754
  Ok(ActionResult {
598
755
  success: true,
599
756
  window_screenshot_path: screenshots.window_path,
@@ -616,6 +773,17 @@ impl Element {
616
773
  options: Option<ActionOptions>,
617
774
  ) -> napi::Result<ActionResult> {
618
775
  let opts = options.unwrap_or_default();
776
+ let restore_focus = opts.restore_focus.unwrap_or(true);
777
+
778
+ // FOCUS RESTORATION: Save focus state BEFORE any window operations
779
+ #[cfg(target_os = "windows")]
780
+ let saved_focus = if restore_focus {
781
+ tracing::debug!("[TS SDK] scroll: saving focus state BEFORE action");
782
+ terminator::platforms::windows::save_focus_state()
783
+ } else {
784
+ None
785
+ };
786
+
619
787
  if opts.highlight_before_action.unwrap_or(false) {
620
788
  let _ = self.inner.highlight_before_action("scroll");
621
789
  }
@@ -629,6 +797,13 @@ impl Element {
629
797
  "scroll",
630
798
  );
631
799
 
800
+ // FOCUS RESTORATION: Restore focus state after action if we saved it
801
+ #[cfg(target_os = "windows")]
802
+ if let Some(state) = saved_focus {
803
+ tracing::debug!("[TS SDK] scroll: restoring focus state after action");
804
+ terminator::platforms::windows::restore_focus_state(state);
805
+ }
806
+
632
807
  Ok(ActionResult {
633
808
  success: true,
634
809
  window_screenshot_path: screenshots.window_path,
@@ -893,10 +1068,30 @@ impl Element {
893
1068
  options: Option<ActionOptions>,
894
1069
  ) -> napi::Result<()> {
895
1070
  let opts = options.unwrap_or_default();
1071
+ let restore_focus = opts.restore_focus.unwrap_or(true);
1072
+
1073
+ // FOCUS RESTORATION: Save focus state BEFORE any window operations
1074
+ #[cfg(target_os = "windows")]
1075
+ let saved_focus = if restore_focus {
1076
+ tracing::debug!("[TS SDK] select_option: saving focus state BEFORE action");
1077
+ terminator::platforms::windows::save_focus_state()
1078
+ } else {
1079
+ None
1080
+ };
1081
+
896
1082
  if opts.highlight_before_action.unwrap_or(false) {
897
1083
  let _ = self.inner.highlight_before_action("select_option");
898
1084
  }
899
- self.inner.select_option(&option_name).map_err(map_error)
1085
+ let result = self.inner.select_option(&option_name).map_err(map_error);
1086
+
1087
+ // FOCUS RESTORATION: Restore focus state after action if we saved it
1088
+ #[cfg(target_os = "windows")]
1089
+ if let Some(state) = saved_focus {
1090
+ tracing::debug!("[TS SDK] select_option: restoring focus state after action");
1091
+ terminator::platforms::windows::restore_focus_state(state);
1092
+ }
1093
+
1094
+ result
900
1095
  }
901
1096
 
902
1097
  /// Lists all available option strings from a dropdown or list box.
@@ -942,10 +1137,30 @@ impl Element {
942
1137
  #[napi]
943
1138
  pub fn set_selected(&self, state: bool, options: Option<ActionOptions>) -> napi::Result<()> {
944
1139
  let opts = options.unwrap_or_default();
1140
+ let restore_focus = opts.restore_focus.unwrap_or(true);
1141
+
1142
+ // FOCUS RESTORATION: Save focus state BEFORE any window operations
1143
+ #[cfg(target_os = "windows")]
1144
+ let saved_focus = if restore_focus {
1145
+ tracing::debug!("[TS SDK] set_selected: saving focus state BEFORE action");
1146
+ terminator::platforms::windows::save_focus_state()
1147
+ } else {
1148
+ None
1149
+ };
1150
+
945
1151
  if opts.highlight_before_action.unwrap_or(false) {
946
1152
  let _ = self.inner.highlight_before_action("set_selected");
947
1153
  }
948
- self.inner.set_selected(state).map_err(map_error)
1154
+ let result = self.inner.set_selected(state).map_err(map_error);
1155
+
1156
+ // FOCUS RESTORATION: Restore focus state after action if we saved it
1157
+ #[cfg(target_os = "windows")]
1158
+ if let Some(state) = saved_focus {
1159
+ tracing::debug!("[TS SDK] set_selected: restoring focus state after action");
1160
+ terminator::platforms::windows::restore_focus_state(state);
1161
+ }
1162
+
1163
+ result
949
1164
  }
950
1165
 
951
1166
  /// Gets the current value from a range-based control like a slider or progress bar.
package/wrapper.js CHANGED
@@ -255,7 +255,7 @@ async function enhancedExecuteBrowserScript(scriptOrFunction, processOrEnv, time
255
255
  }
256
256
  return String(result);
257
257
  }
258
- return null;
258
+ return "undefined";
259
259
  })()
260
260
  `;
261
261
  } else if (typeof scriptOrFunction === "object" && scriptOrFunction.file) {
package/wrapper.ts CHANGED
@@ -271,7 +271,7 @@ async function enhancedExecuteBrowserScript(
271
271
  }
272
272
  return String(result);
273
273
  }
274
- return null;
274
+ return "undefined";
275
275
  })()
276
276
  `;
277
277
  } else if (