@momasiku/dsh-pilot 0.5.12 → 0.5.16
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/README.md +36 -1
- package/lib/index.js +87 -16
- package/lib/scripts/_dsh-uia.ps1 +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
# dsh-pilot
|
|
4
4
|
|
|
5
|
+
## 让 DSH 看见并高效地操作你的电脑
|
|
6
|
+
|
|
5
7
|
**Give DeepSeek Harness hands and eyes on your desktop.**
|
|
6
8
|
|
|
9
|
+
[](https://awesome-dsh-plugin.com/p/momasiku/dsh-pilot/)
|
|
7
10
|
[](https://www.npmjs.com/package/@momasiku/dsh-pilot)
|
|
8
11
|
[](https://github.com/momasiku/dsh-pilot/actions/workflows/compat.yml)
|
|
9
12
|
[](LICENSE)
|
|
10
13
|
|
|
11
14
|

|
|
12
|
-

|
|
13
15
|
|
|
14
16
|
[English](#why-this-exists) · [中文说明](#中文说明)
|
|
15
17
|
|
|
@@ -232,6 +234,30 @@ and what `dsh plugin add` checks before it installs anything. Requirements:
|
|
|
232
234
|
|
|
233
235
|
### Recent changes
|
|
234
236
|
|
|
237
|
+
**0.5.14** — the coordinate contract stops guessing how big the image you see is.
|
|
238
|
+
|
|
239
|
+
- **The request pipeline resizes again after capture**, and this plugin never saw
|
|
240
|
+
that step: on a 2560x1600 display the frame reached the model as 1610x1006 while
|
|
241
|
+
the contract said "delivered at capture size … do not rescale", so every reading
|
|
242
|
+
taken off it was 1.59x off. The contract now **predicts** the projected size from
|
|
243
|
+
the image budget the deployment publishes, and prints the size and the multiplier:
|
|
244
|
+
`2560x1600 capture -> 1609x1006 preview, multiply every reading by 1.5911`. A frame
|
|
245
|
+
whose area fits the budget arrives untouched, so framing the window or a region
|
|
246
|
+
gives a true 1:1 frame — 1365x960 does, 1024x768 does, 4K and 5K say 2.2642x and
|
|
247
|
+
3.0189x instead.
|
|
248
|
+
|
|
249
|
+
**0.5.12** — a cold tree, and an empty list that explained nothing.
|
|
250
|
+
|
|
251
|
+
- **Chromium builds its accessibility tree lazily**, and the first caller pays for it:
|
|
252
|
+
measured on the harness window right after a restart, 3 elements at 1.2 s and 77
|
|
253
|
+
elements at 0.3 s on the next call. A caller that trusts the first answer concludes
|
|
254
|
+
the window has no tree and falls back to pixels for nothing. `controls` now retries
|
|
255
|
+
once, 300 ms later, when the worker has not yet seen a real tree and the answer is
|
|
256
|
+
suspiciously small.
|
|
257
|
+
- **A filtered listing that legitimately matches nothing** now says so, instead of
|
|
258
|
+
returning an empty list that reads as "this window has no tree":
|
|
259
|
+
`(no operable controls here; the window has 2 elements - pass meaningfulOnly:false to see them)`.
|
|
260
|
+
|
|
235
261
|
**0.5.11** — the control tree, and two fixes that make a batch honest.
|
|
236
262
|
|
|
237
263
|
- **`controls` reads the window's UI Automation tree**, and `target` clicks a listed
|
|
@@ -616,6 +642,15 @@ dsh plugin --profile <你的profile> add "file:E:\path\to\dsh-pilot"
|
|
|
616
642
|
|
|
617
643
|
### 最近改动
|
|
618
644
|
|
|
645
|
+
**0.5.14** —— 坐标契约不再猜「你看到的那张图有多大」。
|
|
646
|
+
|
|
647
|
+
- **请求管线会在抓取之后再缩一次**,而插件看不到这一步:2560×1600 的屏幕上,帧送到模型时是 1610×1006,契约却写着「交付即抓取尺寸……不要缩放」,于是每个读数都偏 1.59 倍。现在契约用部署公布的图片预算**预测**投影尺寸,并把尺寸和乘数一起写出来:`2560x1600 抓取 → 1609x1006 预览,把每个读数乘 1.5911`。面积落在预算内的帧会原样送达,所以抓窗口或抓区域能得到真正的 1:1 —— 1365×960 可以,1024×768 可以;4K 和 5K 则分别写 2.2642 倍和 3.0189 倍。
|
|
648
|
+
|
|
649
|
+
**0.5.12** —— 冷树,以及一个什么都没说的空列表。
|
|
650
|
+
|
|
651
|
+
- **Chromium 的无障碍树是懒加载的**,而且由第一个来问的人付账:实测重启后在 1.2 秒时只有 3 个元素,下一次调用 0.3 秒时是 77 个。信任第一个答案的人会认定「这窗口没有树」,白白退回像素。现在 `controls` 在「worker 还没见过完整树、且结果异常少」时会等 300 ms 重试一次。
|
|
652
|
+
- **过滤后确实什么都没匹配到时,现在会明说**,而不是返回一个读起来像「这里没有树」的空列表:`(no operable controls here; the window has 2 elements - pass meaningfulOnly:false to see them)`。
|
|
653
|
+
|
|
619
654
|
**0.5.11** —— 控件树,外加两个让批量操作「不说谎」的修复。
|
|
620
655
|
|
|
621
656
|
- **`controls` 读窗口的 UI Automation 树**,`target` 点列表里那个控件的精确矩形,于是有名字的控件不必再去像素里找。它不截图,所以也不付 settle。在 673 个元素的窗口上实测:过滤后 **0.65 秒、约 850 token**(77 个可操作元素),不过滤约 9k。暖 worker 记着它发出去的那份列表,所以某一行换了身份会报错,而不是点到搬进来的那个。
|
package/lib/index.js
CHANGED
|
@@ -654,6 +654,11 @@ async function capture(ctx, exec, config, args) {
|
|
|
654
654
|
...(ref.name === undefined ? {} : { name: ref.name }),
|
|
655
655
|
...(ref.originalDimensions === undefined ? {} : { originalDimensions: { ...ref.originalDimensions } })
|
|
656
656
|
};
|
|
657
|
+
// Predict where the request pipeline will put this frame, from the same budget
|
|
658
|
+
// it uses. A prediction, always paired with the deferral to the preview size the
|
|
659
|
+
// model is actually shown.
|
|
660
|
+
const projected = projectedRequestSize(Number(result.image.width), Number(result.image.height), limits);
|
|
661
|
+
if (projected !== undefined) result.requestPreview = projected;
|
|
657
662
|
} catch (error) {
|
|
658
663
|
result.imageError = `the frame could not be attached: ${error instanceof Error ? error.message : String(error)}`;
|
|
659
664
|
}
|
|
@@ -736,26 +741,76 @@ function captureContent(value) {
|
|
|
736
741
|
* @param value - a capture, or a tool result carrying one under `capture`.
|
|
737
742
|
* @returns the contract text for that frame.
|
|
738
743
|
*/
|
|
744
|
+
/**
|
|
745
|
+
* Where the request pipeline will put this frame.
|
|
746
|
+
*
|
|
747
|
+
* The store hands back a normalized image, and then the request pipeline shrinks it
|
|
748
|
+
* again to fit the model's image budget - after this plugin has let go of it, which
|
|
749
|
+
* is why a frame that really was 1:1 at capture can still reach the model smaller.
|
|
750
|
+
* The geometry is a pure function of the budget the deployment publishes, so the
|
|
751
|
+
* pipeline can be predicted instead of described. Mirrors requestImageDimensions()
|
|
752
|
+
* in @deepseek-ai/dsh-attachment, and kept local so a drift between the two can only
|
|
753
|
+
* cost accuracy: the contract always defers to the preview size the message states.
|
|
754
|
+
*
|
|
755
|
+
* @param width - normalized image width.
|
|
756
|
+
* @param height - normalized image height.
|
|
757
|
+
* @param limits - the deployment's ImageAttachmentLimits.
|
|
758
|
+
* @returns predicted request dimensions plus the budget they came from, or undefined.
|
|
759
|
+
*/
|
|
760
|
+
function projectedRequestSize(width, height, limits) {
|
|
761
|
+
const maxPixels = Number(limits?.maxImagePixels);
|
|
762
|
+
const maxDimension = Number(limits?.maxImageDimension);
|
|
763
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return undefined;
|
|
764
|
+
let w = width;
|
|
765
|
+
let h = height;
|
|
766
|
+
if (Number.isFinite(maxDimension) && maxDimension > 0 && Math.max(w, h) > maxDimension) {
|
|
767
|
+
if (w >= h) { h = Math.max(1, Math.round(maxDimension * h / w)); w = maxDimension; }
|
|
768
|
+
else { w = Math.max(1, Math.round(maxDimension * w / h)); h = maxDimension; }
|
|
769
|
+
}
|
|
770
|
+
if (Number.isFinite(maxPixels) && maxPixels > 0 && w * h > maxPixels) {
|
|
771
|
+
const s = Math.min(1, Math.sqrt(maxPixels / (w * h)));
|
|
772
|
+
if (w >= h) {
|
|
773
|
+
w = Math.max(1, Math.floor(w * s));
|
|
774
|
+
h = Math.max(1, Math.round(w * height / width));
|
|
775
|
+
while (w * h > maxPixels && w > 1) { w -= 1; h = Math.max(1, Math.round(w * height / width)); }
|
|
776
|
+
} else {
|
|
777
|
+
h = Math.max(1, Math.floor(h * s));
|
|
778
|
+
w = Math.max(1, Math.round(h * width / height));
|
|
779
|
+
while (w * h > maxPixels && h > 1) { h -= 1; w = Math.max(1, Math.round(h * width / height)); }
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
const out = { width: w, height: h };
|
|
783
|
+
if (Number.isFinite(maxPixels) && maxPixels > 0) out.maxPixels = maxPixels;
|
|
784
|
+
return out;
|
|
785
|
+
}
|
|
739
786
|
function coordinateContract(value) {
|
|
740
787
|
const capture = value?.capture ?? value;
|
|
788
|
+
const width = Number(capture?.imageWidth);
|
|
789
|
+
const height = Number(capture?.imageHeight);
|
|
790
|
+
const sizeKnown = Number.isFinite(width) && width > 0 && Number.isFinite(height) && height > 0;
|
|
791
|
+
const frame = sizeKnown ? `${width}x${height}` : 'the captured frame';
|
|
741
792
|
const scale = deliveredScale(capture);
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
793
|
+
const storeShrank = !scaleIsOne(scale);
|
|
794
|
+
// No prediction. The request pipeline resizes a frame to fit the model's image
|
|
795
|
+
// budget, after this plugin has let go of it, and the budget it uses is not the
|
|
796
|
+
// deployment's admission limit the plugin can read: measured 64,000,000 in
|
|
797
|
+
// imageLimits.maxImagePixels against an actual projection of ~1,640,000. A
|
|
798
|
+
// predicted size built from the wrong number reads as a promise, so the contract
|
|
799
|
+
// stops predicting and points at the one size that is not a guess - the preview
|
|
800
|
+
// size the message itself states.
|
|
801
|
+
const lines = [
|
|
802
|
+
`Image pixel (0,0) is the top-left of this frame, which was captured at ${frame} physical screen pixels; both halves of this device are per-monitor DPI aware.`
|
|
803
|
+
];
|
|
804
|
+
if (storeShrank) {
|
|
805
|
+
lines.push(axesAgree(scale)
|
|
806
|
+
? `The attachment store already delivered it at ${printed((scale.x + scale.y) / 2)}x that.`
|
|
807
|
+
: `The attachment store already delivered it at ${printed(scale.x)}x horizontally and ${printed(scale.y)}x vertically.`);
|
|
754
808
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
`
|
|
758
|
-
|
|
809
|
+
lines.push('The image you are looking at may be smaller again: the request pipeline resizes a frame to fit the model image budget, and this plugin cannot see that step, so do not trust the capture size as the size on screen. This same message states what it settled on as "request preview WxH" - that number is authoritative.');
|
|
810
|
+
lines.push(sizeKnown
|
|
811
|
+
? `If the preview is not ${frame}, multiply every reading by ${width} / preview_width (and ${height} / preview_height) before passing it to desktop_control; if it is, call desktop_control with exactly x and y. Ruler labels, when drawn, are already screen coordinates.`
|
|
812
|
+
: 'If the preview is smaller than the capture, multiply every reading by capture_width / preview_width before passing it to desktop_control.');
|
|
813
|
+
return lines.join(' ');
|
|
759
814
|
}
|
|
760
815
|
|
|
761
816
|
/**
|
|
@@ -941,6 +996,17 @@ function windowInfoOf(window) {
|
|
|
941
996
|
};
|
|
942
997
|
}
|
|
943
998
|
|
|
999
|
+
/** Where the request pipeline is predicted to put a frame, and the budget used. */
|
|
1000
|
+
const REQUEST_PREVIEW_SCHEMA = {
|
|
1001
|
+
type: 'object',
|
|
1002
|
+
additionalProperties: false,
|
|
1003
|
+
properties: {
|
|
1004
|
+
width: { type: 'integer', required: true },
|
|
1005
|
+
height: { type: 'integer', required: true },
|
|
1006
|
+
maxPixels: { type: 'number' }
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
|
|
944
1010
|
const CAPTURE_SCHEMA = {
|
|
945
1011
|
type: 'object',
|
|
946
1012
|
additionalProperties: false,
|
|
@@ -969,6 +1035,7 @@ const CAPTURE_SCHEMA = {
|
|
|
969
1035
|
frameHash: { type: 'string' },
|
|
970
1036
|
unchanged: { type: 'boolean' },
|
|
971
1037
|
rulers: { type: 'boolean' },
|
|
1038
|
+
requestPreview: REQUEST_PREVIEW_SCHEMA,
|
|
972
1039
|
capturedAt: { type: 'string' }
|
|
973
1040
|
}
|
|
974
1041
|
};
|
|
@@ -1378,6 +1445,10 @@ function apply(ctx, config) {
|
|
|
1378
1445
|
// Copy the declared fields only: the helper's own bookkeeping keys
|
|
1379
1446
|
// must not reach the closed output schema.
|
|
1380
1447
|
const value = { action: args.action };
|
|
1448
|
+
// A one-element list can still arrive unrolled from the helper; the output
|
|
1449
|
+
// schema wants an array, and a string here is a rejected result, not a bug the
|
|
1450
|
+
// caller can see.
|
|
1451
|
+
if (typeof acted.controls === 'string') acted.controls = [acted.controls];
|
|
1381
1452
|
for (const field of ['cursor', 'foregroundTitle', 'foregroundClass', 'foregroundProcess', 'notes', 'windowCount', 'actedAt', 'controls', 'controlCount', 'controlsTruncated', 'controlsWindow']) {
|
|
1382
1453
|
if (acted[field] !== undefined) value[field] = acted[field];
|
|
1383
1454
|
}
|
package/lib/scripts/_dsh-uia.ps1
CHANGED
|
@@ -124,7 +124,11 @@ function Format-DshUiaNodes {
|
|
|
124
124
|
$lines.Add(('{0} [{1}] ''{2}'' @ {3}{4}' -f $n.index, $n.role, $name, $rect, $tail))
|
|
125
125
|
}
|
|
126
126
|
if ($Truncated) { $lines.Add(('... truncated at the node cap ({0} elements in total)' -f $Total)) }
|
|
127
|
-
return
|
|
127
|
+
# The unary comma stops PowerShell unrolling a one-element array on return:
|
|
128
|
+
# without it a window that exposes a single control comes back as a bare
|
|
129
|
+
# string, and the result then fails its own schema ("controls must be an
|
|
130
|
+
# array"). Measured on a LibreOffice window that publishes exactly one.
|
|
131
|
+
return ,($lines.ToArray())
|
|
128
132
|
}
|
|
129
133
|
|
|
130
134
|
<# Resolve an index back to an element by re-reading the same listing. #>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@momasiku/dsh-pilot",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.16",
|
|
4
4
|
"description": "Desktop vision and control for DeepSeek Harness - the whole Windows machine, any application, not browser automation: screen_view captures the live screen as an image the model can see, desktop_control drives the mouse, keyboard, and windows against the exact same pixel space; desktop_sequence runs a whole batch of actions in one call and looks once at the end.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "momasiku",
|