deepseek-computer-use 0.1.1 → 0.1.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/native/Driver.cs CHANGED
@@ -40,6 +40,7 @@ public interface IPlatformDriver : IDisposable
40
40
  Dictionary<string, object> Configure(Dictionary<string, object> p);
41
41
  Dictionary<string, object> ApproveApp(Dictionary<string, object> p);
42
42
  Dictionary<string, object> EndTurn(Dictionary<string, object> p);
43
+ Dictionary<string, object> InvalidateState(Dictionary<string, object> p);
43
44
  Dictionary<string, object> StartAudioRecording(Dictionary<string, object> p);
44
45
  Dictionary<string, object> StopAudioRecording(Dictionary<string, object> p);
45
46
  }
@@ -50,10 +51,24 @@ public interface IPlatformDriver : IDisposable
50
51
  public static class Budget
51
52
  {
52
53
  static long _deadlineTicks;
54
+ static string _cancelFile;
53
55
 
54
- public static void Begin(long ms)
56
+ public static void Begin(long ms, string cancelFile)
55
57
  {
56
58
  _deadlineTicks = ms > 0 ? DateTime.UtcNow.Ticks + ms * TimeSpan.TicksPerMillisecond : 0;
59
+ _cancelFile = cancelFile;
60
+ }
61
+
62
+ public static void Check()
63
+ {
64
+ if (_cancelFile != null && File.Exists(_cancelFile))
65
+ {
66
+ string reason = "cancelled";
67
+ try { reason = File.ReadAllText(_cancelFile).Trim(); } catch { }
68
+ if (reason != "turn_ended" && reason != "timeout") reason = "cancelled";
69
+ throw new ToolError(reason, "the native request was stopped; take a fresh observation before continuing");
70
+ }
71
+ if (Expired()) throw new ToolError("timeout", "the native request exceeded its time budget");
57
72
  }
58
73
 
59
74
  public static long RemainingMs()
@@ -206,6 +221,7 @@ public class WindowsDriver : IPlatformDriver
206
221
  readonly Dictionary<string, ShotRegion> _shots = new Dictionary<string, ShotRegion>();
207
222
  int _shotTtlMs = 120000;
208
223
  int _shotSeq = 0;
224
+ readonly string _shotGeneration = Guid.NewGuid().ToString("N");
209
225
 
210
226
  const uint GW_OWNER = 4;
211
227
  const int GWL_STYLE = -16;
@@ -218,7 +234,11 @@ public class WindowsDriver : IPlatformDriver
218
234
  try { SetProcessDPIAware(); } catch { }
219
235
  }
220
236
 
221
- public void Dispose() { }
237
+ public void Dispose()
238
+ {
239
+ if (_cursorWatchdog != null) { _cursorWatchdog.Dispose(); _cursorWatchdog = null; }
240
+ StopOverlay();
241
+ }
222
242
 
223
243
  // ---- approval plumbing ----
224
244
  public Dictionary<string, object> Configure(Dictionary<string, object> p)
@@ -255,6 +275,13 @@ public class WindowsDriver : IPlatformDriver
255
275
  return r;
256
276
  }
257
277
 
278
+ public Dictionary<string, object> InvalidateState(Dictionary<string, object> p)
279
+ {
280
+ lock (_sync) { _snapshots.Clear(); _shots.Clear(); }
281
+ StopOverlay();
282
+ return new Dictionary<string, object> { { "invalidated", true } };
283
+ }
284
+
258
285
  void AssertAppApproved(IntPtr h)
