dsh-click 0.3.23 → 0.3.25

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +0 -0
  3. package/README-es.md +152 -105
  4. package/README-hi.md +152 -105
  5. package/README-pt.md +152 -105
  6. package/README-zh.md +152 -105
  7. package/README.md +154 -110
  8. package/SECURITY.md +0 -0
  9. package/THIRD_PARTY_NOTICES.md +0 -0
  10. package/cordis.patch.yml +4 -0
  11. package/lib/index.js +9 -4
  12. package/lib/types/actions.d.ts +0 -0
  13. package/lib/types/actions.d.ts.map +0 -0
  14. package/lib/types/config.d.ts +10 -0
  15. package/lib/types/config.d.ts.map +1 -1
  16. package/lib/types/events.d.ts +0 -0
  17. package/lib/types/events.d.ts.map +0 -0
  18. package/lib/types/index.d.ts +0 -0
  19. package/lib/types/index.d.ts.map +0 -0
  20. package/lib/types/observe.d.ts +3 -2
  21. package/lib/types/observe.d.ts.map +1 -1
  22. package/lib/types/platform/runner.d.ts +0 -0
  23. package/lib/types/platform/runner.d.ts.map +0 -0
  24. package/lib/types/platform/selection.d.ts +0 -0
  25. package/lib/types/platform/selection.d.ts.map +0 -0
  26. package/lib/types/platform/types.d.ts +0 -0
  27. package/lib/types/platform/types.d.ts.map +0 -0
  28. package/lib/types/sanitize.d.ts +0 -0
  29. package/lib/types/sanitize.d.ts.map +0 -0
  30. package/lib/types/tools.d.ts +0 -0
  31. package/lib/types/tools.d.ts.map +0 -0
  32. package/lib/types/version.d.ts +1 -1
  33. package/lib/types/version.d.ts.map +0 -0
  34. package/lib/types/vision/grounding.d.ts +0 -0
  35. package/lib/types/vision/grounding.d.ts.map +0 -0
  36. package/lib/types/vision/index.d.ts +0 -0
  37. package/lib/types/vision/index.d.ts.map +0 -0
  38. package/lib/types/vision/ocr.d.ts +0 -0
  39. package/lib/types/vision/ocr.d.ts.map +0 -0
  40. package/lib/types/vision.d.ts +0 -0
  41. package/lib/types/vision.d.ts.map +0 -0
  42. package/native/win32/dsh-click-helper.ps1 +145 -69
  43. package/package.json +6 -6
@@ -1,4 +1,4 @@
1
- # dsh-click native helper (Windows PowerShell 5.1).
1
+ # dsh-click native helper (Windows PowerShell 5.1).
2
2
  #
3
3
  # One request = one process: JSON request on stdin, JSON response on stdout
4
4
  # (exit code 0 even for handled failures; the error envelope is the response).
