dsh-codex-style-computer-use 1.2.0 → 1.2.1

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 CHANGED
@@ -1,13 +1,15 @@
1
- # Computer Use · Codex-style desktop control
1
+ # dsh-codex-style-computer-use · Codex-style desktop control
2
2
 
3
3
  Windows desktop computer use for **DeepSeek Harness**: 18 desktop tools plus an Agent activation entry that observe and drive
4
4
  native Windows applications through prebuilt C# workers included in the package. No PowerShell 7,
5
- SDK, terminal commands or install-time builds are needed.
5
+ SDK or install-time builds are needed.
6
6
 
7
7
  English | [中文](README.zh.md)
8
8
 
9
9
  Windows only (`os: win32`). Requires Node.js ≥ 22 and the .NET Framework 4.x that Windows ships with.
10
10
 
11
+ **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).
12
+
11
13
  ## What it does
12
14
 
13
15
  - **Observe** — window list, screenshots (region / JPEG / PNG / downscaled), a Set-of-Marks map of
@@ -27,12 +29,35 @@ under `$DSH_HOME/data/computer-use/`.
27
29
 
28
30
  ## Install and update in dsh-market
29
31
 
30
- Search **computer-use** and select **dsh-codex-style-computer-use**, by **ltmroberthk915** (npm maintainer: **ltmroberthk**). Click **Install**. Later, click **Update** or **Update all** in the market. The package includes both native helpers; no PowerShell 7 or build permission is needed.
32
+ 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.
33
+
34
+ Before catalog sync, use **Settings → Plugins → Add plugin** and enter `dsh-codex-style-computer-use`. The default non-strict pnpm 11.7.0 configuration installs this published package 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**.
35
+
36
+ ### Releases less than 24 hours old
37
+
38
+ The default non-strict pnpm 11.7.0 configuration automatically records a single-version exception and installs. A successful install on one machine does not establish that another machine permits fresh versions.
39
+
40
+ With `minimumReleaseAgeStrict: true` (or a host policy that enables strict release-age checks), and no applicable exception, a version below the configured age is rejected before plugin code runs. The bare name, an exact `@1.2.0` pin, and the npm tarball URL were all verified to fail with `ERR_PNPM_NO_MATURE_MATCHING_VERSION` under that policy. Explicitly setting `minimumReleaseAge: 1440` also enables strict behavior in pnpm 11.7.0 unless `minimumReleaseAgeStrict` is separately set to false. Restarting DSH, installing PowerShell, or retrying the same command does not fix a strict-policy refusal. The official desktop bridge does not accept extra pnpm flags.
41
+
42
+ If a strict policy blocks installation, wait until the version meets the configured age to install without changing policy. Version 1.2.0 was published at `2026-10-03T03:50:11.586Z`; it meets a 24-hour cutoff after **2026-10-04 03:50:12 UTC** (11:50:12 in China). A longer custom cutoff or a lagging registry mirror can delay availability further. Default non-strict users do not need to wait or edit configuration for this package.
43
+
44
+ If you explicitly choose to install earlier, merge this single-version exception into the target profile's existing `pnpm-workspace.yaml`, then retry the market install. Preserve other entries; do not disable the global age policy:
45
+
46
+ ```yaml
47
+ minimumReleaseAgeExclude:
48
+ - dsh-codex-style-computer-use@1.2.0
49
+ ```
50
+
51
+ The default desktop path is `%USERPROFILE%\.dsh\profiles\desktop\pnpm-workspace.yaml`; use the actual directory if `DSH_HOME` is customized. Remove this one exception after the version matures if desired.
31
52
 
32
- If the catalog has not synced yet, use **Settings → Plugins → Add plugin**, enter `dsh-codex-style-computer-use`, and install. This also creates an ordinary registry installation that can be updated from the market. If DSH reports **restart required**, fully exit DSH from its tray menu and reopen it.
53
+ The earlier documentation and publisher-check corrections did not change the 1.2.0 runtime and needed no reinstall. The native attention fix in **1.2.1** does require an update. Re-adding the same source can produce a host `ambiguous-install` error on some installation paths; reinstalling 1.2.0 will not apply the 1.2.1 fix.
33
54
 