259
286
  {
260
287
  if (!_requireAppApproval) return;
@@ -385,6 +412,7 @@ public class WindowsDriver : IPlatformDriver
385
412
  args.Append(" --cancel-file \"").Append(_overlayCancelFile).Append('"');
386
413
  args.Append(" --text \"").Append(_overlayText).Append('"');
387
414
  args.Append(" --hint \"").Append(_overlayHint).Append('"');
415
+ args.Append(" --owner-pid ").Append(Process.GetCurrentProcess().Id);
388
416
  args.Append(" --accent \"").Append(_overlayAccent).Append('"');
389
417
  if (_cursorFile != null) args.Append(" --cursor-file \"").Append(_cursorFile).Append('"');
390
418
  // Suppression is the default (the virtual cursor stands in for the real one);
@@ -424,6 +452,7 @@ public class WindowsDriver : IPlatformDriver
424
452
 
425
453
  void CheckCancel(Dictionary<string, object> p)
426
454
  {
455
+ Budget.Check();
427
456
  string token = p == null ? null : Json.GetString(p, "turnToken");
428
457
  if (token != null && (token == _interruptedTurn || InterruptMarkerExists(token)))
429
458
  throw new ToolError("turn_stopped", EscMessage);
@@ -438,7 +467,7 @@ public class WindowsDriver : IPlatformDriver
438
467
  throw new ToolError("cancelled_by_user", EscMessage);
439
468
  }
440
469
 
441
- const int ProtocolVersion = 1;
470
+ const int ProtocolVersion = 2;
442
471
 
443
472
  static string InterruptMarkerPath(string token)
444
473
  {
@@ -607,6 +636,8 @@ public class WindowsDriver : IPlatformDriver
607
636
  {
608
637
  Dictionary<string, object> d = new Dictionary<string, object>();
609
638
  d["platform"] = PlatformName;
639
+ d["pid"] = (long)Process.GetCurrentProcess().Id;
640
+ d["overlayPid"] = _overlay != null && !_overlay.HasExited ? (object)(long)_overlay.Id : null;
610
641
  d["ok"] = true;
611
642
  d["uia"] = true;
612
643
  return d;
@@ -1141,7 +1172,7 @@ public class WindowsDriver : IPlatformDriver
1141
1172
  if (zIndex < 0) zIndex = total - 1;
1142
1173
  Dictionary<string, object> shot = CaptureWindow(w);
1143
1174
  shot["zIndex"] = (long)(total - zIndex); // larger = visually above
1144
- string id = "shot-" + (++_shotSeq).ToString() + "-" + w.ToInt64().ToString();
1175
+ string id = "shot-" + _shotGeneration + "-" + (++_shotSeq).ToString() + "-" + w.ToInt64().ToString();
1145
1176
  shot["id"] = id;
1146
1177
  RECT r = BoundsOf(w);
1147
1178
  lock (_sync)
@@ -1450,19 +1481,20 @@ public class WindowsDriver : IPlatformDriver
1450
1481
  SendInput(1, arr, Marshal.SizeOf(typeof(INPUT)));
1451
1482
  }
1452
1483
 
1453
- void ClickAt(int screenX, int screenY, int count, string button)
1484
+ void ClickAt(int screenX, int screenY, int count, string button, Dictionary<string, object> p)
1454
1485
  {
1455
1486
  SetCursorPos(screenX, screenY);
1456
1487
  System.Threading.Thread.Sleep(35);
1457
1488
  for (int i = 0; i < Math.Max(1, count); i++)
1458
1489
  {
1490
+ CheckCancel(p);
1459
1491
  if (i > 0) System.Threading.Thread.Sleep(60);
1460
1492
  uint down = MOUSEEVENTF_LEFTDOWN, up = MOUSEEVENTF_LEFTUP;
1461
1493
  if (button == "r") { down = MOUSEEVENTF_RIGHTDOWN; up = MOUSEEVENTF_RIGHTUP; }
1462
1494
  else if (button == "m") { down = MOUSEEVENTF_MIDDLEDOWN; up = MOUSEEVENTF_MIDDLEUP; }
1463
1495
  SendMouse(down);
1464
- System.Threading.Thread.Sleep(22);
1465
- SendMouse(up);
1496
+ try { System.Threading.Thread.Sleep(22); }
1497
+ finally { SendMouse(up); }
1466
1498
  }
1467
1499
  }
1468
1500
 
@@ -1528,7 +1560,8 @@ public class WindowsDriver : IPlatformDriver
1528
1560
 
1529
1561
  Activate(h); // pointer clicks need a real foreground window
1530
1562
  MarkCursor(sx, sy);
1531
- ClickAt(sx, sy, count, button);
1563
+ CheckCancel(p);
1564
+ ClickAt(sx, sy, count, button, p);
1532
1565
  Dictionary<string, object> r = new Dictionary<string, object>();
1533
1566
  r["clicked"] = "pointer";
1534
1567
  r["screenX"] = (long)sx;
@@ -1556,7 +1589,7 @@ public class WindowsDriver : IPlatformDriver
1556
1589
  string key = Json.GetString(p, "key");
1557
1590
  if (string.IsNullOrEmpty(key)) throw new ToolError("bad_request", "press_key requires 'key'");
1558
1591
  Activate(h);
1559
- Chord(key);
1592
+ Chord(key, p);
1560
1593
  Dictionary<string, object> r = new Dictionary<string, object>();
1561
1594
  r["pressed"] = key;
1562
1595
  return r;
@@ -1614,7 +1647,7 @@ public class WindowsDriver : IPlatformDriver
1614
1647
  SendKey(vk, 0, flags);
1615
1648
  }
1616
1649
 
1617
- static void Chord(string chord)
1650
+ void Chord(string chord, Dictionary<string, object> p)
1618
1651
  {
1619
1652
  string[] parts = chord.Split('+');
1620
1653
  List<ushort> keys = new List<ushort>();
@@ -1625,8 +1658,17 @@ public class WindowsDriver : IPlatformDriver
1625
1658
  keys.Add(ResolveKey(token));
1626
1659
  }
1627
1660
  if (keys.Count == 0) throw new ToolError("bad_request", "empty key chord");
1628
- for (int i = 0; i < keys.Count; i++) { SendVk(keys[i], false); System.Threading.Thread.Sleep(12); }
1629
- for (int i = keys.Count - 1; i >= 0; i--) { SendVk(keys[i], true); System.Threading.Thread.Sleep(12); }
1661
+ int pressed = 0;
1662
+ try
1663
+ {
1664
+ for (int i = 0; i < keys.Count; i++)
1665
+ {
1666
+ CheckCancel(p);
1667
+ SendVk(keys[i], false); pressed++;
1668
+ System.Threading.Thread.Sleep(12);
1669
+ }
1670
+ }
1671
+ finally { for (int i = pressed - 1; i >= 0; i--) SendVk(keys[i], true); }
1630
1672
  }