@@ -57,6 +57,87 @@ public static class DshClickWin32 {
57
57
  [DllImport("user32.dll")]
58
58
  public static extern bool SetForegroundWindow(IntPtr hWnd);
59
59
 
60
+ // ---- dsh-click input patch: real keyboard injection + reliable focus ----
61
+ [DllImport("user32.dll")]
62
+ public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
63
+
64
+ [DllImport("user32.dll")]
65
+ public static extern IntPtr SetFocus(IntPtr hWnd);
66
+
67
+ [DllImport("user32.dll")]
68
+ public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
69
+
70
+ [DllImport("user32.dll")]
71
+ public static extern bool IsIconic(IntPtr hWnd);
72
+
73
+ [DllImport("kernel32.dll")]
74
+ public static extern uint GetCurrentThreadId();
75
+
76
+ [DllImport("user32.dll")]
77
+ public static extern short VkKeyScanW(char ch);
78
+
79
+ [StructLayout(LayoutKind.Sequential)]
80
+ public struct KEYBDINPUT {
81
+ public ushort wVk;
82
+ public ushort wScan;
83
+ public uint dwFlags;
84
+ public uint time;
85
+ public IntPtr dwExtraInfo;
86
+ }
87
+
88
+ [StructLayout(LayoutKind.Explicit, Size = 40)]
89
+ public struct INPUT {
90
+ [FieldOffset(0)] public uint type;
91
+ [FieldOffset(8)] public KEYBDINPUT ki;
92
+ }
93
+
94
+ [DllImport("user32.dll", SetLastError = true)]
95
+ public static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
96
+
97
+ public static uint SendUnicodeText(string text) {
98
+ if (string.IsNullOrEmpty(text)) return 0;
99
+ var list = new List<INPUT>();
100
+ foreach (char c in text) {
101
+ INPUT down = new INPUT();
102
+ down.type = 1;
103
+ down.ki.wScan = (ushort)c;
104
+ down.ki.dwFlags = 0x0004;
105
+ list.Add(down);
106
+ INPUT up = new INPUT();
107
+ up.type = 1;
108
+ up.ki.wScan = (ushort)c;
109
+ up.ki.dwFlags = 0x0004 | 0x0002;
110
+ list.Add(up);
111
+ }
112
+ return SendInput((uint)list.Count, list.ToArray(), Marshal.SizeOf(typeof(INPUT)));
113
+ }
114
+
115
+ public static uint SendVirtualKey(ushort vk, bool up) {
116
+ INPUT inp = new INPUT();
117
+ inp.type = 1;
118
+ inp.ki.wVk = vk;
119
+ inp.ki.dwFlags = up ? 0x0002u : 0u;
120
+ return SendInput(1, new INPUT[] { inp }, Marshal.SizeOf(typeof(INPUT)));
121
+ }
122
+
123
+ public static bool BringToFront(IntPtr hWnd) {
124
+ if (IsIconic(hWnd)) ShowWindow(hWnd, 9);
125
+ if (GetForegroundWindow() == hWnd) return true;
126
+ IntPtr fg = GetForegroundWindow();
127
+ uint ignored;
128
+ uint fgThread = GetWindowThreadProcessId(fg, out ignored);
129
+ uint targetThread = GetWindowThreadProcessId(hWnd, out ignored);
130
+ uint myThread = GetCurrentThreadId();
131
+ bool a1 = AttachThreadInput(myThread, fgThread, true);
132
+ bool a2 = AttachThreadInput(myThread, targetThread, true);
133
+ SetForegroundWindow(hWnd);
134
+ SetFocus(hWnd);
135
+ if (a2) AttachThreadInput(myThread, targetThread, false);
136
+ if (a1) AttachThreadInput(myThread, fgThread, false);
137
+ System.Threading.Thread.Sleep(40);
138
+ return GetForegroundWindow() == hWnd;
139
+ }
140
+
60
141
  [StructLayout(LayoutKind.Sequential)]
61
142
  public struct POINT { public int X; public int Y; }
62
143
 
@@ -396,7 +477,7 @@ function Find-ElementByRuntimeId($windowElement, $runtimeIdText) {
396
477
  $ids = @()
397
478
  foreach ($part in ($runtimeIdText -split '\.')) { $ids += [int]$part }
398
479
  if ($ids.Count -eq 0) { return $null }
399
- $condition = [System.Windows.Automation.PropertyCondition]::new([System.Windows.Automation.AutomationElement]::RuntimeIdProperty, [int[]]$ids)
480
+ $condition = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::RuntimeIdProperty, [int[]]$ids)
400
481
  try {
401
482
  return $windowElement.FindFirst([System.Windows.Automation.TreeScope]::Descendants, $condition)
402
483
  } catch {
@@ -449,45 +530,30 @@ function Post-Click($hwnd, $cx, $cy, $button) {
449
530
  [void][DshClickWin32]::PostMessage($hwnd, $up, [IntPtr]0, $lparam)
450
531
  }
451
532
 
452
- function Get-KeyMap() {
453
- $map = @{}
454
- $map['ENTER'] = @(0x0D, $true)
455
- $map['RETURN'] = @(0x0D, $true)
456
- $map['TAB'] = @(0x09, $true)
457
- $map['ESC'] = @(0x1B, $true)
458
- $map['ESCAPE'] = @(0x1B, $true)
459
- $map['BACKSPACE'] = @(0x08, $true)
460
- $map['DELETE'] = @(0x2E, $true)
461
- $map['DEL'] = @(0x2E, $true)
462
- $map['SPACE'] = @(0x20, $true)
463
- $map['INSERT'] = @(0x2D, $true)
464
- $map['HOME'] = @(0x24, $true)
465
- $map['END'] = @(0x23, $true)
466
- $map['PAGEUP'] = @(0x21, $true)
467
- $map['PGUP'] = @(0x21, $true)
468
- $map['PAGEDOWN'] = @(0x22, $true)
469
- $map['PGDN'] = @(0x22, $true)
470
- $map['UP'] = @(0x26, $true)
471
- $map['DOWN'] = @(0x28, $true)
472
- $map['LEFT'] = @(0x25, $true)
473
- $map['RIGHT'] = @(0x27, $true)
474
- $map['CTRL'] = @(0x11, $false)
475
- $map['CONTROL'] = @(0x11, $false)
476
- $map['SHIFT'] = @(0x10, $false)
477
- $map['ALT'] = @(0x12, $false)
478
- $map['MENU'] = @(0x12, $false)
479
- $map['WIN'] = @(0x5B, $false)
480
- $map['WINDOWS'] = @(0x5B, $false)
481
- foreach ($n in 1..12) { $map["F$n"] = @((0x6F + $n), $true) }
482
- foreach ($c in 0..9) { $map["$c"] = @((0x30 + $c), $true) }
483
- foreach ($code in 65..90) {
484
- $map[[string][char]$code] = @((0x41 + ($code - 65)), $true)
485
- }
486
- return $map
533
+ function Get-VirtualKey([string]$token) {
534
+ $names = @{
535
+ 'ENTER' = 0x0D; 'RETURN' = 0x0D; 'TAB' = 0x09; 'ESC' = 0x1B; 'ESCAPE' = 0x1B;
536
+ 'SPACE' = 0x20; 'BACKSPACE' = 0x08; 'DELETE' = 0x2E; 'DEL' = 0x2E; 'INSERT' = 0x2D;
537
+ 'HOME' = 0x24; 'END' = 0x23; 'PAGEUP' = 0x21; 'PGUP' = 0x21; 'PAGEDOWN' = 0x22; 'PGDN' = 0x22;
538
+ 'UP' = 0x26; 'DOWN' = 0x28; 'LEFT' = 0x25; 'RIGHT' = 0x27;
539
+ 'CTRL' = 0x11; 'CONTROL' = 0x11; 'SHIFT' = 0x10; 'ALT' = 0x12; 'MENU' = 0x12;
540
+ 'WIN' = 0x5B; 'WINDOWS' = 0x5B; 'LWIN' = 0x5B; 'RWIN' = 0x5C; 'APPS' = 0x5D;
541
+ 'CAPSLOCK' = 0x14; 'NUMLOCK' = 0x90; 'SCROLLLOCK' = 0x91; 'PRINTSCREEN' = 0x2C;
542
+ 'PAUSE' = 0x13; 'COMMA' = 0xBC; 'PERIOD' = 0xBE; 'SLASH' = 0xBF; 'SEMICOLON' = 0xBA;
543
+ 'QUOTE' = 0xDE; 'LBRACKET' = 0xDB; 'RBRACKET' = 0xDD; 'BACKSLASH' = 0xDC;
544
+ 'MINUS' = 0xBD; 'PLUS' = 0xBB; 'TILDE' = 0xC0
545
+ }
546
+ if ($names.ContainsKey($token)) { return [int]$names[$token] }
547
+ if ($token -match '^F([1-9]|1[0-2])$') { return 0x6F + [int]$Matches[1] }
548
+ if ($token.Length -eq 1) {
549
+ $scan = [DshClickWin32]::VkKeyScanW([char]$token)
550
+ if ($scan -ne -1) { return [int]($scan -band 0xFF) }
551
+ return [int][char]$token
552
+ }
553
+ throw "unknown key '$token'"
487
554
  }
488
555
 
489
556
  function Post-KeyCombo($hwnd, $keysText) {
490
- $map = Get-KeyMap
491
557
  $tokens = @()
492
558
  foreach ($token in ($keysText -split '\+')) {
493
559
  $trimmed = $token.Trim()
@@ -495,39 +561,37 @@ function Post-KeyCombo($hwnd, $keysText) {
495
561
  $tokens += $trimmed.ToUpperInvariant()
496
562
  }
497
563
  if ($tokens.Count -eq 0) { throw 'empty key combination' }
564
+ $modifierNames = @('CTRL', 'CONTROL', 'SHIFT', 'ALT', 'MENU', 'WIN', 'WINDOWS', 'LWIN', 'RWIN')
498
565
  $modifiers = @()
499
566
  $mainKeys = @()
500
567
  foreach ($token in $tokens) {
501
- if ($token -in @('CTRL', 'CONTROL', 'SHIFT', 'ALT', 'MENU', 'WIN', 'WINDOWS')) {
502
- $modifiers += $token
503
- } elseif ($map.ContainsKey($token)) {
504
- $mainKeys += $token
505
- } elseif ($token.Length -eq 1) {
506
- $mainKeys += $token
507
- } else {
508
- throw "unknown key '$token'"
568
+ if ($modifierNames -contains $token) { $modifiers += $token } else { $mainKeys += $token }
569
+ }
570
+ $focused = $false
571
+ try { $focused = [DshClickWin32]::BringToFront($hwnd) } catch { $focused = $false }
572
+ if (-not $focused) {
573
+ # Last resort: posted messages (only works for windows that accept posted input).
574
+ foreach ($modifier in $modifiers) {
575
+ [void][DshClickWin32]::PostMessage($hwnd, 0x100, [IntPtr](Get-VirtualKey $modifier), [IntPtr]0)
509
576
  }
577
+ foreach ($key in $mainKeys) {
578
+ $vk = Get-VirtualKey $key
579
+ [void][DshClickWin32]::PostMessage($hwnd, 0x100, [IntPtr]$vk, [IntPtr]0)
580
+ [void][DshClickWin32]::PostMessage($hwnd, 0x101, [IntPtr]$vk, [IntPtr]0)
581
+ }
582
+ for ($i = $modifiers.Count - 1; $i -ge 0; $i--) {
583
+ [void][DshClickWin32]::PostMessage($hwnd, 0x101, [IntPtr](Get-VirtualKey $modifiers[$i]), [IntPtr]0)
584
+ }
585
+ return
510
586
  }
511
- foreach ($modifier in $modifiers) {
512
- $entry = $map[$modifier]
513
- [void][DshClickWin32]::PostMessage($hwnd, 0x100, [IntPtr]$entry[0], [IntPtr]0)
514
- }
587
+ foreach ($modifier in $modifiers) { [void][DshClickWin32]::SendVirtualKey([uint16](Get-VirtualKey $modifier), $false) }
515
588
  foreach ($key in $mainKeys) {
516
- $isSpecial = $map.ContainsKey($key)
517
- if ($isSpecial) {
518
- $entry = $map[$key]
519
- [void][DshClickWin32]::PostMessage($hwnd, 0x100, [IntPtr]$entry[0], [IntPtr]0)
520
- [void][DshClickWin32]::PostMessage($hwnd, 0x101, [IntPtr]$entry[0], [IntPtr]0)
521
- } else {
522
- $charCode = [int][char]$key
523
- [void][DshClickWin32]::PostMessage($hwnd, 0x100, [IntPtr]$charCode, [IntPtr]0)
524
- [void][DshClickWin32]::PostMessage($hwnd, 0x102, [IntPtr]$charCode, [IntPtr]0)
525
- [void][DshClickWin32]::PostMessage($hwnd, 0x101, [IntPtr]$charCode, [IntPtr]0)
526
- }
589
+ $vk = [uint16](Get-VirtualKey $key)
590
+ [void][DshClickWin32]::SendVirtualKey($vk, $false)
591
+ [void][DshClickWin32]::SendVirtualKey($vk, $true)
527
592
  }
528
593
  for ($i = $modifiers.Count - 1; $i -ge 0; $i--) {
529
- $entry = $map[$modifiers[$i]]
530
- [void][DshClickWin32]::PostMessage($hwnd, 0x101, [IntPtr]$entry[0], [IntPtr]0)
594
+ [void][DshClickWin32]::SendVirtualKey([uint16](Get-VirtualKey $modifiers[$i]), $true)
531
595
  }
532
596
  }
533
597
 
@@ -799,14 +863,26 @@ function Invoke-OpType($opArgs) {
799
863
  throw "type failed and rollback $(if ($restored) { 'restored the original text' } else { 'could NOT restore the original text' }): $($_.Exception.Message)"
800
864
  }
801
865
  }
802
- if ($focusFallback) {
803
- foreach ($ch in [char[]][string]$request.text) {
804
- $charCode = [int]$ch
805
- [void][DshClickWin32]::PostMessage($hwnd, 0x102, [IntPtr]$charCode, [IntPtr]0)
866
+ # dsh-click input patch: real keyboard input (SendInput) instead of posted WM_CHAR.
867
+ $center = Get-ElementCenter $element
868
+ $windowRect = Get-WindowRectInfo $hwnd
869
+ $client = Get-ClientPoint $hwnd $windowRect $center.x $center.y
870
+ Post-Click $hwnd $client.x $client.y 'left'
871
+ $focused = $false
872
+ try { $focused = [DshClickWin32]::BringToFront($hwnd) } catch { $focused = $false }
873
+ if ($focused) {
874
+ $sent = [DshClickWin32]::SendUnicodeText([string]$request.text)
875
+ if ($sent -gt 0) {
876
+ return (Get-ActionOutcome $hwnd 'type' 'posted' $null "typed $($sent / 2) chars via SendInput")
806
877
  }
807
- return (Get-ActionOutcome $hwnd 'type' 'posted' $null 'typed via posted WM_CHAR (no value pattern on the element)')
808
878
  }
809
- throw "element '$($request.elementId)' exposes no value pattern and focusFallback is disabled - refusing to type into it"
879
+ if (-not $focusFallback) {
880
+ throw "element '$($request.elementId)' exposes no value pattern and the window could not be focused - refusing to type into it"
881
+ }
882
+ foreach ($ch in [char[]][string]$request.text) {
883
+ [void][DshClickWin32]::PostMessage($hwnd, 0x102, [IntPtr][int]$ch, [IntPtr]0)
884
+ }
885
+ return (Get-ActionOutcome $hwnd 'type' 'posted' $null 'typed via posted WM_CHAR (SendInput unavailable)')
810
886
  }
811
887
 
812
888
  function Invoke-OpScroll($opArgs) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-click",
3
- "version": "0.3.23",
3
+ "version": "0.3.25",
4
4
  "description": "Cross-platform native desktop control tools for DeepSeek Harness (Windows first): screen_shot, screen_read (accessibility tree + pixel description for text-only models), click/type/scroll/key, and app_list/app_launch — every mutating action gated by approval, never stealing foreground focus, with stale-observation rejection and process-identity checks",
5
5
  "repository": {
6
6
  "type": "git",
@@ -115,7 +115,7 @@
115
115
  "@deepseek-ai/dsh-subprocess": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0 || >=0.1.7-0 <0.2.0 || >=0.2.0-0 <0.3.0 || >=0.2.1-0 <0.3.0",
116
116
  "@deepseek-ai/dsh-tools": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0 || >=0.1.7-0 <0.2.0 || >=0.2.0-0 <0.3.0 || >=0.2.1-0 <0.3.0",
117
117
  "@deepseek-ai/dsh-user-approval": ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0 || >=0.1.7-0 <0.2.0 || >=0.2.0-0 <0.3.0 || >=0.2.1-0 <0.3.0",
118
- "@deepseek-ai/schemastery": "^3.18.5-alpha.1"
118
+ "@deepseek-ai/schemastery": "^3.18.5"
119
119
  },
120
120
  "dependencies": {},
121
121
  "devDependencies": {
@@ -131,13 +131,13 @@
131
131
  "@deepseek-ai/dsh-system-prompt": "0.2.1-rc.2",
132
132
  "@deepseek-ai/dsh-tools": "0.2.1-rc.2",
133
133
  "@deepseek-ai/dsh-user-approval": "0.2.1-rc.2",
134
- "@deepseek-ai/schemastery": "^3.18.5-alpha.1",
135
- "@types/node": "^22.19.0",
136
- "@vitest/coverage-v8": "3.2.0",
134
+ "@deepseek-ai/schemastery": "^3.18.5",
135
+ "@types/node": "^22.20.5",
136
+ "@vitest/coverage-v8": "3.2.7",
137
137
  "oxlint": "0.18.1",
138
138
  "vitest": "^3.2.0",
139
139
  "@deepseek-ai/dsh-working-directory": "0.2.1-rc.2",
140
- "tsdown": "^0.22.14",
140
+ "tsdown": "^0.23.1",
141
141
  "typescript": "^5.9.0"
142
142
  },
143
143
  "scripts": {