34
55
  **Migrating an older Git/tarball installation from this repository:** remove the old **dsh-computer-use** entry in the market, then install **dsh-codex-style-computer-use**. The unscoped npm name `dsh-computer-use` belongs to a different repository; do not install it as an upgrade of this plugin. A Git installation cannot switch its dependency identity just by fetching a new commit. After this one-time UI migration, use normal market updates. Existing `computer_*` tool names and the `computer-use` settings namespace are retained.
35
56
 
57
+ **Preserve your saved configuration too:** back up the profile's `cordis.patch.yml`, then change only `name: dsh-computer-use` on an override with `id: computer-use` to `name: dsh-codex-style-computer-use`. Keep its entire `config` and `disabled` fields. Retaining the settings namespace alone is insufficient: a name mismatch makes DSH skip even `read-only`, `dryRun`, and disabled-state overrides. Do not replace other plugins or a shared home patch globally. The repository's `scripts/migrate-profile.mjs` previews by default; `--apply` creates a backup and replaces the file atomically, refusing concurrent edits.
58
+
59
+ Alternatively, download the [configuration migration utility](https://github.com/ltmroberthk915/dsh-computer-use/releases/download/v1.2.0/computer-use-profile-migration.zip), extract it and double-click `migrate-profile.cmd`. It locates the installed official Desktop app and uses Windows PowerShell 5.1 plus DSH's bundled Node to repair the default desktop profile. It stops if the new bundle is absent or the old bundle is still selected. It does not change release-age policy. For another profile, run `migrate-profile.ps1 -ProfileDirectory <absolute-directory>` to preview, then add `-Apply` when ready.
60
+
36
61
  The GitHub Release also includes `dsh-computer-use.tgz` for offline/manual installation; registry installation is the default for market updates.
37
62
 
38
63
  CLI users (web profile):
@@ -41,6 +66,15 @@ CLI users (web profile):
41
66
  dsh plugin --profile web add dsh-codex-style-computer-use
42
67
  ```
43
68
 
69
+ Desktop diagnostics must use the **CLI bundled with the official Desktop app**:
70
+
71
+ ```powershell
72
+ $CuDshInstall = Join-Path $env:LOCALAPPDATA 'Programs\DeepSeek Harness'
73
+ & "$CuDshInstall\resources\runtime\cli\bin\dsh.cmd" plugin --profile desktop why dsh-codex-style-computer-use
74
+ ```
75
+
76
+ For a custom installation, set `$CuDshInstall` to the directory in the Desktop shortcut's target. An older global npm `dsh` can reject the desktop profile. Do not add `resources/runtime/bin` to the global PATH or install a separate pnpm for this task. Prefer the plugin UI for installation.
77
+
44
78
  ## The bundled skill
45
79
 
46
80
  The driving tools stay locked until the session produces a receipt phrase that exists only inside
@@ -127,6 +161,16 @@ node mcp/src/index.js
127
161
 
128
162
  ## Development
129
163
 
164
+ 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):
165
+
166
+ ```powershell
167
+ $env:CU_REGISTRY = 'https://registry.npmmirror.com'
168
+ node scripts/release-channel.mjs check 1.2.0
169
+ Remove-Item Env:CU_REGISTRY
170
+ ```
171
+
172
+ Exit code `0` means the version has passed the 24-hour publisher check, `2` means it is still cooling, and `1` means the check failed. A network failure does not establish that a package is unpublished, and mirrors can lag. `promote` always rechecks and writes `https://registry.npmjs.org`, ignoring `CU_REGISTRY`; it needs access to that origin, using npm's proxy configuration when necessary. This publisher tool is not part of end-user installation. See [the release procedure](docs/RELEASE-1.2.0.md).
173
+
130
174
  The repository ships the guard suite that protects the invariants above — the cycle lifetime, the
131
175
  ask/brake protocol, the operation classification, the skill gate, and the bundled-skill wiring:
132
176
 
package/README.zh.md CHANGED
@@ -1,4 +1,4 @@
1
- # Computer Use · Codex 风格桌面操控
1
+ # dsh-codex-style-computer-use · Codex 风格桌面操控
2
2
 
3
3
  给 **DeepSeek Harness** 用的 Windows 桌面操控插件:18 个桌面工具加一个 Agent 激活入口,通过一个 C# worker
4
4
  观察并驱动原生 Windows 应用。发布包已包含编译好的原生组件,无需安装 PowerShell 7、SDK 或执行编译命令。
@@ -7,6 +7,8 @@
7
7
 
8
8
  仅支持 Windows(`os: win32`)。需要 Node.js ≥ 22 与 Windows 自带的 .NET Framework 4.x。
9
9
 
10
+ **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)。
11
+
10
12
  ## 它能做什么