1631
1673
 
1632
1674
  /// The Windows/Meta key family is hard-denied: it opens Run, Settings and
@@ -1667,6 +1709,7 @@ public class WindowsDriver : IPlatformDriver
1667
1709
  Activate(h);
1668
1710
  for (int i = 0; i < text.Length; i++)
1669
1711
  {
1712
+ CheckCancel(p);
1670
1713
  char c = text[i];
1671
1714
  if (c == '\n') { SendVk(0x0D, false); SendVk(0x0D, true); continue; }
1672
1715
  if (c == '\t') { SendVk(0x09, false); SendVk(0x09, true); continue; }
@@ -1764,18 +1807,24 @@ public class WindowsDriver : IPlatformDriver
1764
1807
  MarkCursor(fx, fy);
1765
1808
  SetCursorPos(fx, fy);
1766
1809
  System.Threading.Thread.Sleep(60);
1810
+ CheckCancel(p);
1767
1811
  SendMouse(MOUSEEVENTF_LEFTDOWN);
1768
- System.Threading.Thread.Sleep(80);
1769
- const int steps = 14;
1770
- for (int i = 1; i <= steps; i++)
1812
+ try
1771
1813
  {
1772
- int x = fx + (tx - fx) * i / steps;
1773
- int y = fy + (ty - fy) * i / steps;
1774
- SetCursorPos(x, y);
1775
- System.Threading.Thread.Sleep(18);
1814
+ System.Threading.Thread.Sleep(80);
1815
+ const int steps = 14;
1816
+ for (int i = 1; i <= steps; i++)
1817
+ {
1818
+ CheckCancel(p);
1819
+ int x = fx + (tx - fx) * i / steps;
1820
+ int y = fy + (ty - fy) * i / steps;
1821
+ SetCursorPos(x, y);
1822
+ System.Threading.Thread.Sleep(18);
1823
+ }
1824
+ System.Threading.Thread.Sleep(90);
1825
+ CheckCancel(p);
1776
1826
  }
1777
- System.Threading.Thread.Sleep(90);
1778
- SendMouse(MOUSEEVENTF_LEFTUP);
1827
+ finally { SendMouse(MOUSEEVENTF_LEFTUP); }
1779
1828
 
1780
1829
  Dictionary<string, object> r = new Dictionary<string, object>();
1781
1830
  r["dragged"] = true;
package/native/Overlay.cs CHANGED
@@ -22,6 +22,7 @@ public sealed class Config
22
22
  public static string Title = "DSH 正在使用您的电脑";
23
23
  public static string Hint = "Esc 取消";
24
24
  public static bool SuppressCursor = false;
25
+ public static int OwnerPid = 0;
25
26
  public static int Corner1 = 0; // unused placeholder kept for arg compatibility
26
27
  public static Color Accent = Color.FromArgb(63, 127, 216);
27
28
  }
