dsh-click 0.3.22 → 0.3.24

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 +3 -1
  4. package/README-hi.md +3 -1
  5. package/README-pt.md +3 -1
  6. package/README-zh.md +3 -1
  7. package/README.md +1 -0
  8. package/SECURITY.md +0 -0
  9. package/THIRD_PARTY_NOTICES.md +0 -0
  10. package/cordis.patch.yml +0 -0
  11. package/lib/index.js +1 -1
  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 +0 -0
  15. package/lib/types/config.d.ts.map +0 -0
  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 +0 -0
  21. package/lib/types/observe.d.ts.map +0 -0
  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 -70
  43. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.24] - 2026-10-19
4
+
5
+ ### Fixed
6
+
7
+ - **Real keyboard input and foreground focus for the Windows helper (PR #21).** `type` now uses `SendInput` with `KEYEVENTF_UNICODE` (CJK-safe) instead of relying only on posted `WM_CHAR`, and element/key actions can bring the target window to the foreground via `AttachThreadInput` + `SetForegroundWindow` + `SetFocus`. The UIA `ValuePattern` fast path remains. Credit: @LIU12312-Source.
8
+
9
+ ## [0.3.23] - 2026-10-19
10
+
11
+ ### Fixed
12
+
13
+ - **`elementId` click/type and every `key` combination on Windows (issue #20).** `Find-ElementByRuntimeId` builds an `Int32[]` and passes it via the `PropertyCondition` constructor; the F-key/digit map uses explicit parentheses around the arithmetic; and the A–Z map iterates `65..90` with string keys, matching `Post-KeyCombo`'s lookup. Three one-line helper defects that made these paths throw before posting input.
14
+
3
15
  ## [0.3.22] - 2026-10-18
4
16
 
5
17
  ### Changed
package/LICENSE CHANGED
File without changes
package/README-es.md CHANGED
@@ -319,7 +319,9 @@ Este proyecto es uno de los **87 plugins activamente mantenidos** de DeepSeek Ha
319
319
 
320
320
  </details>
321
321
 
322
- </details>## License
322
+ </details>
323
+
324
+ ## License
323
325
 
324
326
  [Apache License 2.0](LICENSE) © 2026 dsh-click contributors
325
327
 
package/README-hi.md CHANGED
@@ -319,7 +319,9 @@ pnpm pack # प्रकाशित tarball
319
319
 
320
320
  </details>
321
321
 
322
- </details>## License
322
+ </details>
323
+
324
+ ## License
323
325
 
324
326
  [Apache License 2.0](LICENSE) © 2026 dsh-click contributors
325
327
 
package/README-pt.md CHANGED
@@ -319,7 +319,9 @@ Este projeto é um dos **87 plugins ativamente mantidos** do DeepSeek Harness da
319
319
 
320
320
  </details>
321
321
 
322
- </details>## License
322
+ </details>
323
+
324
+ ## License
323
325
 
324
326
  [Apache License 2.0](LICENSE) © 2026 dsh-click contributors
325
327
 
package/README-zh.md CHANGED
@@ -319,7 +319,9 @@ pnpm pack # 发布用 tarball
319
319
 
320
320
  </details>
321
321
 
322
- </details>## License
322
+ </details>
323
+
324
+ ## License
323
325
 
324
326
  [Apache License 2.0](LICENSE) © 2026 dsh-click contributors
325
327
 
package/README.md CHANGED
@@ -208,6 +208,7 @@ pnpm pack # the published tarball
208
208
 
209
209
  ## Contributors
210
210
 
211
+ - [@LIU12312-Source](https://github.com/LIU12312-Source) — real `SendInput`/foreground-focus fallback for the Windows helper (PR #21).
211
212
  - [@PerryLink](https://github.com/PerryLink) — creator and maintainer: tool surface, action safety boundary, Windows native helper, sanitizers, and the five-language docs.
212
213
  - [@Mchsd](https://github.com/Mchsd) — added the `auditSessionEvents` opt-out for harnesses whose session reader rejects the `dsh-click` audit events (#2).
213
214
 
package/SECURITY.md CHANGED
File without changes
File without changes
package/cordis.patch.yml CHANGED
File without changes
package/lib/index.js CHANGED
@@ -586,7 +586,7 @@ var ObservationStore = class {
586
586
  * @module dsh-click/version
587
587
  */
588
588
  /** Plugin version advertised in the helper-protocol handshake. */
589
- const VERSION = "0.3.22";
589
+ const VERSION = "0.3.24";
590
590
  /** The helper protocol revision; bump only on incompatible wire changes. */
591
591
  const HELPER_PROTOCOL_VERSION = 1;
592
592
  //#endregion
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -7,7 +7,7 @@
7
7
  * @module dsh-click/version
8
8
  */
9
9
  /** Plugin version advertised in the helper-protocol handshake. */
10
- export declare const VERSION = "0.3.22";
10
+ export declare const VERSION = "0.3.24";
11
11
  /** The helper protocol revision; bump only on incompatible wire changes. */
12
12
  export declare const HELPER_PROTOCOL_VERSION = 1;
13
13
  //# sourceMappingURL=version.d.ts.map
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -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 = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::RuntimeIdProperty, $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,46 +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 ($c in 'A'..'Z') {
484
- $code = [int][char]$c
485
- $map[$c] = @(0x41 + ($code - 65), $true)
486
- }
487
- 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'"
488
554
  }
489
555
 
490
556
  function Post-KeyCombo($hwnd, $keysText) {
491
- $map = Get-KeyMap
492
557
  $tokens = @()
493
558
  foreach ($token in ($keysText -split '\+')) {
494
559
  $trimmed = $token.Trim()
@@ -496,39 +561,37 @@ function Post-KeyCombo($hwnd, $keysText) {
496
561
  $tokens += $trimmed.ToUpperInvariant()
497
562
  }
498
563
  if ($tokens.Count -eq 0) { throw 'empty key combination' }
564
+ $modifierNames = @('CTRL', 'CONTROL', 'SHIFT', 'ALT', 'MENU', 'WIN', 'WINDOWS', 'LWIN', 'RWIN')
499
565
  $modifiers = @()
500
566
  $mainKeys = @()
501
567
  foreach ($token in $tokens) {
502
- if ($token -in @('CTRL', 'CONTROL', 'SHIFT', 'ALT', 'MENU', 'WIN', 'WINDOWS')) {
503
- $modifiers += $token
504
- } elseif ($map.ContainsKey($token)) {
505
- $mainKeys += $token
506
- } elseif ($token.Length -eq 1) {
507
- $mainKeys += $token
508
- } else {
509
- 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)
510
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
511
586
  }
512
- foreach ($modifier in $modifiers) {
513
- $entry = $map[$modifier]
514
- [void][DshClickWin32]::PostMessage($hwnd, 0x100, [IntPtr]$entry[0], [IntPtr]0)
515
- }
587
+ foreach ($modifier in $modifiers) { [void][DshClickWin32]::SendVirtualKey([uint16](Get-VirtualKey $modifier), $false) }
516
588
  foreach ($key in $mainKeys) {
517
- $isSpecial = $map.ContainsKey($key)
518
- if ($isSpecial) {
519
- $entry = $map[$key]
520
- [void][DshClickWin32]::PostMessage($hwnd, 0x100, [IntPtr]$entry[0], [IntPtr]0)
521
- [void][DshClickWin32]::PostMessage($hwnd, 0x101, [IntPtr]$entry[0], [IntPtr]0)
522
- } else {
523
- $charCode = [int][char]$key
524
- [void][DshClickWin32]::PostMessage($hwnd, 0x100, [IntPtr]$charCode, [IntPtr]0)
525
- [void][DshClickWin32]::PostMessage($hwnd, 0x102, [IntPtr]$charCode, [IntPtr]0)
526
- [void][DshClickWin32]::PostMessage($hwnd, 0x101, [IntPtr]$charCode, [IntPtr]0)
527
- }
589
+ $vk = [uint16](Get-VirtualKey $key)
590
+ [void][DshClickWin32]::SendVirtualKey($vk, $false)
591
+ [void][DshClickWin32]::SendVirtualKey($vk, $true)
528
592
  }
529
593
  for ($i = $modifiers.Count - 1; $i -ge 0; $i--) {
530
- $entry = $map[$modifiers[$i]]
531
- [void][DshClickWin32]::PostMessage($hwnd, 0x101, [IntPtr]$entry[0], [IntPtr]0)
594
+ [void][DshClickWin32]::SendVirtualKey([uint16](Get-VirtualKey $modifiers[$i]), $true)
532
595
  }
533
596
  }
534
597
 
@@ -800,14 +863,26 @@ function Invoke-OpType($opArgs) {
800
863
  throw "type failed and rollback $(if ($restored) { 'restored the original text' } else { 'could NOT restore the original text' }): $($_.Exception.Message)"
801
864
  }
802
865
  }
803
- if ($focusFallback) {
804
- foreach ($ch in [char[]][string]$request.text) {
805
- $charCode = [int]$ch
806
- [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")
807
877
  }
808
- return (Get-ActionOutcome $hwnd 'type' 'posted' $null 'typed via posted WM_CHAR (no value pattern on the element)')
809
878
  }
810
- 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)')
811
886
  }
812
887
 
813
888
  function Invoke-OpScroll($opArgs) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-click",
3
- "version": "0.3.22",
3
+ "version": "0.3.24",
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",