dsh-codex-style-computer-use 1.3.0 → 2.0.0

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 (41) hide show
  1. package/README.md +53 -22
  2. package/README.zh.md +46 -21
  3. package/icon.svg +29 -0
  4. package/lib/audit.js +7 -0
  5. package/lib/batch.js +14 -19
  6. package/lib/bin/attention-worker.exe +0 -0
  7. package/lib/bin/attention-worker.exe.json +1 -1
  8. package/lib/compact-feedback.js +175 -0
  9. package/lib/continuation.js +1 -1
  10. package/lib/core/bin/dsh-computer-use-worker.exe +0 -0
  11. package/lib/core/bin/dsh-computer-use-worker.exe.json +2 -2
  12. package/lib/core/compile-worker.ps1 +5 -1
  13. package/lib/core/index.js +452 -129
  14. package/lib/core/native-runtime.js +23 -1
  15. package/lib/core/winmd/Windows.winmd +0 -0
  16. package/lib/core/worker.cs +2415 -356
  17. package/lib/feedback-routes.json +11 -0
  18. package/lib/feedback.js +49 -16
  19. package/lib/image-output.js +59 -11
  20. package/lib/index.js +42 -7
  21. package/lib/key-command.js +37 -0
  22. package/lib/media-contract.js +43 -0
  23. package/lib/observations.js +267 -13
  24. package/lib/policy.js +30 -10
  25. package/lib/tool-contract.js +56 -0
  26. package/lib/tools.js +174 -51
  27. package/lib/verification-region.js +17 -0
  28. package/mcp/package.json +2 -1
  29. package/mcp/src/execution.js +8 -5
  30. package/mcp/src/index.js +16 -267
  31. package/mcp/src/tools.js +271 -0
  32. package/package.json +5 -2
  33. package/scripts/check-behavior.mjs +83 -0
  34. package/scripts/measure-tool-tokens.mjs +21 -0
  35. package/scripts/render-promo.py +81 -0
  36. package/skills/computer-use/SKILL.md +35 -8
  37. package/skills/computer-use/references/lifecycle.md +11 -7
  38. package/skills/computer-use/references/maintenance.md +9 -5
  39. package/skills/computer-use/references/precision.md +39 -14
  40. package/skills/computer-use/references/progressive-control.md +10 -4
  41. package/skills/computer-use/references/zero-uia-apps.md +54 -0
package/README.md CHANGED
@@ -1,29 +1,44 @@
1
- # dsh-codex-style-computer-use · Codex-style desktop control
1
+ # Codex-style Computer Use
2
2
 
3
- **Give DeepSeek eyes and hands for Windows.**
3
+ **Bring a Codex-style desktop workflow to DeepSeek Harness.**
4
4
 
5
- Ask your agent to work with desktop apps: see the screen, find controls, click and type. Take over with your own mouse or keyboard whenever needed; the agent waits before continuing. Approval prompts remain enabled by default.
5
+ Describe the goal. Your agent observes the screen, locates controls, clicks and types, then checks the result. Work with native Windows apps from the conversation.
6
6
 