@@ -557,6 +558,11 @@ internal sealed class Controller : ApplicationContext
557
558
  void Tick()
558
559
  {
559
560
  _tick++;
561
+ if (Config.OwnerPid > 0)
562
+ {
563
+ try { if (System.Diagnostics.Process.GetProcessById(Config.OwnerPid).HasExited) { ExitThreadCore(); return; } }
564
+ catch { ExitThreadCore(); return; }
565
+ }
560
566
  if (DateTime.UtcNow > _deadline) { ExitThreadCore(); return; }
561
567
 
562
568
  Rectangle screen = SystemInformation.VirtualScreen;
@@ -697,6 +703,7 @@ internal static class Program
697
703
  else if (a == "--text") Config.Title = args[i + 1];
698
704
  else if (a == "--hint") Config.Hint = args[i + 1];
699
705
  else if (a == "--ttl-ms") int.TryParse(args[i + 1], out ttlMs);
706
+ else if (a == "--owner-pid") int.TryParse(args[i + 1], out Config.OwnerPid);
700
707
  }
701
708
  Config.SuppressCursor = true; // Codex default
702
709
  for (int i = 0; i < args.Length; i++) if (args[i] == "--no-suppress-cursor") Config.SuppressCursor = false;
package/native/Program.cs CHANGED
@@ -88,17 +88,19 @@ public static class Program
88
88
  if (p == null) p = new Dictionary<string, object>();
89
89
  // Codex ships the budget with every call (x-oai-cua-request-budget-ms) and the
90
90
  // helper honours it; here it bounds capture waits and marks overruns.
91
- Budget.Begin(Json.GetLong(p, "__budgetMs", 0));
91
+ Budget.Begin(Json.GetLong(p, "__budgetMs", 0), Json.GetString(p, "__cancelFile"));
92
92
  try { if (_driver != null) _driver.Maintenance(); } catch { }
93
93
  object result = Dispatch(method, p);
94
94
  return Ok(id, result);
95
95
  }
96
96
  catch (ToolError te)
97
97
  {
98
+ try { _driver.InvalidateState(new Dictionary<string, object>()); } catch { }
98
99
  return Fail(id, te.Code, te.Message);
99
100
  }
100
101
  catch (Exception ex)
101
102
  {
103
+ try { _driver.InvalidateState(new Dictionary<string, object>()); } catch { }
102
104
  return Fail(id, "internal_error", ex.GetType().Name + ": " + ex.Message);
103
105
  }
104
106
  }
@@ -130,6 +132,7 @@ public static class Program
130
132
  case "configure": return _driver.Configure(p);
