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/CHANGELOG.md +16 -0
- package/README.md +66 -21
- package/assets/cover-whale-girl.png +0 -0
- package/lib/bridge.js +153 -150
- package/lib/build-helper.mjs +18 -4
- package/lib/build.mjs +2 -2
- package/lib/index.js +51 -35
- package/lib/parameters.js +50 -0
- package/lib/safety.js +2 -9
- package/lib/tools.js +131 -32
- package/native/Driver.cs +70 -21
- package/native/Overlay.cs +7 -0
- package/native/Program.cs +4 -1
- package/package.json +8 -4
- package/skills/computer-use/SKILL.md +5 -2
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1629
|
-
|
|
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
|
-
|
|
1769
|
-
const int steps = 14;
|
|
1770
|
-
for (int i = 1; i <= steps; i++)
|
|
1812
|
+
try
|
|
1771
1813
|
{
|
|
1772
|
-
|
|
1773
|
-
int
|
|
1774
|
-
|
|
1775
|
-
|
|
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
|
-
|
|
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.
|
|
4
|
-
"description": "
|
|
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:
|
|
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
|
|
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 |
|