7
- Windows desktop computer use for **DeepSeek Harness**: 18 desktop tools plus an Agent activation entry that observe and drive
8
- native Windows applications through prebuilt C# workers included in the package. No PowerShell 7,
9
- SDK or install-time builds are needed.
7
+ English | [中文](README.zh.md) · [Install](#install-and-update-in-dsh-market) · [Changelog](CHANGELOG.md)
10
8
 
11
- English | [中文](README.zh.md)
9
+ ![Codex-style Windows desktop control: observe, act, verify](docs/images/01-codex-style.png)
12
10
 
13
- Windows only (`os: win32`). Requires Node.js ≥ 22 and the .NET Framework 4.x that Windows ships with.
11
+ - **See the target. Take the action.** Combine screenshots, UI Automation and numbered targets with mouse, keyboard, drag, scroll and window tools.
12
+ - **Take over. Let the agent wait. Hand back and continue.** Version 2.0.0 shows a 1-second notice before input, or 0.1 second when the previous notice was less than 3 minutes ago. After handback, the agent re-observes and resumes the original task.
13
+ - **Fewer round trips. More efficient token use.** Incremental control observations, cropped or downsampled screenshots and batches of known actions reduce repeated context and model calls.
14
14
 
15
- **1.3.0 desktop lifecycle update:** the deep-blue cue now matches actual actuation, with a 16-pixel pointer jitter allowance and a visible 1.5-second purple notice before input. Human takeover returns a required continuation checkpoint. Session/agent ownership and a native cross-process lease allow only one controller. Call `computer_ctrl {action:"release"}` after desktop work and verification; the conversation continues and later desktop work can open a fresh cycle in the same turn. Normal completion releases automatically without an exit question card. Explicit pauses, exits and pending questions remain authoritative.
15
+ ![Human handoff: prepare, take over, continue](docs/images/02-handoff.png)
16
16
 
17
- The indicators use continuous 2-pixel rails with square screen corners and an 8-pixel glow. Normal working states have no floating badges; handoff, pause and preparation use compact top hints. Content interiors remain transparent. The visual revision preserves state logic and animation timing.
17
+ ![Token efficiency: focused observations, batched actions and result readback](docs/images/03-token-efficiency.png)
18
18
 
19
- **1.2.3 automatic fix:** normal installation/update now installs the approval-attribution correction for computer-use calls on affected DSH hosts. No manual host patch is required for those calls. Host approvals and audit remain authoritative; follow the host's restart indication after updating an already loaded plugin. See [1.2.3](docs/RELEASE-1.2.3.md).
19
+ These are **1.3.0 feature illustrations**. Actual token use depends on the task, model and observation strategy. Approvals remain enabled by default; explicit pause, cancellation and exit remain authoritative.
20
20
 
21
- **1.2.1 attention fix:** fixes a missing crypto import that prevented 1.2.0's native `probe` and `raise` requests from reaching the helper. Updating from 1.2.0 is required for this runtime fix; reinstalling 1.2.0 does not fix it. New releases are staged on npm's `next` tag before promotion to `latest`; the market's normal update follows `latest`. See [1.2.1 validation and release status](docs/RELEASE-1.2.1.md).
21
+ **Ready to install.** 19 desktop tools, with native helpers included. No SDK or install-time compilation. Requires Windows, Node.js ≥ 22 and the .NET Framework 4.x included with Windows. An independent community plugin; Codex describes the interaction style.
22
22
 
23
- **1.2.2 GitHub pre-release:** includes a separately deployed DSH host approval-attribution patch and 14 regression cases. Plugin input and approval behavior is retained from 1.2.1. See [release contents and deployment scope](docs/RELEASE-1.2.2.md); GitHub publication does not update npm or the market automatically.
23
+ <details>
24
+ <summary>Current release and compatibility</summary>
25
+
26
+ **2.0.0** is the new major release, initially distributed on npm `next` while the 24-hour release gate runs. See [2.0.0 changes and migration](docs/RELEASE-2.0.0.md). Check registry tags for current stable availability.
27
+
28
+ Version 1.3.1 reads text from the screen by name, notices the Windows secure desktop and resumes on its own, and keeps every colour on the same DPI-derived edge rail with a visible breathing floor. Version 1.3.0 releases desktop control when work ends while the chat continues.
29
+
30
+ Earlier fixes: [1.2.3 approval attribution](docs/RELEASE-1.2.3.md) · [1.2.1 native attention](docs/RELEASE-1.2.1.md) · [standalone host patch](docs/RELEASE-1.2.2.md).
31
+
32
+ </details>
24
33
 
25
34
  ## What it does
26
35
 
36
+ Version 2.0.0 also uses a compact blue kite pointer during AI operation and scene hold. Its tilted tip marks the click point, with small text/resize symbols inside the kite. A breathing kite means the page and pointer position should stay in place; free mode restores the application's normal cursor. The user's Windows cursor theme is preserved.
37
+
38
+ Version 2.0.0 bounds UIA traversal and defaults to compact post-action text. Existing image feedback remains available; `feedback: compact|full|none` controls detail, and click `verificationRegion: "x,y,w,h"` names the expected effect area independently of the input point. Compact text normally adds at most 6 KiB to the original action receipt. Complete point evidence can be retained in a bounded, expiring local cache and retrieved on demand. Disk failure falls back to bounded memory; if no retrievable snapshot can be retained, the current evidence is returned inline in full, outside that compact budget, without another observation or input.
39
+
40
+ Filtered UIA queries require `hwnd`. Defaults are depth 6, 120 nodes and a 1200 ms soft budget. A query deeper than 6 or larger than 300 nodes requires a measured `anchor:"x,y"` in that window; an exact `id` does not bypass this rule. A provider call can exceed the soft budget, and partial results cannot establish absence or uniqueness. After a window-management shortcut, inspect `computer_state {windows:true}`, activate the intended window successfully, then observe it before more keyboard input.
41
+
27
42
  - **Observe** — window list, screenshots (region / JPEG / PNG / downscaled), a Set-of-Marks map of
28
43
  clickable elements, and the UI Automation tree with exact `AutomationId`s.
29
44
  - **Act** — click, move, drag, scroll, select a text range, type, press keys, and drive controls
@@ -35,15 +50,16 @@ The indicators use continuous 2-pixel rails with square screen corners and an 8-
35
50
  - **A human brake that reaches the model** — a stop is pushed into the running session, so the agent
36
51
  learns it was interrupted instead of discovering it on the next turn.
37
52
 
38
- The tools read the desktop; they do not read your files. The only file the plugin writes on its own
39
- is the Worker binary in `%LOCALAPPDATA%\dsh-computer-use\worker\`, plus the audit log and screenshots
40
- under `$DSH_HOME/data/computer-use/`.
53
+ The tools observe the desktop rather than crawl user documents. The plugin manages native helpers in
54
+ `%LOCALAPPDATA%\dsh-computer-use\worker\`, and audit logs, screenshots and its own bounded
55
+ `observation-cache` under `$DSH_HOME/data/computer-use/`. `computer_ctrl {action:"clear_cache"}` clears
56
+ this Agent's observation cache; it does not delete screenshots, attachments or user documents.
41
57
 
42
58
  ## Install and update in dsh-market
43
59
 
44
- Once the catalog submission is merged and synced, search **computer-use** and select **dsh-codex-style-computer-use**, by **ltmroberthk915** (npm maintainer: **ltmroberthk**). Click **Install**. Later, use **Update** or **Update all** for versions admitted by the host's release policy. The package includes both native helpers; no PowerShell 7 or build permission is needed.
60
+ Search **computer-use** or **Codex-style** in the market. Look for repository **ltmroberthk915/dsh-computer-use** and npm package **dsh-codex-style-computer-use** (maintainer: **ltmroberthk**), then click **Install**. Use **Update** or **Update all** for stable-channel updates. Both native helpers are included; no PowerShell 7 or build permission is needed.
45
61
 
46
- Before catalog sync, use **Settings → Plugins → Add plugin**. The corrected version is **`dsh-codex-style-computer-use@1.2.1`**; the unversioned name still resolves to 1.2.0 until promotion. Do not paste the GitHub repository URL: that selects the source-download path instead of the small prebuilt npm package. The default non-strict pnpm 11.7.0 configuration can install 1.2.1 immediately; an explicitly strict host policy can require waiting as described below. A newly added bundle can load live on the official Desktop host; check that its tools and skill appear. Restart from the tray when replacing an already loaded version, when the client remains stale, or when DSH reports **restart-required**.
62
+ Alternatively, enter **`dsh-codex-style-computer-use`** in **Settings → Plugins → Add plugin** for the current stable channel, or **`dsh-codex-style-computer-use@1.3.0`** for the version illustrated here. Enter the npm package spec directly. Your host may apply a waiting period to fresh versions; follow its indication. A newly added bundle can load live on supported hosts; check that its tools and skill appear. Restart from the tray when replacing a loaded version, when the client remains stale, or when DSH reports **restart-required**.
47
63
 
48
64
  ### Releases less than 24 hours old
49
65
 
@@ -103,9 +119,9 @@ The configured GLM 5.3 Max, GLM 5.3 Flash and DeepSeek Flash routes passed a con
103
119
 
104
120
  ## On-demand controls in 1.1
105
121
 
106
- - **Per-Agent tools:** idle Agents see `computer_use_activate`, `computer_ctrl` and `computer_ask`. A successful computer-use skill load, receipt or activation exposes the 18 existing tools plus the activation entry to that Agent. Other Agents and children activate independently. Native calls and Node `run_code` are supported. Activation does not start control or release a human brake.
122
+ - **Per-Agent tools:** idle Agents see `computer_use_activate`, `computer_ctrl` and `computer_ask`. A successful computer-use skill load, receipt or activation exposes the 19 desktop tools plus the activation entry to that Agent. Other Agents and children activate independently. Native calls and Node `run_code` are supported. Activation does not start control or release a human brake.
107
123
  - **Observed UIA targets:** a scoped name/id query returns an opaque target handle. Use it for pattern actions; opt-in `rebind:true` allows unique identity or semantic recovery after replacement in the same window. Changed context, ambiguity and incomplete scans refuse. Broad queries skip recovery handles unless `targets:true`; creating witnesses for a large list costs extra native work.
108
- - **Incremental observations:** pass the previous observation ID as `since` with the same query. Deltas preserve additions, changes, removals and ordering. Each Agent retains 64 complete snapshots; `snapshot` retrieves an exact historical result. Missing bases, changed scope, incomplete evidence or a larger delta fall back to full output.
124
+ - **Incremental observations:** pass the previous observation ID as `since` with the same query. Deltas preserve additions, changes, removals and ordering. Snapshots are isolated by Agent, with at most 64 indexed observations and 4 MiB of retained JSON per store. `snapshot` retrieves a retained historical result; `offset` and `limit` page its arrays. Missing bases, changed scope, incomplete evidence or a larger delta fall back to full output.
109
125
 
110
126
  See [the detailed protocol](skills/computer-use/references/progressive-control.md) for cache limits and recovery boundaries. UIA snapshots preserve provider-returned fields within existing provider limits; historical retrieval is not fresh outcome verification.
111
127
 
@@ -114,7 +130,7 @@ See [the detailed protocol](skills/computer-use/references/progressive-control.m
114
130
  | Group | Tools |
115
131
  |---|---|
116
132
  | Activate | `computer_use_activate` |
117
- | Observe | `computer_state` `computer_shot` `computer_marks` `computer_uia` |
133
+ | Observe | `computer_state` `computer_shot` `computer_ocr` `computer_marks` `computer_uia` |
118
134
  | Act | `computer_click` `computer_move` `computer_drag` `computer_scroll` `computer_select` `computer_key` `computer_type` `computer_uia_act` `computer_window` `computer_clip` |
119
135
  | Flow | `computer_wait` `computer_batch` |
120
136
  | Meta | `computer_ask` `computer_ctrl` |
@@ -157,7 +173,9 @@ fields are detected through UI Automation's `IsPassword` and refused by default.
157
173
  | Key | Default | Meaning |
158
174
  |---|---:|---|
159
175
  | `automationMode` | `standard` | approval mode (above) |
160
- | `progressiveTools` | `true` | per-Agent activation; `false` keeps the original 18 global tools |
176
+ | `progressiveTools` | `true` | per-Agent activation; `false` exposes the 19 desktop tools globally |
177
+ | `actionFeedback` | `true` | automatically observe after direct actions and batches |
178
+ | `actionFeedbackDetail` | `compact` | `compact`, `full` or `none`; a call's `feedback` overrides this default; uncached evidence falls back to full inline output |
161
179
  | `dryRun` | `false` | simulate actuations, log only |
162
180
  | `maxActionsPerMinute` | `60` | core-level actuation rate limit |
163
181
  | `annotateMarks` | `true` | draw Set-of-Marks boxes on screenshots |
@@ -173,6 +191,19 @@ than Cordis:
173
191
  node mcp/src/index.js
174
192
  ```
175
193
 
194
+ The embedded server reports the version of this root package. It starts with observation only.
195
+ The launching host may set `DSH_CU_ALLOW_INPUT=1` when it owns authorization for desktop actions;
196
+ MCP tool calls cannot enable input. The compatibility tool `cu_approve` only reports this rule and
197
+ never grants consent. DSH's `automationMode` policy belongs to the Cordis adapter and is not an
198
+ MCP approval service. `DSH_CU_DRY_RUN=1` simulates input that the host has already enabled.
199
+
200
+ Both adapters use the same key-command grammar, batch failure and continuation rules, and image
201
+ coordinate mapping. MCP returns structured capture metadata and one latest PNG/JPEG image, without
202
+ serializing image bytes into JSON text. `cu_hold` / `cu_free` reserve or release the scene between
203
+ actions; `cu_release` ends the desktop cycle. `cu_drag` accepts `expectCursor` and `hwnd` checks.
204
+ MCP exposes a smaller tool surface; DSH-specific human-question and host-lifecycle integration are
205
+ provided by the Cordis adapter.
206
+
176
207
  ## Development
177
208
 
178
209
  Publisher release checks use npm's official registry by default. If that origin is unreachable on your network, `CU_REGISTRY` selects an explicit mirror for the read-only `check` command; the JSON result includes `registry` and `authoritative`. Example in PowerShell (this changes only the current shell):
package/README.zh.md CHANGED
@@ -1,30 +1,44 @@
1
- # dsh-codex-style-computer-use · Codex 风格桌面操控
1
+ # Codex 风格 Computer Use
2
2
 
3
- **让 DeepSeek 看得见,也点得动。**
3
+ **更接近 Codex 的电脑操控体验,带到 DeepSeek Harness。**
4
4
 
5
- 把 Windows 应用里的重复操作交给 AI:看屏幕、找控件、点鼠标、敲键盘。AI 实际操控前提示 1.5 秒,非操控期间你可以正常使用电脑;操控中接管后,AI 等待交回并接续原任务。默认保留操作审批。
5
+ 你描述目标,AI 看屏幕、找控件、点鼠标、敲键盘,再检查结果。让原生 Windows 应用里的工作,也能在对话中完成。
6
6
 
7
- 给 **DeepSeek Harness** 用的 Windows 桌面操控插件:18 个桌面工具加一个 Agent 激活入口,通过一个 C# worker
8
- 观察并驱动原生 Windows 应用。发布包已包含编译好的原生组件,无需安装 PowerShell 7、SDK 或执行编译命令。
7
+ [English](README.md) | 中文 · [安装](#在-dsh-market-安装和更新) · [版本记录](CHANGELOG.md)
9
8
 
10
- [English](README.md) | 中文
9
+ ![Codex 风格 Windows 桌面操控:观察、操作、验证](docs/images/01-codex-style.png)
11
10
 
12
- 仅支持 Windows(`os: win32`)。需要 Node.js ≥ 22 与 Windows 自带的 .NET Framework 4.x。
11
+ - **看得准,也点得动。** 截图、控件树与编号定位配合使用,支持点击、输入、拖拽、滚动和窗口操作。
12
+ - **你接手,AI 等待,再接着做。** 2.0.0首次操作预告倒计时 1 秒;距上次提示不足 3 分钟时为 0.1 秒。交回后重新观察并接续原任务。
13
+ - **更少往返,更省 Token。** 控件增量观察、区域与降采样截图,加上连续动作批量执行,减少重复观察和模型往返。
13
14
 
14
- **1.3.0 桌面控制周期升级:**实际操控与深蓝色指示同步;鼠标轻微抖动有 16 像素容差;紫色操作前提示持续 1.5 秒;接管后返回必须继续任务的断点;同一时间只有一个 agent/worker 拥有控制周期。桌面工作和验证结束后调用 `computer_ctrl {action:"release"}` 即可释放,聊天继续,同一轮后来仍可重新开始。正常收尾自动释放,不再弹出结束确认选项单。人工暂停、明确退出和待回答的问题保持有效。
15
+ ![人工接管与续接:先提醒、可接管、再继续](docs/images/02-handoff.png)
15
16
 
16
- 灯效统一为贴合屏幕直角的 2 像素光线和最多 8 像素柔光,四角没有圆弧缺口。普通工作状态不显示悬浮文字牌;让出、暂停和预告使用紧凑提示,正文区域保持透明。此次美工调整不改变状态逻辑或闪烁节奏。
17
+ ![Token 效率:聚焦观察、合并操作、回读验证](docs/images/03-token-efficiency.png)
17
18
 
18
- **1.2.3 自动修复:**正常安装或更新插件,即会为受影响宿主中的 computer-use 调用安装审批归因修正,无需手工修改宿主。审批决定与审计仍由宿主处理;更新已加载插件后按宿主提示重启。详见 [1.2.3](docs/RELEASE-1.2.3.md)。
19
+ 上图为 **1.3.0 功能示意**。实际 Token 用量取决于任务、模型和观察方式。默认保留操作审批;明确暂停、取消和退出始终有效。
19
20
 
20
- **1.2.1 提醒修复:**补齐 crypto 导入,修复 1.2.0 原生 `probe` / `raise` 请求还未发送就抛错的问题。这次是运行时代码修复,需要从 1.2.0 更新;重复安装 1.2.0 无效。新版本先发布到 npm 的 `next`,再提升为 `latest`,市场普通更新跟随 `latest`。详见 [1.2.1 验证和发布状态](docs/RELEASE-1.2.1.md)。
21
+ **安装即用。** 19 个桌面工具,原生组件随包提供,无需 SDK 或安装时编译。支持 Windows、Node.js ≥ 22 和系统自带的 .NET Framework 4.x。独立社区插件;Codex 用于说明交互风格。
21
22
 
22
- 宿主在禁用审批弹窗时仍提示“用户拒绝工具”的问题,详见 [DSH 审批归因补丁与回归测试](docs/APPROVAL-ATTRIBUTION.md)。
23
+ <details>
24
+ <summary>当前版本与兼容性</summary>
23
25
 
24
- **1.2.2 GitHub 预发布:**收录独立部署的宿主审批归因补丁和 14 项回归测试。插件输入与审批行为延续 1.2.1,详见 [发布内容及部署范围](docs/RELEASE-1.2.2.md)。GitHub 发布不会自动更新 npm 或市场版本。
26
+ **2.0.0** 为本次大版本,先发布至 npm `next`,通过仓库规定的 24 小时成熟期后再升稳定频道。详见 [2.0.0 变更与迁移](docs/RELEASE-2.0.0.md),稳定版以 registry 标签为准。
27
+
28
+ 1.3.1 可直接从屏幕读字定位控件,察觉 Windows 安全桌面并在其结束后自动续接;所有颜色共用同一条按 DPI 换算的边缘光环,呼吸谷值保持可见。1.3.0 起工具用完可主动释放,聊天继续;正常收尾自动退出。
29
+
30
+ 历史兼容修复:[1.2.3 审批归因](docs/RELEASE-1.2.3.md) · [1.2.1 原生提醒](docs/RELEASE-1.2.1.md) · [独立宿主补丁](docs/RELEASE-1.2.2.md)。
31
+
32
+ </details>
25
33
 
26
34
  ## 它能做什么
27
35
 
36
+ 2.0.0在 AI 操作和保留现场时使用小巧的蓝色风筝光标:倾斜的尖端就是点击位置,文本、缩放等提示收在风筝中央。呼吸状态表示仍需保持页面与鼠标位置;放开现场后恢复应用原来的鼠标。Windows 鼠标主题保持原样。
37
+
38
+ 2.0.0进一步收紧 UIA 查询范围,并默认精简动作后的文字反馈;视觉模型仍保留原有图片检查。调用可用 `feedback: compact|full|none`,点击可用 `verificationRegion: "x,y,w,h"` 指定真正的结果区域。正常精简反馈在原动作回执之外最多增加 6 KiB 文字。完整点查证据可留在有容量与期限限制的本地缓存,按需读快照。磁盘失败先回退有界内存;如果无法保留可回读的快照,则直接完整返回已有证据,此时不受精简文字预算限制,不重复查询或操作。
39
+
40
+ 带过滤条件的 UIA 查询必须指定 `hwnd`,默认深度 6、最多 120 节点和 1200 毫秒软预算。深度大于 6 或节点上限大于 300 时,须提供该窗口内已测量的 `anchor:"x,y"`;精确 `id` 也不豁免。单次提供器调用可能超过软预算,部分结果不能证明目标不存在或唯一。窗口管理快捷键后,先用 `computer_state {windows:true}` 查看窗口,再成功激活目标并重新观察,随后才能继续键盘输入。
41
+
28
42
  - **观察** —— 窗口列表、截图(区域 / JPEG / PNG / 降采样)、可点元素的 Set-of-Marks 编号图,
29
43
  以及带精确 `AutomationId` 的 UI Automation 树。
30
44
  - **操作** —— 点击、移动、拖拽、滚动、选中文本区间、键入、按键,并可走控件**自身的 UI
@@ -34,14 +48,13 @@
34
48
  - **能传到模型的人为刹车** —— 停止事件被推入正在运行的会话,agent 当场知道自己被打断,
35
49
  而不是等到下一轮才发现。
36
50
 
37
- 工具读的是桌面,不读你的文件。插件自己只写三处:worker 二进制
38
- (`%LOCALAPPDATA%\dsh-computer-use\worker\`)、审计日志与截图(`$DSH_HOME/data/computer-use/`)。
51
+ 工具观察桌面,不遍历用户文档。插件管理原生组件(`%LOCALAPPDATA%\dsh-computer-use\worker\`),以及 `$DSH_HOME/data/computer-use/` 下的审计日志、截图和自有 `observation-cache` 有界缓存。`computer_ctrl {action:"clear_cache"}` 只清理本 Agent 的观察缓存,不删除截图、附件或用户文档。
39
52
 
40
53
  ## 在 dsh-market 安装和更新
41
54
 
42
- 市场收录同步后,搜索 **computer-use**,选择 **dsh-codex-style-computer-use**,作者 **ltmroberthk915**(npm 维护者 **ltmroberthk**),点击**安装**。以后通过市场的**更新**或**全部更新**安装符合宿主发布策略的版本。两份原生组件都已随包提供,无需 PowerShell 7、SDK 或构建白名单。
55
+ 在市场搜索 **computer-use** 或 **Codex-style**,认准仓库 **ltmroberthk915/dsh-computer-use**、npm 包 **dsh-codex-style-computer-use**(维护者 **ltmroberthk**),点击**安装**。以后通过市场的**更新**或**全部更新**获取正式频道版本。两份原生组件都已随包提供,无需 PowerShell 7、SDK 或构建白名单。
43
56
 
44
- 目录尚未同步时,在 **设置 → 插件 → 添加插件** 输入修复版 **`dsh-codex-style-computer-use@1.2.1`**。正式标签提升前,省略版本号仍会装到 1.2.0。这里填 npm 包名即可;填写 GitHub 仓库网址会转为下载源码,容易受 GitHub 下载线路影响。默认非严格 pnpm 11.7.0 配置可以直接安装 1.2.1;宿主显式启用严格策略时,才可能需要等待冷却。首次新增 bundle 在官方桌面端可热加载;确认工具与 skill 已出现即可使用。替换已经加载的版本、客户端界面未刷新或界面明确提示 **restart-required / 需要重启** 时,再从托盘完整退出 DSH 并重新打开。
57
+ 也可在 **设置 → 插件 → 添加插件** 输入 **`dsh-codex-style-computer-use`**,安装当前正式频道;要体验本页展示的 1.3.0,输入 **`dsh-codex-style-computer-use@1.3.0`**。这里填 npm 包名即可。宿主可能对新版本设置等待期,以界面提示为准。首次新增 bundle 可在支持的宿主中热加载;确认工具与 skill 已出现即可使用。替换已经加载的版本、客户端界面未刷新或界面提示 **restart-required / 需要重启** 时,从托盘完整退出 DSH 并重新打开。
45
58
 
46
59
  ### 发布未满 24 小时
47
60
 
@@ -100,9 +113,9 @@ $CuDshInstall = Join-Path $env:LOCALAPPDATA 'Programs\DeepSeek Harness'
100
113
 
101
114
  ## 1.1 的按需控制
102
115
 
103
- - **按 Agent 加载:**未激活时暴露 `computer_use_activate`、`computer_ctrl`、`computer_ask`。成功读取 computer-use skill、确认规程或调用激活入口后,该 Agent 获得原有 18 个工具,加入口共 19 个。其他 Agent 和子 Agent 独立启用,支持原生调用和 Node `run_code`。激活本身不启动控制,也不释放人为刹车。
116
+ - **按 Agent 加载:**未激活时暴露 `computer_use_activate`、`computer_ctrl`、`computer_ask`。成功读取 computer-use skill、确认规程或调用激活入口后,该 Agent 获得 19 个桌面工具,加入口共 20 个。其他 Agent 和子 Agent 独立启用,支持原生调用和 Node `run_code`。激活本身不启动控制,也不释放人为刹车。
104
117
  - **已观察的 UIA 目标:**指定窗口和 name/id 的聚焦查询默认生成 target 句柄。控件重建时,显式 `rebind:true` 才允许在同一窗口内进行唯一身份或语义匹配。上下文变化、歧义、扫描不完整时拒绝动作。宽查询需 `targets:true` 才生成恢复句柄,避免普通观察承担额外开销。
105
- - **增量观察:**重复相同查询并传入上一 observation 的 `since`,返回新增、变化、删除及顺序信息。每 Agent 保留最近 64 份完整快照,`snapshot` 可取回确切的历史结果。缺少基线、范围变化、证据不完整或增量更大时自动回退全量。
118
+ - **增量观察:**重复相同查询并传入上一 observation 的 `since`,返回新增、变化、删除及顺序信息。快照按 Agent 隔离,每个 store 总计最多索引 64 份观察、在内存保留 4 MiB JSON。`snapshot` 可取回仍保留的历史结果,`offset` 和 `limit` 可分页读取数组。缺少基线、范围变化、证据不完整或增量更大时自动回退全量。
106
119
 
107
120
  保留的是提供器实际返回的完整字段,沿用原有提供器限制;历史快照不代替动作后的新验证。缓存上限和恢复边界见[详细协议](skills/computer-use/references/progressive-control.md)。
108
121
 
@@ -111,7 +124,7 @@ $CuDshInstall = Join-Path $env:LOCALAPPDATA 'Programs\DeepSeek Harness'
111
124
  | 分组 | 工具 |
112
125
  |---|---|
113
126
  | 激活 | `computer_use_activate` |
114
- | 观察 | `computer_state` `computer_shot` `computer_marks` `computer_uia` |
127
+ | 观察 | `computer_state` `computer_shot` `computer_ocr` `computer_marks` `computer_uia` |
115
128
  | 操作 | `computer_click` `computer_move` `computer_drag` `computer_scroll` `computer_select` `computer_key` `computer_type` `computer_uia_act` `computer_window` `computer_clip` |
116
129
  | 流程 | `computer_wait` `computer_batch` |
117
130
  | 元 | `computer_ask` `computer_ctrl` |
@@ -149,7 +162,9 @@ ID**:派发输入前会核对窗口身份与采样像素,因此过期的 mar
149
162
  | 键 | 默认 | 含义 |
150
163
  |---|---:|---|
151
164
  | `automationMode` | `standard` | 审批模式(见上) |
152
- | `progressiveTools` | `true` | 按 Agent 激活;设为 `false` 可保留原有 18 个全局工具 |
165
+ | `progressiveTools` | `true` | 按 Agent 激活;设为 `false` 全局暴露 19 个桌面工具 |
166
+ | `actionFeedback` | `true` | 直接动作及批处理后自动观察 |
167
+ | `actionFeedbackDetail` | `compact` | `compact`、`full` 或 `none`;单次调用的 `feedback` 可覆盖默认值;无法缓存时完整内联返回证据 |
153
168
  | `dryRun` | `false` | 只模拟操作并记录 |
154
169
  | `maxActionsPerMinute` | `60` | 核心层操作限速 |
155
170
  | `annotateMarks` | `true` | 在截图上绘制 Set-of-Marks 方框 |
@@ -164,6 +179,16 @@ ID**:派发输入前会核对窗口身份与采样像素,因此过期的 mar
164
179
  node mcp/src/index.js
165
180
  ```
166
181
 
182
+ 该入口随根包发布,运行时报告根包版本;默认只能观察桌面。启动它的宿主在负责桌面操作授权时,
183
+ 可以设置 `DSH_CU_ALLOW_INPUT=1`。模型调用工具不能开启输入:兼容工具 `cu_approve` 只说明授权来源,
184
+ 不会授予同意。DSH 的 `automationMode` 属于 Cordis 入口,不是 MCP 的审批服务。
185
+ `DSH_CU_DRY_RUN=1` 只模拟已由宿主启用的输入。
186
+
187
+ 两个入口共享按键规范化、批次失败与接管后续行语义,以及图像坐标换算。MCP 保留截图时间、光标、
188
+ 稳定性和区域元数据,只附最后一张 PNG/JPEG,不把图像字节重复塞进 JSON。`cu_hold` / `cu_free`
189
+ 用于操作间保护或放开现场,`cu_release` 结束桌面周期;`cu_drag` 支持 `expectCursor` 与 `hwnd` 校验。
190
+ MCP 工具范围较小,DSH 的人类提问和宿主生命周期集成仍由 Cordis 入口提供。
191
+
167
192
  ## 开发
168
193
 
169
194
  发布者检查默认查询 npm 官方源。若本地无法访问,可用 `CU_REGISTRY` 为只读的 `check` 命令指定镜像;JSON 输出会注明 `registry` 和 `authoritative`。PowerShell 示例(只影响当前终端):
package/icon.svg ADDED
@@ -0,0 +1,29 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
2
+ <defs>
3
+ <linearGradient id="tile" x1="4" y1="0" x2="28" y2="32" gradientUnits="userSpaceOnUse">
4
+ <stop stop-color="#17243F"/>
5
+ <stop offset="1" stop-color="#05080F"/>
6
+ </linearGradient>
7
+ <radialGradient id="halo" cx="11" cy="27" r="20" gradientUnits="userSpaceOnUse">
8
+ <stop stop-color="#8B5CF6" stop-opacity=".2"/>
9
+ <stop offset="1" stop-color="#8B5CF6" stop-opacity="0"/>
10
+ </radialGradient>
11
+ <linearGradient id="rail" x1="5" y1="6" x2="27" y2="24" gradientUnits="userSpaceOnUse">
12
+ <stop stop-color="#5EEAD4"/>
13
+ <stop offset=".5" stop-color="#38BDF8"/>
14
+ <stop offset="1" stop-color="#A78BFA"/>
15
+ </linearGradient>
16
+ <radialGradient id="glow" cx="18.2" cy="19.4" r="7.6" gradientUnits="userSpaceOnUse">
17
+ <stop stop-color="#38BDF8" stop-opacity=".32"/>
18
+ <stop offset="1" stop-color="#38BDF8" stop-opacity="0"/>
19
+ </radialGradient>
20
+ </defs>
21
+ <rect width="32" height="32" rx="7.5" fill="url(#tile)"/>
22
+ <rect width="32" height="32" rx="7.5" fill="url(#halo)"/>
23
+ <rect x="5.6" y="5.9" width="20.8" height="16" rx="3" fill="#FFFFFF" fill-opacity=".06" stroke="url(#rail)" stroke-width="1.9"/>
24
+ <path d="M5.6 10.1 H26.4" stroke="url(#rail)" stroke-width="1.05" stroke-opacity=".5"/>
25
+ <circle cx="8.5" cy="8" r=".9" fill="#5EEAD4"/>
26
+ <circle cx="11.2" cy="8" r=".9" fill="#A78BFA" fill-opacity=".85"/>
27
+ <circle cx="18.2" cy="19.4" r="7.6" fill="url(#glow)"/>
28
+ <path d="M15.6 12.4 L15.6 21.2 L17.8 19 L19.3 22.4 L20.8 21.7 L19.3 18.5 L22.3 18.5 Z" fill="#F8FAFC" stroke="#070B14" stroke-width=".85" stroke-linejoin="round"/>
29
+ </svg>
package/lib/audit.js CHANGED
@@ -28,3 +28,10 @@ export function appendAudit (dataDir, rec) {
28
28
  appendFileSync(join(dataDir.root, 'audit.jsonl'), JSON.stringify(rec) + '\n')
29
29
  } catch { /* audit must never break the action */ }
30
30
  }
31
+
32
+ export function installAudit (cu, dataDir) {
33
+ const events = ['action', 'action:settled', 'handoff-wait']
34
+ const write = record => appendAudit(dataDir, record)
35
+ for (const event of events) cu.on(event, write)
36
+ return () => { for (const event of events) cu.off(event, write) }
37
+ }
package/lib/batch.js CHANGED
@@ -1,32 +1,21 @@
1
1
  // Serial execution and observation selection; no desktop or lifecycle side effects here.
2
2
  import { handoffResult } from './continuation.js'
3
- const reads = new Set(['computer_state', 'computer_uia', 'computer_marks', 'computer_shot', 'computer_wait'])
4
- const isRead = (step) => reads.has(step?.tool) || (step?.tool === 'computer_clip' && step.args?.text === undefined)
5
- const isReadback = (step) => ['computer_state', 'computer_uia', 'computer_marks', 'computer_shot'].includes(step.tool) ||
6
- (step.tool === 'computer_clip' && step.args?.text === undefined)
7
-
8
- export function actionRefusal (step, result) {
9
- if (result?.ok === false || result?.success === false) return result.error || 'action reported failure'
10
- if (step.tool === 'computer_window' && (step.args?.op || 'activate') === 'activate' && result?.activated !== true) {
11
- return 'target window activation was not confirmed; subsequent actions were not dispatched'
12
- }
13
- if (step.tool === 'computer_ctrl' && step.args?.action === 'recover' && result?.recovered !== true) return result.reason || 'temporary pause recovery refused'
14
- if (step.tool === 'computer_ctrl' && step.args?.action === 'release' && !result?.released && !result?.alreadyReleased) return result.reason || 'cycle release refused'
15
- return null
16
- }
3
+ import { actionRefusal, actionError, isReadStep as isRead, isReadbackStep as isReadback, endsBatch } from './tool-contract.js'
4
+ export { actionRefusal } from './tool-contract.js'
17
5
 
18
6
  export async function runBatch ({ steps, handlers, signal, stopped, screenshot, shot = 'auto', exec, assertOwnership }) {
19
7
  if (!Array.isArray(steps) || !['auto', 'always', 'never'].includes(shot)) throw new Error('invalid batch actions or shot mode')
20
8
  const actions = []
21
9
  let failed = false
22
10
  let continuation = null
11
+ let endedBy = null
23
12
  for (const step of steps) {
24
13
  const name = step?.tool
25
14
  if (failed || signal?.aborted) {
26
15
  actions.push({ tool: name || '', ok: false, outcome: 'not-dispatched',
27
16
  ...(continuation && !signal?.aborted
28
17
  ? { status: 'deferred', message: 'Resume this unfinished step after fresh target evidence; the original task must continue.' }
29
- : { error: signal?.aborted ? 'cancelled' : 'earlier action failed' }) })
18
+ : { error: signal?.aborted ? 'cancelled' : endedBy ? 'batch ended by control action' : 'earlier action failed' }) })
30
19
  failed = true
31
20
  continue
32
21
  }
@@ -38,22 +27,28 @@ export async function runBatch ({ steps, handlers, signal, stopped, screenshot,
38
27
  const result = await fn(step.args || {}, exec)
39
28
  const refusal = actionRefusal(step, result)
40
29
  actions.push({ tool: name, ok: !refusal, result, ...(refusal ? { error: refusal } : {}) })
41
- failed = !!refusal || (!stoppedBefore && stopped()) ||
42
- (name === 'computer_ctrl' && ['stop', 'exit', 'release'].includes(step.args?.action))
30
+ if (result?.taskStatus === 'continuing' && result.continuation?.required && !signal?.aborted && !stopped()) {
31
+ const nestedPath = result.continuation.resumeActionPath || [result.continuation.resumeFromActionIndex]
32
+ continuation = { ...result.continuation, resumeFromActionIndex: actions.length - 1,
33
+ resumeActionPath: [actions.length - 1, ...nestedPath.filter(Number.isInteger)] }
34
+ }
35
+ if (endsBatch(step)) endedBy = { tool: name, action: step.args?.action || name.replace(/^cu_/, '') }
36
+ else if (result?.endedBy) endedBy = result.endedBy
37
+ failed = !!refusal || !!continuation || (!stoppedBefore && stopped()) || !!endedBy
43
38
  } catch (error) {
44
39
  const checkpoint = !signal?.aborted && !stopped() ? handoffResult(error) : null
45
40
  if (checkpoint) {
46
41
  continuation = { ...checkpoint.continuation, resumeFromActionIndex: actions.length }
47
42
  actions.push({ tool: name, ...checkpoint, continuation })
48
43
  } else {
49
- actions.push({ tool: name, ok: false, error: String(error.message || error),
50
- code: error.code || 'action-error', outcome: error.outcome || 'unknown' })
44
+ actions.push({ tool: name, ...actionError(error) })
51
45
  }
52
46
  failed = true
53
47
  }
54
48
  }
55
49
  const needsShot = shot === 'always' || (shot === 'auto' && steps.some(s => !isRead(s)) && !isReadback(steps.at(-1) || {}))
56
50
  const result = { actions, attachedShot: null }
51
+ if (endedBy) result.endedBy = endedBy
57
52
  if (continuation) Object.assign(result, {
58
53
  status: 'continuation-required', taskStatus: 'continuing', transient: true, continuation,
59
54
  })
Binary file
@@ -3,5 +3,5 @@
3
3
  "platform": "win32",
4
4
  "architecture": "x64",
5
5
  "sourceSha256": "6e17308fc1cb0de1ece6f3164866b6de03ecdf74a42a6e78c0a8e6d02912c3bf",
6
- "binarySha256": "c15feb47d7ec975ccab12fea2ee4300ea0a4699069be21d0fb9d1fedebe26158"
6
+ "binarySha256": "897c8dfd1ef2c0722105311d4775241999fab42d77a0ed1ecfc95ae290bf456f"
7
7
  }