131
133
  case "approve_app": return _driver.ApproveApp(p);
132
134
  case "end_turn": return _driver.EndTurn(p);
135
+ case "invalidate_state": return _driver.InvalidateState(p);
133
136
  case "start_audio_recording": return _driver.StartAudioRecording(p);
134
137
  case "stop_audio_recording": return _driver.StopAudioRecording(p);
135
138
  case "shutdown": return new Dictionary<string, object> { { "bye", true } };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "deepseek-computer-use",
3
- "version": "0.1.1",
4
- "description": "Windows Computer Use for DeepSeek Harness: UIA observation, SendInput actions, Windows.Graphics.Capture — modeled on Codex's window2 API. Community plugin, not an official DeepSeek package.",
3
+ "version": "0.1.2",
4
+ "description": "在 DSH 中查看和操作 Windows 桌面软件,支持窗口截图、鼠标键盘与控件操作。 欢迎提交 issues 和 PR。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -20,15 +20,19 @@
20
20
  "native/wgcfw/Capture.csproj",
21
21
  "native/wgcfw/Program.cs",
22
22
  "skills",
23
+ "assets/cover-whale-girl.png",
23
24
  "cordis.patch.yml",
24
25
  "README.md",
26
+ "CHANGELOG.md",
25
27
  "LICENSE"
26
28
  ],
27
29
  "scripts": {
28
30
  "build": "node lib/build.mjs",
29
31
  "build:helper": "node lib/build.mjs",
30
- "test": "node test/integration.mjs",
31
- "test:all": "node test/integration.mjs && node test/parity.mjs && node test/parity-js.mjs"
32
+ "test": "node --test test/contracts.test.mjs test/bridge.test.mjs && node test/integration.mjs",
33
+ "test:contracts": "node --test test/contracts.test.mjs test/bridge.test.mjs",
34
+ "test:harness": "node test/harness.mjs",
35
+ "test:all": "npm test && node test/parity.mjs && node test/parity-js.mjs"
32
36
  },
33
37
  "dsh": {
34
38
  "bundle": {
@@ -5,7 +5,8 @@ description: Drive Windows desktop apps through DSH Computer Use tools (UIA obse
5
5
 
6
6
  # Computer Use (Windows)
7
7
 
8
- The `deepseek-computer-use` plugin exposes the Codex window2 API on Windows.
8
+ The `deepseek-computer-use` plugin provides Windows desktop tools for DSH,
9
+ using window2-style action and observation semantics.
9
10
  Prefer shell, files and web tools whenever they can do the job: GUI automation is
10
11
  the slowest, most fragile option and it moves the user's real mouse and keyboard.
11
12
 
@@ -81,10 +82,12 @@ submit, publish, install, 删除, 卸载, 支付, 提交 …) asks for confirmat
81
82
  |---|---|---|
82
83
  | `stale_snapshot` | element indexes expired | `get_window_state include_text:true` again |
83
84
  | `stale_screenshot` | coordinate screenshot expired | observe again and use the new id |
85
+ | `fresh_observation_required` | an earlier input or capture failed | observe again; discard every old index, screenshot id and coordinate before deciding the next action |
86
+ | `timeout` | the old helper was stopped and its snapshots discarded | take a fresh observation; never repeat an input whose outcome is unknown |
87
+ | `cancelled` / `turn_ended` | the active native request was stopped | stop issuing input and wait for a new turn |
84
88
  | `window_gone` | app closed / handle changed | `list_windows` again |
85
89
  | `window_minimized` | nothing to observe | `activate_window`, then observe |
86
90
  | `focus_failed` | another window holds the foreground | stop, ask the user; do not retry in a loop |
87
91
  | `screen_locked` | the desktop is locked (LockApp) | stop and ask the user to unlock |
88
92
  | `hard_denied` | the surface is never automatable | tell the user, do not route around it |
89
- | `turn_ended` | the turn is over | wait for the user to continue |
90
93
  | empty tree | Electron/Canvas surface | observe with the screenshot and act by coordinates |