11
13
 
12
14
  - **观察** —— 窗口列表、截图(区域 / JPEG / PNG / 降采样)、可点元素的 Set-of-Marks 编号图,
@@ -23,12 +25,35 @@
23
25
 
24
26
  ## 在 dsh-market 安装和更新
25
27
 
26
- 搜索 **computer-use**,选择 **dsh-codex-style-computer-use**,作者 **ltmroberthk915**(npm 维护者 **ltmroberthk**),点击**安装**。以后直接点市场里的**更新**或**全部更新**。两份原生组件都已随包提供,无需终端、PowerShell 7、SDK 或手改构建白名单。
28
+ 市场收录同步后,搜索 **computer-use**,选择 **dsh-codex-style-computer-use**,作者 **ltmroberthk915**(npm 维护者 **ltmroberthk**),点击**安装**。以后通过市场的**更新**或**全部更新**安装符合宿主发布策略的版本。两份原生组件都已随包提供,无需 PowerShell 7、SDK 或构建白名单。
29
+
30
+ 目录尚未同步时,可在 **设置 → 插件 → 添加插件** 输入 `dsh-codex-style-computer-use`。默认非严格 pnpm 11.7.0 配置可以直接安装这个已发布的包;宿主显式启用严格策略时,才可能需要按下节等待冷却。首次新增 bundle 在官方桌面端可热加载;确认工具与 skill 已出现即可使用。替换已经加载的版本、客户端界面未刷新或界面明确提示 **restart-required / 需要重启** 时,再从托盘完整退出 DSH 并重新打开。
31
+
32
+ ### 发布未满 24 小时
33
+
34
+ 默认非严格 pnpm 11.7.0 配置会自动记录单版本例外并完成安装;“这台能装”不能推断另一台也允许安装新版本。
35
+
36
+ 只有在 `minimumReleaseAgeStrict: true`(或宿主显式启用严格冷却)、版本未满配置的冷却时间且没有匹配例外时,才会在插件代码运行前被拒绝。已实测在该策略下,裸包名、`dsh-codex-style-computer-use@1.2.0` 和 npm 的 tgz 下载地址都会报 `ERR_PNPM_NO_MATURE_MATCHING_VERSION`。在 pnpm 11.7.0 中,显式设置 `minimumReleaseAge: 1440` 也会启用严格行为,除非另行设置 `minimumReleaseAgeStrict: false`。重启 DSH、反复重装或安装 PowerShell 都不能解决严格策略的拒绝。官方桌面桥接不接受额外 pnpm 参数。
37
+
38
+ **如果被严格策略拦截,无需改配置的做法是等待该版本满冷却时间后,再安装或更新。** 1.2.0 的 npm 发布时刻为 2026-10-03 11:50:11.586(北京时间),24 小时门槛在 **2026-10-04 11:50:12** 后满足。自定义更长冷却或镜像同步延迟仍以宿主提示为准。默认非严格配置的用户安装本包不需要等待,也不需要手动修改配置。
39
+
40
+ 若明确要在冷却期内安装,经确认后可在目标 profile 的 `pnpm-workspace.yaml` 中,将下面的**单个版本**合并进现有列表,再回到市场重试;不要覆盖原有项,也不需要把全局冷却设为 0:
41
+
42
+ ```yaml
43
+ minimumReleaseAgeExclude:
44
+ - dsh-codex-style-computer-use@1.2.0
45
+ ```
46
+
47
+ 默认 desktop 文件位于 `%USERPROFILE%\.dsh\profiles\desktop\pnpm-workspace.yaml`;设置过 `DSH_HOME` 时以实际目录为准。例外仅限这个版本,过了冷却可删除该项。
27
48
 