@@ -0,0 +1,175 @@
1
+ // Small post-action evidence, shared by host/MCP adapters. Never dispatches input,
2
+ // retries a read, inventories a UIA tree, or changes the original action receipt.
3
+ export const COMPACT_FEEDBACK_BYTES = 6144
4
+ export const COMPACT_OBSERVATION_MS = 6000
5
+ export const COMPACT_POINT_MS = 5000
6
+
7
+ const finite = value => typeof value === 'number' && Number.isFinite(value)
8
+ const bytes = value => Buffer.byteLength(JSON.stringify(value), 'utf8')
9
+ const rectKeys = ['x', 'y', 'width', 'height']
10
+ const windowKeys = ['hwnd', 'pid', 'process', 'title', 'class', 'active', 'minimized']
11
+ const elementKeys = ['name', 'role', 'controlType', 'automationId', 'className', 'value', 'text', 'enabled', 'isEnabled', 'offscreen', 'isOffscreen', 'editable', 'focused', 'hasKeyboardFocus', 'selected', 'checked', 'toggleState', 'expanded', 'expandCollapseState', 'hwnd', 'pid']
12
+
13
+ function point (value) {
14
+ if (typeof value === 'string') {
15
+ const parts = value.trim().split(/[,\s]+/)
16
+ if (parts.length !== 2 || parts.some(p => !p)) return null
17
+ value = { x: Number(parts[0]), y: Number(parts[1]) }
18
+ }
19
+ return value && finite(value.x) && finite(value.y) ? { x: Math.round(value.x), y: Math.round(value.y) } : null
20
+ }
21
+ export function feedbackRegion (value) {
22
+ if (typeof value === 'string') {
23
+ const parts = value.trim().split(/[,\s]+/)
24
+ if (parts.length !== 4 || parts.some(p => !p)) return null
25
+ value = Object.fromEntries(rectKeys.map((k, i) => [k, Number(parts[i])]))
26
+ }
27
+ if (!value || !rectKeys.every(k => finite(value[k])) || value.width <= 0 || value.height <= 0) return null
28
+ return Object.fromEntries(rectKeys.map(k => [k, value[k]]))
29
+ }
30
+
31
+ // A native default verification crop is NOT authorization to narrow vision.
32
+ // Only an explicitly requested region changes the automatic image viewport.
33
+ export function feedbackViewport (args = {}, receipt = {}) {
34
+ args ||= {}; receipt ||= {}
35
+ const requested = (receipt.verificationSource === 'requested-region' ? feedbackRegion(receipt.verificationRegion) : null) ||
36
+ feedbackRegion(args.verificationRegion)
37
+ if (requested) return { region: rectKeys.map(k => requested[k]).join(','),
38
+ point: { x: Math.floor(requested.x + requested.width / 2), y: Math.floor(requested.y + requested.height / 2) }, source: 'requested-region' }
39
+ const actual = point(receipt)
40
+ if (actual) return { point: actual, source: 'action-receipt' }
41
+ const target = point(args.to) || point(args.at)
42
+ if (target) return { point: { x: target.x + (finite(args.dx) ? args.dx : 0), y: target.y + (finite(args.dy) ? args.dy : 0) }, source: 'requested-point' }
43
+ return { source: 'no-point' }
44
+ }
45
+
46
+ function clipped (value, limit, cuts, path) {
47
+ const text = String(value), raw = Buffer.from(text, 'utf8')
48
+ if (raw.length <= limit) return text
49
+ let end = limit - 3
50
+ while (end > 0 && (raw[end] & 0xc0) === 0x80) end--
51
+ cuts.push(path)
52
+ return raw.subarray(0, end).toString('utf8') + '…'
53
+ }
54
+ function brief (value, keys, cuts, path, limit = 768) {
55
+ if (!value || typeof value !== 'object') return null
56
+ const result = {}
57
+ for (const key of keys) {
58
+ const item = value[key]
59
+ if (typeof item === 'string') result[key] = clipped(item, limit, cuts, path + '.' + key)
60
+ else if (typeof item === 'boolean' || finite(item) || item === null) result[key] = item
61
+ }
62
+ const rect = feedbackRegion(value.rect || value.bounds)
63
+ if (rect) result.rect = rect
64
+ return result
65
+ }
66
+
67
+ // Budget applies to the ADDED observation envelope, never to an input receipt.
68
+ // Whole fields are omitted if needed; a truncated field is never silently complete.
69
+ export function limitCompactFeedback (envelope, maxBytes = COMPACT_FEEDBACK_BYTES) {
70
+ const limit = Number.isInteger(maxBytes) && maxBytes >= 1024 ? Math.min(maxBytes, COMPACT_FEEDBACK_BYTES) : COMPACT_FEEDBACK_BYTES
71
+ const result = JSON.parse(JSON.stringify(envelope))
72
+ const omitted = []
73
+ const existing = result.observation?.truncatedFields || []
74
+ const budget = result.budget = { maxBytes: limit, returnedBytes: 0, truncated: existing.length > 0, omittedFields: omitted }
75
+ const fit = () => {
76
+ // returnedBytes is part of its own JSON; stabilize decimal width.
77
+ for (let i = 0; i < 4; i++) budget.returnedBytes = bytes(result)
78
+ return budget.returnedBytes <= limit
79
+ }
80
+ if (fit()) return result
81
+ for (const field of ['ancestors', 'focused', 'element', 'receiver', 'window', 'contextFromAction']) {
82
+ if (result.observation && Object.hasOwn(result.observation, field)) {
83
+ delete result.observation[field]; omitted.push('observation.' + field); budget.truncated = true
84
+ if (fit()) return result
85
+ }
86
+ }
87
+ // Unknown adapter metadata is not allowed to evade the byte contract. Retain
88
+ // the truth that this observation could not fit, rather than cut JSON or paths.
89
+ result.observation = { scope: 'compact', incomplete: true, reason: 'observation exceeded compact byte budget; request feedback full for complete readback' }
90
+ omitted.push('observation'); budget.truncated = true
91
+ if (result.feedback) result.feedback = { kind: result.feedback.kind, detail: 'compact', reason: 'bounded post-action observation' }
92
+ if (!fit()) {
93
+ for (const key of Object.keys(result)) if (!['observation', 'feedback', 'verification', 'budget'].includes(key)) delete result[key]
94
+ if (typeof result.verification === 'string') result.verification = clipped(result.verification, 512, omitted, 'verification')
95
+ fit()
96
+ }
97
+ return result
98
+ }
99
+
100
+ export async function collectCompactState (cu, { args = {}, receipt = {}, checkAllowed = () => {}, archive, now = Date.now } = {}) {
101
+ args ||= {}; receipt ||= {}
102
+ const began = now(), deadline = began + COMPACT_OBSERVATION_MS, cuts = [], failures = []
103
+ const observation = { scope: 'local-post-action', ts: began, elementAvailable: false }
104
+ const raw = { scope: 'local-post-action', ts: began }
105
+ let viewport = feedbackViewport(args, receipt), calls = 0
106
+ const read = async (op, params, cap) => {
107
+ checkAllowed()
108
+ const remaining = Math.floor(deadline - now())
109
+ if (remaining <= 0) throw new Error('compact observation time budget exhausted')
110
+ calls++
111
+ return cu.call(op, params, Math.min(cap, remaining))
112
+ }
113
+ if (!viewport.point) {
114
+ try {
115
+ raw.cursor = await read('cursor', {}, 1000)
116
+ const current = point(raw.cursor)
117
+ if (current) viewport = { point: current, source: 'current-cursor' }
118
+ else failures.push({ channel: 'cursor', error: 'cursor position unavailable' })
119
+ } catch (error) { failures.push({ channel: 'cursor', error: clipped(error.message || error, 320, cuts, 'errors.cursor') }) }
120
+ }
121
+ if (viewport.point) {
122
+ observation.point = { ...viewport.point, source: viewport.source }
123
+ raw.point = observation.point
124
+ if (viewport.source === 'action-receipt' || viewport.source === 'current-cursor') observation.cursor = { ...viewport.point, source: viewport.source }
125
+ try {
126
+ const probe = await read('uiaFromPoint', viewport.point, COMPACT_POINT_MS)
127
+ raw.probe = probe
128
+ observation.window = brief(probe.root, windowKeys, cuts, 'window', 320)
129
+ observation.receiver = brief(probe.receives, windowKeys, cuts, 'receiver', 320)
130
+ observation.element = brief(probe.element, elementKeys, cuts, 'element')
131
+ observation.focused = brief(probe.focused, elementKeys, cuts, 'focused', 320)
132
+ if (typeof probe.typingWouldReach === 'boolean') observation.typingWouldReach = probe.typingWouldReach
133
+ const ancestors = probe.ancestors || probe.element?.ancestors || []
134
+ observation.ancestors = Array.isArray(ancestors) ? ancestors.slice(0, 3).map((a, i) => brief(a, elementKeys, cuts, `ancestors.${i}`, 160)) : []
135
+ if (Array.isArray(ancestors) && ancestors.length > 3) cuts.push('ancestors')
136
+ observation.elementAvailable = !!probe.element
137
+ if (!probe.element) failures.push({ channel: 'uiaFromPoint', error: 'no UIA element at this point; absence does not prove task failure' })
138
+ } catch (error) { failures.push({ channel: 'uiaFromPoint', error: clipped(error.message || error, 320, cuts, 'errors.uiaFromPoint') }) }
139
+ }
140
+ if (!viewport.point) observation.reason = 'No action, witness-region or current-cursor point was available; no UIA tree was read.'
141
+ if (receipt.receiver || receipt.focus || receipt.cursor) observation.contextFromAction = {
142
+ source: 'action-receipt, not an independent post-action read',
143
+ receiver: brief(receipt.receiver, windowKeys, cuts, 'context.receiver', 160),
144
+ focus: brief(receipt.focus, windowKeys, cuts, 'context.focus', 160),
145
+ cursor: point(receipt.cursor),
146
+ }
147
+ if (failures.length) observation.errors = failures
148
+ if (archive && (raw.probe || raw.cursor)) {
149
+ try {
150
+ const archived = await archive({ ...raw, ...(failures.length ? { errors: failures } : {}) })
151
+ const snapshot = typeof archived === 'string' ? archived : archived?.snapshot || archived?.observation
152
+ if (typeof snapshot === 'string' && /^O[\w-]{1,127}$/.test(snapshot)) observation.snapshot = snapshot
153
+ const cache = archived?.cache || archived?.archive
154
+ if (cache && (cache.status === 'memory-only' || cache.reason || cache.fallback)) {
155
+ observation.cache = brief(cache, ['status', 'reason', 'fallback'], cuts, 'cache', 160)
156
+ }
157
+ if (!observation.snapshot) {
158
+ // Once neither disk nor memory retains the evidence, truncation would
159
+ // make the omitted fields irretrievable. Return the existing raw value
160
+ // inline without another probe, input replay, clone or disk attempt.
161
+ observation.cache = { status: 'uncached', reason: cache?.reason || 'cache-unavailable', fallback: 'inline' }
162
+ observation.inlineEvidence = raw
163
+ }
164
+ } catch (error) {
165
+ observation.archiveError = clipped(error.message || error, 240, cuts, 'archiveError')
166
+ observation.cache = { status: 'uncached', reason: 'cache-unavailable', fallback: 'inline' }
167
+ observation.inlineEvidence = raw
168
+ }
169
+ }
170
+ if (cuts.length) observation.truncatedFields = [...new Set(cuts)]
171
+ observation.reads = calls
172
+ observation.elapsedMs = Math.max(0, now() - began)
173
+ observation.limitations = 'A local point/focus read may miss remote or visual changes. It does not verify the task effect; use explicit UIA/visual or application readback when needed.'
174
+ return observation
175
+ }
@@ -1,6 +1,6 @@
1
1
  // Temporary ownership handoff is a task checkpoint, not a failed/cancelled task.
2
2
  export function handoffResult (error) {
3
- if (error?.code !== 'HUMAN_REOBSERVE' || !error.continuation?.required) return null
3
+ if (!['HUMAN_REOBSERVE', 'WORKER_REOBSERVE'].includes(error?.code) || !error.continuation?.required) return null
4
4
  return {
5
5
  ok: false,
6
6
  status: 'continuation-required',
@@ -2,6 +2,6 @@
2
2
  "format": 1,
3
3
  "platform": "win32",
4
4
  "architecture": "x64",
5
- "sourceSha256": "885c7935d5520bc6f865da92e3ab63b29c6515a853e2a3e39ceb2221606acf08",
6
- "binarySha256": "c7ae5f0f77912674a8f6f310b95809eef10941cf986f8aa7691aed7a7f08bff5"
5
+ "sourceSha256": "a7496b49b57ca8a505ecf12abad049e9b1b5d4b70f3ae574a5b55cb3fa76bd42",
6
+ "binarySha256": "60bbcd1bd6638b1088e65e7b4a6e68deeecc83cb470967499ed4d9911d98c99a"
7
7
  }