28
- 目录尚未同步时,可在 **设置 → 插件 → 添加插件** 输入 `dsh-codex-style-computer-use` 安装;这同样是可由市场更新的 npm 安装。如果界面提示**需要重启**,从托盘彻底退出 DSH 后重新打开。
49
+ 此前文档和发布检查修正没有改变 1.2.0 运行时,因此无需重装;**1.2.1 的原生提醒修复则需要更新版本**。某些安装路径重复添加同一来源会触发宿主的 `ambiguous-install`;重复安装 1.2.0 不会得到 1.2.1 的修复。
29
50
 
30
51
  **已有本仓库旧 Git/tgz 版本:**在市场卸载旧的 **dsh-computer-use**,再安装 **dsh-codex-style-computer-use**。npm 上的无 scope 包 `dsh-computer-use` 属于另一仓库,不能用作本插件的更新。旧 Git 依赖不会因仓库改名自动迁移;完成这一次界面内迁移后,后续使用普通市场更新即可。原有 `computer_*` 工具名与 `computer-use` 设置命名空间保留。
31
52
 
53
+ **旧配置也要迁移:**先备份目标 profile 的 `cordis.patch.yml`,仅把 `id: computer-use` 覆盖项里的 `name: dsh-computer-use` 改为 `name: dsh-codex-style-computer-use`,保留整段 `config` 和 `disabled`。只保留设置命名空间并不足够:旧 name 不匹配时,DSH 会跳过覆盖项,连 `read-only`、`dryRun`、禁用状态也可能失效。不要全文替换其他插件或共享 home 层配置。可使用仓库的 `scripts/migrate-profile.mjs`:默认预览,`--apply` 才备份并原子写入,且会拒绝并发修改。
54
+
55
+ 也可下载 [配置迁移工具](https://github.com/ltmroberthk915/dsh-computer-use/releases/download/v1.2.0/computer-use-profile-migration.zip),解压后双击 `migrate-profile.cmd`。它自动寻找已安装的官方 DSH,使用 Windows 自带的 PowerShell 5.1 和 DSH 自带的 Node,修复默认 desktop profile;新包未安装或旧 bundle 仍在时会停止。它不改变发布冷却策略。自定义 profile 可通过 `migrate-profile.ps1 -ProfileDirectory <完整目录>` 预览,确认后追加 `-Apply`。
56
+
32
57
  GitHub Release 仍提供 `dsh-computer-use.tgz`,供离线或手动安装。市场自动更新优先使用 npm 包。
33
58
 
34
59
  网页版的命令行安装方式:
@@ -37,6 +62,15 @@ GitHub Release 仍提供 `dsh-computer-use.tgz`,供离线或手动安装。市
37
62
  dsh plugin --profile web add dsh-codex-style-computer-use
38
63
  ```
39
64
 
65
+ 桌面版排障必须使用**官方桌面自带的入口**。例如默认安装目录可这样查询:
66
+
67
+ ```powershell
68
+ $CuDshInstall = Join-Path $env:LOCALAPPDATA 'Programs\DeepSeek Harness'
69
+ & "$CuDshInstall\resources\runtime\cli\bin\dsh.cmd" plugin --profile desktop why dsh-codex-style-computer-use
70
+ ```
71
+
72
+ 若安装在 D 盘等自定义位置,先将 `$CuDshInstall` 改成桌面快捷方式实际指向的安装目录。PATH 上旧 npm CLI 的 `dsh` 可能拒绝 desktop profile;不要把 `resources/runtime/bin` 加到全局 PATH,也无需自己安装 pnpm。GUI 安装仍是首选。
73
+
40
74
  ## 随包 skill
41
75
 
42
76
  驱动类工具会被锁住,直到会话给出那句只存在于 `skills/computer-use/SKILL.md` 里的回执短语。
@@ -116,6 +150,16 @@ node mcp/src/index.js
116
150
 
117
151
  ## 开发
118
152
 
153
+ 发布者检查默认查询 npm 官方源。若本地无法访问,可用 `CU_REGISTRY` 为只读的 `check` 命令指定镜像;JSON 输出会注明 `registry` 和 `authoritative`。PowerShell 示例(只影响当前终端):
154
+
155
+ ```powershell
156
+ $env:CU_REGISTRY = 'https://registry.npmmirror.com'
157
+ node scripts/release-channel.mjs check 1.2.0
158
+ Remove-Item Env:CU_REGISTRY
159
+ ```
160
+
161
+ 退出码 `0` 表示通过发布者的 24 小时检查,`2` 表示仍在冷却,`1` 表示检查失败。网络失败不代表版本未发布,镜像也可能尚未同步。`promote` 会忽略 `CU_REGISTRY`,始终向 `https://registry.npmjs.org` 重新检查并写入 `latest`,因此需要能访问官方源,必要时使用 npm 的代理配置。本工具只供发布者使用,普通用户安装不需要运行它。详见 [发布流程](docs/RELEASE-1.2.0.md)。
162
+
119
163
  仓库自带守卫套件,保护上面这些不变量——循环生命周期、提问/刹车协议、操作分类、skill 门禁,
120
164
  以及随包 skill 的注册接线:
121
165
 
@@ -3,6 +3,7 @@ import path from 'node:path'
3
3
  import os from 'node:os'
4
4
  import { fileURLToPath } from 'node:url'
5
5
  import { spawn } from 'node:child_process'
6
+ import { randomUUID } from 'node:crypto'
6
7
  import { prepareNativeBinary } from './core/native-runtime.js'
7
8
 
8
9
  export class AttentionNative {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-style-computer-use",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Codex-style Computer Use for DeepSeek Harness: Windows desktop control, screenshots, UI Automation, human input handoff, approval gates and prebuilt native workers. Install and update from dsh-market without PowerShell 7 or build steps.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -0,0 +1,8 @@
1
+ @echo off
2
+ setlocal
3
+ echo Restoring saved Computer Use configuration after the package rename.
4
+ echo A backup is created before changing the desktop profile.
5
+ "%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File "%~dp0migrate-profile.ps1" -Apply
6
+ set "CU_RESULT=%ERRORLEVEL%"
7
+ pause
8
+ exit /b %CU_RESULT%
@@ -0,0 +1,65 @@
1
+ // Explicit one-time user-patch migration. No install hook and no plugin startup write.
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { createRequire } from 'node:module'
5
+ import { fileURLToPath, pathToFileURL } from 'node:url'
6
+ import { randomUUID } from 'node:crypto'
7
+
8
+ export const OLD_NAME = 'dsh-computer-use'
9
+ export const NEW_NAME = 'dsh-codex-style-computer-use'
10
+
11
+ export function planMigration(source, yaml) {
12
+ const doc = yaml.parseDocument(source, { keepSourceTokens: true, uniqueKeys: true })
13
+ if (doc.errors.length) throw Error(`Invalid patch YAML: ${doc.errors[0].message}`)
14
+ if (!yaml.isSeq(doc.contents)) throw Error('The patch file must be a YAML list')
15
+ const replacements = [], unsupported = []
16
+ for (const item of doc.contents.items) {
17
+ if (!yaml.isMap(item) || item.get('id') !== 'computer-use') continue
18
+ const name = item.get('name', true)
19
+ if (!yaml.isScalar(name) || name.value !== OLD_NAME) continue
20
+ if (item.has('insert') || !name.range) { unsupported.push('A computer-use insert patch needs manual review'); continue }
21
+ replacements.push({ start: name.range[0], end: name.range[1], value: JSON.stringify(NEW_NAME) })
22
+ }
23
+ if (unsupported.length) throw Error(unsupported.join('; '))
24
+ let updated = source
25
+ for (const replacement of replacements.sort((a, b) => b.start - a.start)) updated = updated.slice(0, replacement.start) + replacement.value + updated.slice(replacement.end)
26
+ // Only exact name scalars are replaced; config values, disabled state, comments,
27
+ // other plugins, line endings and !!js expressions keep their original bytes.
28
+ return { source, updated, changes: replacements.length }
29
+ }
30
+
31
+ export async function applyMigration(filename, plan, atomic) {
32
+ if (!plan.changes) return { changed: false, changes: 0 }
33
+ return atomic.withFileLock(filename, async () => {
34
+ const stat = fs.lstatSync(filename)
35
+ if (!stat.isFile() || stat.isSymbolicLink()) throw Error('Refusing to replace a non-regular patch file')
36
+ if (fs.readFileSync(filename, 'utf8') !== plan.source) throw Error('The patch changed after inspection; read it again before applying')
37
+ const backup = `${filename}.before-computer-use-rename-${randomUUID()}.bak`
38
+ fs.writeFileSync(backup, plan.source, { flag: 'wx', mode: stat.mode & 0o777 })
39
+ await atomic.writeFileAtomic(filename, plan.updated, { mode: stat.mode & 0o777 })
40
+ return { changed: true, changes: plan.changes, backup }
41
+ })
42
+ }
43
+
44
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
45
+ try {
46
+ const args = process.argv.slice(2)
47
+ const value = key => { const i = args.indexOf(key); return i < 0 ? undefined : args[i + 1] }
48
+ const filename = value('--patch')
49
+ if (!filename || !path.isAbsolute(filename)) throw Error('Usage: migrate-profile.mjs --patch <absolute cordis.patch.yml> [--host-root <absolute DSH runtime>] [--apply]')
50
+ const host = value('--host-root') || path.join(path.dirname(process.execPath), 'resources/app.asar/dsh')
51
+ const require = createRequire(path.join(host, 'package.json'))
52
+ const yaml = require('yaml')
53
+ const atomic = await import(pathToFileURL(require.resolve('@deepseek-ai/dsh-atomic-write')).href)
54
+ const plan = planMigration(fs.readFileSync(filename, 'utf8'), yaml)
55
+ if (args.includes('--apply') && plan.changes) {
56
+ const profile = JSON.parse(fs.readFileSync(path.join(path.dirname(filename), 'package.json'), 'utf8'))
57
+ const bundles = profile.dsh?.profile?.bundles || []
58
+ if (!bundles.includes(NEW_NAME) || bundles.includes(OLD_NAME)) throw Error('Install the new bundle and remove the old bundle first; this tool only migrates one profile, never the shared home patch')
59
+ const installed = JSON.parse(fs.readFileSync(path.join(path.dirname(filename), 'node_modules', NEW_NAME, 'package.json'), 'utf8'))
60
+ if (installed.name !== NEW_NAME) throw Error('The new package identity does not match')
61
+ }
62
+ const result = args.includes('--apply') ? await applyMigration(filename, plan, atomic) : { changed: false, changes: plan.changes, preview: true }
63
+ console.log(JSON.stringify({ patch: filename, oldName: OLD_NAME, newName: NEW_NAME, ...result }, null, 2))
64
+ } catch (error) { console.error(error.message); process.exitCode = 1 }
65
+ }
@@ -0,0 +1,35 @@
1
+ #Requires -Version 5.1
2
+ param([switch]$Apply, [string]$DshInstall = '', [string]$ProfileDirectory = '')
3
+ $ErrorActionPreference = 'Stop'
4
+ $cuPreviousNodeMode = $env:ELECTRON_RUN_AS_NODE
5
+ try {
6
+ if (-not $DshInstall) {
7
+ $cuCandidates = @(Join-Path $env:LOCALAPPDATA 'Programs/DeepSeek Harness')
8
+ foreach ($cuRegistry in @('HKCU:/Software/Microsoft/Windows/CurrentVersion/Uninstall/*', 'HKLM:/Software/Microsoft/Windows/CurrentVersion/Uninstall/*')) {
9
+ $cuEntries = Get-ItemProperty $cuRegistry -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like 'DeepSeek Harness*' }
10
+ foreach ($cuEntry in $cuEntries) { if ($cuEntry.InstallLocation) { $cuCandidates += [string]$cuEntry.InstallLocation } }
11
+ }
12
+ $DshInstall = $cuCandidates | Where-Object { Test-Path -LiteralPath (Join-Path $_ 'DeepSeek Harness.exe') } | Select-Object -First 1
13
+ }
14
+ if (-not $DshInstall) { throw 'DSH Desktop was not found. Pass -DshInstall with its installation directory.' }
15
+ if (-not $ProfileDirectory) {
16
+ $cuDshDirectory = if ($env:DSH_HOME) { $env:DSH_HOME } else { Join-Path $env:USERPROFILE '.dsh' }
17
+ $ProfileDirectory = Join-Path $cuDshDirectory 'profiles/desktop'
18
+ }
19
+ $cuPatch = [IO.Path]::GetFullPath((Join-Path $ProfileDirectory 'cordis.patch.yml'))
20
+ if (-not (Test-Path -LiteralPath $cuPatch)) { throw "Profile patch not found: $cuPatch" }
21
+ $cuExecutable = Join-Path $DshInstall 'DeepSeek Harness.exe'
22
+ $cuScript = Join-Path $PSScriptRoot 'migrate-profile.mjs'
23
+ $cuArguments = @('--expose-internals', $cuScript, '--patch', $cuPatch)
24
+ if ($Apply) { $cuArguments += '--apply' }
25
+ $env:ELECTRON_RUN_AS_NODE = '1'
26
+ # Piping makes Windows PowerShell wait for Electron's GUI-subsystem exe.
27
+ & $cuExecutable @cuArguments | Out-Host
28
+ if ($LASTEXITCODE -ne 0) { throw "Migration failed (exit $LASTEXITCODE). No success is claimed; inspect the message above." }
29
+ } catch {
30
+ Write-Host $_.Exception.Message -ForegroundColor Red
31
+ exit 1
32
+ } finally {
33
+ if ($null -eq $cuPreviousNodeMode) { Remove-Item Env:ELECTRON_RUN_AS_NODE -ErrorAction SilentlyContinue }
34
+ else { $env:ELECTRON_RUN_AS_NODE = $cuPreviousNodeMode }
35
+ }
@@ -0,0 +1,78 @@
1
+ // Publisher tool. This file is never an npm lifecycle hook.
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+ import { execFileSync } from 'node:child_process'
6
+
7
+ export const PACKAGE = 'dsh-codex-style-computer-use'
8
+ export const MIN_AGE_MS = 24 * 60 * 60 * 1000
9
+ export const OFFICIAL_REGISTRY = 'https://registry.npmjs.org'
10
+
11
+ // A mirror is useful for a read-only local check. Promotion must recheck and
12
+ // write the authoritative registry, even when CU_REGISTRY remains in the shell.
13
+ export function releaseRegistry(operation, override = process.env.CU_REGISTRY) {
14
+ if (operation === 'promote') return OFFICIAL_REGISTRY
15
+ if (operation !== 'check') throw Error('Expected check or promote')
16
+ let registry
17
+ try { registry = new URL(override?.trim() || OFFICIAL_REGISTRY) }
18
+ catch { throw Error('CU_REGISTRY must be an HTTPS registry URL') }
19
+ if (registry.protocol !== 'https:' || registry.username || registry.password || registry.search || registry.hash) {
20
+ throw Error('CU_REGISTRY must be an HTTPS registry URL without credentials, query or fragment')
21
+ }
22
+ return registry.href.replace(/\/+$/, '')
23
+ }
24
+
25
+ export function releaseReadiness(metadata, version, now = Date.now()) {
26
+ if (!/^\d+\.\d+\.\d+$/.test(version)) throw Error('Promotion requires an exact stable version, e.g. 1.2.1')
27
+ const manifest = metadata.versions?.[version]
28
+ if (manifest?.name !== PACKAGE || manifest.version !== version) throw Error('The requested published version is missing or has the wrong identity')
29
+ const repository = typeof manifest.repository === 'string' ? manifest.repository : manifest.repository?.url
30
+ if (repository?.replace(/^git\+/, '').replace(/\.git$/, '').replace(/\/$/, '') !== 'https://github.com/ltmroberthk915/dsh-computer-use') throw Error('Published repository identity does not match')
31
+ const publishedText = metadata.time?.[version]
32
+ const publishedAt = typeof publishedText === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(publishedText) ? Date.parse(publishedText) : NaN
33
+ if (!Number.isFinite(publishedAt) || publishedAt > now) throw Error('Missing or invalid registry publication time; refusing promotion')
34
+ const latest = metadata['dist-tags']?.latest
35
+ if (latest && /^\d+\.\d+\.\d+$/.test(latest)) {
36
+ const a = version.split('.').map(Number), b = latest.split('.').map(Number)
37
+ for (let i = 0; i < 3; i++) {
38
+ if (a[i] < b[i]) throw Error('Refusing to move latest backwards')
39
+ if (a[i] > b[i]) break
40
+ }
41
+ }
42
+ const eligibleAt = publishedAt + MIN_AGE_MS
43
+ return { package: PACKAGE, version, ready: now >= eligibleAt, publishedAt: new Date(publishedAt).toISOString(), eligibleAt: new Date(eligibleAt).toISOString(), remainingSeconds: Math.max(0, Math.ceil((eligibleAt - now) / 1000)) }
44
+ }
45
+
46
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
47
+ const [operation, version] = process.argv.slice(2)
48
+ let registry
49
+ try {
50
+ if (!['check', 'promote'].includes(operation) || !/^\d+\.\d+\.\d+$/.test(version || '')) throw Error('Usage: node scripts/release-channel.mjs check|promote <stable-version>')
51
+ registry = releaseRegistry(operation)
52
+ const npm = path.join(path.dirname(process.execPath), 'node_modules/npm/bin/npm-cli.js')
53
+ if (!fs.existsSync(npm)) throw Error('Run this publisher command with the Node.js installation that includes npm')
54
+ const npmArgs = [`--registry=${registry}`, '--fetch-retries=0', '--fetch-timeout=20000']
55
+ const view = args => {
56
+ let stdout
57
+ try {
58
+ stdout = execFileSync(process.execPath, [npm, 'view', ...args, '--json', ...npmArgs], { encoding: 'utf8', windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] })
59
+ } catch (error) {
60
+ const code = String(error.stderr ?? '').match(/npm (?:error|ERR!) code ([A-Z0-9_]+)/i)?.[1] || error.code || error.status || 'unknown'
61
+ throw Error(`Registry query failed (${code}). Check network/proxy access and mirror synchronization; a failed query does not prove the package is unpublished. For a read-only check, set CU_REGISTRY to an accessible mirror. Promotion requires access to registry.npmjs.org.`)
62
+ }
63
+ try { return JSON.parse(stdout) }
64
+ catch { throw Error('Registry query did not return valid JSON; release readiness is unknown') }
65
+ }
66
+ const manifest = view([`${PACKAGE}@${version}`])
67
+ const metadata = { ...manifest, versions: { [version]: manifest } }
68
+ const verdict = { ...releaseReadiness(metadata, version), registry, authoritative: registry === OFFICIAL_REGISTRY }
69
+ console.log(JSON.stringify(verdict, null, 2))
70
+ if (!verdict.ready) { process.exitCode = 2 }
71
+ else if (operation === 'promote') {
72
+ execFileSync(process.execPath, [npm, 'dist-tag', 'add', `${PACKAGE}@${version}`, 'latest', ...npmArgs], { stdio: 'inherit', windowsHide: true })
73
+ const tag = view([PACKAGE, 'dist-tags.latest'])
74
+ if (tag !== version) throw Error('Registry latest did not match after promotion')
75
+ console.log(`Verified latest: ${PACKAGE}@${version}`)
76
+ }
77
+ } catch (error) { console.error(JSON.stringify({ operation, registry, error: error.message }, null, 2)); process.exitCode = 1 }
78
+ }
@@ -23,10 +23,14 @@ try {
23
23
  assert.equal(typeof cursor.x, 'number')
24
24
  const shot = await cu.screenshot({ maxWidth: 320 })
25
25
  assert(shot.image.length > 100)
26
- attention = new AttentionNative({ dataDir: path.join(scratch, 'attention') })
26
+ attention = new AttentionNative({ exe: path.join(scratch, 'absent-host.exe'), hostPid: 0, dataDir: path.join(scratch, 'attention') })
27
27
  assert.match(await attention.compile(), /prebuilt/)
28
28
  await attention.start()
29
- console.log(JSON.stringify({ ok: true, pathEmpty: true, coldCache: true, prebuiltWorker: true, ping: true, screenshotBytes: shot.image.length, attentionReady: true }))
29
+ const probe = await attention.request('probe')
30
+ assert.equal(probe.status, 'no-window')
31
+ assert.equal(probe.visible, false)
32
+ assert.match(probe.requestId, /^[0-9a-f-]{36}$/i)
33
+ console.log(JSON.stringify({ ok: true, pathEmpty: true, coldCache: true, prebuiltWorker: true, ping: true, screenshotBytes: shot.image.length, attentionReady: true, attentionProbe: true }))
30
34
  } finally {
31
35
  await attention?.close()
32
36
  await cu?.kill()