dsh-codearts 0.0.0-stage → 0.3.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.
package/DEV.md ADDED
@@ -0,0 +1,97 @@
1
+ # 开发循环(改代码后不用重启 DSH)
2
+
3
+ > 英文文档见 [README.en.md](./README.en.md) 与 [INSTALL.en.md](./INSTALL.en.md);本文目前只有中文。
4
+
5
+ 这份文档解决一件事:**改插件代码后,怎样不用「退出 DSH → 重新打开 → 手动点开卡片验证」**。
6
+
7
+ 结论先说:DSH 自带两条热重载链路,插件只要按它们的规矩部署,宿主侧和客户端改动都能自动生效。
8
+
9
+ | 改的是 | 生效方式 | 需要重启 DSH 吗 |
10
+ |---|---|---|
11
+ | `src/*.ts`(provider、shim、签名、状态路由…) | `dsh-hmr` 监听部署目录,重建后热重载插件条目 | 不需要(首次启用监听时可能要重启一次) |
12
+ | `src/client/*.tsx`、`*.module.css`(设置卡片 UI) | `dsh-client-hmr` 轮询客户端产物,变化后送进浏览器重新执行 bundle | 不需要,也不用刷新页面 |
13
+ | `package.json` 的 `dsh.*` 清单、换包版本、增删插件条目 | 宿主只在启动时读取这些 | **需要** |
14
+
15
+ ## 一次性准备
16
+
17
+ ```sh
18
+ pnpm run hmr:on # 往 profile 的 cordis.patch.yml 写入 hmr 覆盖行(幂等、无 BOM、带备份)
19
+ pnpm run hmr:status # 查看当前状态
20
+ pnpm run hmr:off # 关掉,回到 base 默认(只监听 profile 配置)
21
+ ```
22
+
23
+ `hmr:on` 做的是:把插件的部署目录加进 `@deepseek-ai/dsh-hmr` 的监听范围。
24
+
25
+ ```yaml
26
+ - id: hmr
27
+ name: '@deepseek-ai/dsh-hmr'
28
+ disabled: false
29
+ config:
30
+ root:
31
+ - node_modules/dsh-codearts
32
+ ignored: []
33
+ ```
34
+
35
+ 两个字段都不能省,原因都在 DSH 的实现里:
36
+
37
+ - `root` 默认是空表(`dsh-base` 的 patch 里写作 `root: []`),意思是**只监听 profile 配置文件、不监听模块**,所以改代码必须重启。
38
+ - `ignored` 的默认值里有一条排除 `node_modules` 的 glob,而插件恰好装在 `node_modules/dsh-codearts`,正好被默认值排除;而且它是按「相对 base 目录」做 picomatch 匹配的,所以覆盖行里必须显式写成空表。
39
+ - patch 是**整份 `config` 替换**而不是字段合并,所以 `root` 和 `ignored` 要一起给全。
40
+
41
+ 改完这个 patch 通常不用重启:profile 的 patch 文件本身也在 HMR 的监听范围内(profile 里 `patchReload: live`),条目会重新加载。若发现没生效,重启一次 DSH 即可,之后就一直有效。
42
+
43
+ ## 日常循环
44
+
45
+ ```sh
46
+ pnpm run dev # tsdown watch + 自动部署;Ctrl-C 退出
47
+ ```
48
+
49
+ 它会:监听 `src/` 的改动 → tsdown 重建 `lib/` → 包客户端 bundle → **原地同步**进 profile。剩下的交给 DSH:
50
+
51
+ - 宿主侧:改动落到部署目录的 `lib/*.mjs`,`dsh-hmr` 捕获到模块变化后重挂载插件条目。
52
+ - 客户端侧:`lib/client/index.js` 的 mtime/大小一变,`dsh-client-hmr` 就通过 `/plugins/events` 通知已打开的页面替换插件(页面里的 React 状态会丢,会话和工作区不受影响)。
53
+
54
+ 其他用法:
55
+
56
+ ```sh
57
+ pnpm run dev -- --once # 只构建并部署一次(等价于旧的 build + deploy)
58
+ pnpm run dev -- <profileDir> # 指定 profile,默认 ~/.dsh/profiles/desktop
59
+ DSH_CODEARTS_DEV_DEBUG=1 pnpm run dev # 打印 fs.watch 事件,排查「改了没反应」
60
+ pnpm run deploy # 只部署(可带 profileDir 与 --quiet)
61
+ ```
62
+
63
+ ## 两个必须守住的约定
64
+
65
+ 这两条不是风格问题,破了热重载就不工作,都是踩过之后写进来的:
66
+
67
+ 1. **部署必须原地覆盖写文件,不能清空目录再拷回。**
68
+ `dsh-hmr` 只处理 chokidar 的 `change` 事件(add/unlink 不在处理之列),而「先删整个 `lib/` 再复制」产生的是 unlink + add。`scripts/deploy.mjs` 因此改成:内容不同的文件原地覆盖,源里没有的文件才删,内容相同的连 mtime 都不碰。
69
+
70
+ 2. **绝不能用 PowerShell 5.1 的文本写入去碰 profile 里的 manifest / patch。**
71
+ PS 5.1 的 `Set-Content` / `Out-File` 默认带 UTF-8 BOM,宿主的 `readProfileManifest` 是裸 `JSON.parse`,见 BOM 就抛 `DesktopHostFatalError`,启动直接崩,而且「自动恢复」会把 BOM 再写回去,于是无限崩溃。`scripts/` 下的脚本一律走 `fs.writeFileSync`(无 BOM)并在写完后复查前 3 字节。
72
+
73
+ ## 排查
74
+
75
+ - **改了代码没反应**:先 `pnpm run hmr:status` 确认覆盖行在;再确认 `pnpm run dev` 真的部署了(日志里会有「已部署(新增 x / 覆盖 y / 清理 z)」)。
76
+ - **日志里出现「产物无有效变化,跳过部署」**:`lib/` 内容与上次部署一致。若你确实改了源码,多半是被构建常量折叠掉了(例如只改了被 `define` 替换掉的分支),或者 tsdown 还没重建完。
77
+ - **`lib/` 首次构建期间**:`dev` 会静默等待,不会报错。
78
+ - **事件很多但内容没变**:tsdown 清理输出目录时会瞬间产生大量文件事件,本脚本按内容指纹判断并自动退避(400ms → 最多 5s),不会空转刷盘。
79
+ - **插件加载失败**:看 `profiles/<profile>/.plugin-manager/logs/` 里的宿主日志,重载失败会在插件列表里显示并可重试。
80
+
81
+ ## 脚本一览
82
+
83
+ | 脚本 | 作用 |
84
+ |---|---|
85
+ | `scripts/dev.mjs` | 开发循环:tsdown watch + 包客户端 bundle + 原地部署 |
86
+ | `scripts/deploy.mjs` | 部署:原地同步 `lib/` 与包内文件,按需登记 profile 的三份 manifest(改动前留 `*.bak-codearts-*` 备份) |
87
+ | `scripts/hmr.mjs` | 开关 profile 的 hmr 模块监听(`on` / `off` / `status`) |
88
+ | `scripts/wrap-client.mjs` | 把客户端 CJS 产物包成 `__ModuleLoader__` 自注册形态 |
89
+
90
+ ## 依据
91
+
92
+ 以上关于热重载的结论来自 DSH 自带文档与实现,而不是猜测:
93
+
94
+ - `@deepseek-ai/dsh-base` 的 `cordis.patch.yml`:`hmr` 条目默认 `root: []`,注释写着「Profile configuration reloads by default; module roots are opt-in.」
95
+ - `@deepseek-ai/dsh-hmr` 的 README:`root` / `ignored` / `debounce` 的含义;「模块替换需要 Node loader 内部接口……HMR 本身不重启进程」;「通过插件管理器替换已安装包版本仍需要重启」。
96
+ - `@deepseek-ai/dsh-hmr` 的实现:只 `watch` 后注册 `change` 回调;`ignored` 以 `relative(watchBaseDir, path)` 做 picomatch 匹配;`partialReload()` 把改动映射回插件条目并重挂载。
97
+ - `@deepseek-ai/dsh-client-hmr` 的 README 与实现:宿主半侧 `stat` 轮询每个图条目的客户端产物(`artifactBaseline`),变化即 `clientModules.rebuilt(id)` 并通过 `/plugins/events` 推送 `rebuilt` 帧,浏览器半侧据此替换插件。
package/INSTALL.en.md ADDED
@@ -0,0 +1,135 @@
1
+ # dsh-codearts install & verification guide
2
+
3
+ English | [中文](./INSTALL.md)
4
+
5
+ The plugin that brings Huawei Cloud CodeArts into DeepSeek Harness Desktop (DSH).
6
+
7
+ ## 1. Install into the desktop profile
8
+
9
+ Copying and registering the plugin takes three steps:
10
+
11
+ - put the package into `<home>/.dsh/profiles/desktop/node_modules/dsh-codearts/`
12
+ (containing `lib/`, `cordis.patch.yml`, `package.json`, `README.md`, `LICENSE`)
13
+ - declare `dsh-codearts` under `dependencies` in `profiles/desktop/package.json`
14
+ - register it in `node_modules/.package-map.json` and `node_modules/.modules.yaml`
15
+
16
+ `scripts/deploy.mjs` does all three (and only writes the profile manifests when something
17
+ actually changes):
18
+
19
+ ```sh
20
+ node scripts/deploy.mjs [profileDir] # defaults to <home>/.dsh/profiles/desktop
21
+ ```
22
+
23
+ > Backups: any profile file the script changes is backed up as `*.bak-codearts-*`; rolling
24
+ > back is just restoring it.
25
+
26
+ > Deployment **overwrites in place** (a file whose content is unchanged is not even
27
+ > touched): that is what lets an enabled `dsh-hmr` module watch see the change and reload
28
+ > the plugin. The full dev-loop and hot-reload story is in [DEV.md](./DEV.md) (Chinese).
29
+
30
+ ## 2. Verify in DSH Desktop
31
+
32
+ 1. **Fully quit** DeepSeek Harness (quit from the tray; make sure the process is gone).
33
+ 2. Start DSH again.
34
+ 3. Open **Settings → Plugins/Providers**: `dsh-codearts` (CodeArts Connect) should be
35
+ registered there.
36
+ 4. Open **Settings → the CodeArts card** (or codearts under Providers):
37
+ - click “Sign in with Huawei Cloud” → the browser opens the authorization page → sign in;
38
+ - after a successful sign-in it redirects to a local callback, and the credential is
39
+ written to `<home>/.dsh/.codearts-auth.json`.
40
+ 5. Start a new conversation, pick the **codearts** provider and a model such as `GLM-5.2`
41
+ (or `deepseek-v4-flash`), then send a message.
42
+
43
+ ## 3. If the GUI does not show the plugin, or loading fails
44
+
45
+ Check in this order:
46
+
47
+ 1. Make sure DSH was fully quit before starting again (hot reload does not scan for *new*
48
+ plugins).
49
+ 2. Check that `profiles/desktop/node_modules/dsh-codearts/lib/index.mjs` exists.
50
+ 3. Look for `dsh-codearts` errors in the DSH startup log (for example under
51
+ `profiles/desktop/.plugin-manager/logs/`) and send them to the developer.
52
+ 4. To uninstall completely: delete the `node_modules/dsh-codearts` directory and remove the
53
+ `dsh-codearts` entries from `profiles/desktop/package.json`,
54
+ `node_modules/.package-map.json` and `node_modules/.modules.yaml` (the backups show
55
+ exactly what to delete).
56
+
57
+ ## 4. Another machine / clean reinstall
58
+
59
+ 1. Build: `pnpm install && pnpm run build` (output lands in `lib/`).
60
+ 2. Copy this plugin’s `package.json` + `lib/` + `cordis.patch.yml` into the target DSH’s
61
+ `profiles/<profile>/node_modules/dsh-codearts/`.
62
+ 3. Add `"dsh-codearts": "<version>"` to `dependencies` in the target profile’s
63
+ `package.json`.
64
+ 4. In `node_modules/.package-map.json` add
65
+ `"dsh-codearts": {"url":"./dsh-codearts","dependencies":{"dsh-codearts":"dsh-codearts"}}`
66
+ and add `"dsh-codearts":"dsh-codearts"` to the `"."` entry’s `dependencies`.
67
+ 5. In `node_modules/.modules.yaml` add
68
+ `"dsh-codearts@<version>": ["node_modules\\dsh-codearts"]` to `hoistedLocations`.
69
+ 6. Restart DSH.
70
+
71
+ ## 5. Technical chain (verified independently)
72
+
73
+ | Step | Result |
74
+ |---|---|
75
+ | OAuth sign-in (local callback + PKCE + DPoP) | ✅ browser sign-in → token exchange → stored |
76
+ | Token exchange (STS `/v1/oauth2/tokens`, form + DPoP) | ✅ AK/SK + refresh_token obtained |
77
+ | Chat auth (`x-auth-token` + AK/SK HMAC signature) | ✅ HTTP 200 |
78
+ | Chat endpoint `/api/v2/chat/completions` | ✅ OpenAI-compatible SSE |
79
+ | Model discovery (built-in / benefit / agent-center) | ✅ merged live, see `dsh-codearts models` |
80
+ | Local usage ledger (per day, per model, per account) | ✅ recorded from the stream’s usage frame |
81
+ | Account identity (`/snap-manager/v1/current/user`) | ✅ stable `user_id` returned |
82
+
83
+ > The ✅ rows above were confirmed against a live account from this machine. The end-to-end
84
+ > path *inside* the plugin process (shim → adapter → card) still needs to be seen in the DSH
85
+ > GUI, which a headless environment cannot do for you.
86
+
87
+ ## 6. Known limitations
88
+
89
+ - Free / limited-time benefit models (`deepseek-v4-flash-0731`, `deepseek-v4-pro-0813`,
90
+ `deepseek-v4.1-flash`, `glm-5.3-flash`) are supported: the plugin claims the benefit
91
+ before chatting and sends the `maas_type: benefit` header before signing, so they cost
92
+ nothing. Commercial models (GLM-5.2 and friends) work normally too.
93
+ - The model list is **discovered** (built-in `/v1/model/builtin`, benefit
94
+ `/api/v1/gateway/config`, agent-center `gpts.models`, merged), refreshed at startup and
95
+ hourly, falling back to the built-in seed list when a source fails or nobody is signed in.
96
+ `dsh-codearts models` shows what upstream really returns.
97
+ - The advertised `max_tokens` exceeds what the chat endpoint accepts (the benefit gateway
98
+ advertises 393216; the endpoint reports `InferHub.001001005.400` above 65536). Requests
99
+ are clamped to the measured ceiling — see `MEASURED_MAX_TOKENS`.
100
+ - Usage shown in the card and by `dsh-codearts usage` is a **local ledger**
101
+ (`$DSH_HOME/.codearts-usage.json`, kept per day for 60 days): CodeArts has no quota API.
102
+ Official detail is at
103
+ `codearts.huaweicloud.com/portal/settings/personal-usage`.
104
+ - Credentials last about 24 hours and are renewed automatically with the refresh token.
105
+ - A headless environment cannot render the DSH GUI, so the GUI end-to-end path has to be
106
+ confirmed on your machine.
107
+
108
+ ## 7. Deployment taboo (hard-won: it crashed DSH on startup)
109
+
110
+ ⚠️ **Never write any profile manifest** (`profiles/desktop/package.json`,
111
+ `node_modules/.package-map.json`, `node_modules/.modules.yaml`, …) **with Windows
112
+ PowerShell 5.1’s `Set-Content` / `Out-File`**. PS 5.1 writes UTF-8 **with a BOM
113
+ (EF BB BF)** by default, and the DSH host reads those files with a bare `JSON.parse` — a
114
+ BOM makes it throw `DesktopHostFatalError` and startup dies outright. Its “automatic
115
+ recovery” then rewrites the file through its own path, BOM included, so the crash repeats
116
+ forever behind “The recovery operation failed”.
117
+
118
+ **Do this instead (any one of them):**
119
+
120
+ 1. **Prefer `Copy-Item`** (binary copy of an already-correct file) — it never adds or
121
+ removes bytes, so no BOM.
122
+ 2. **Use Node’s `fs.writeFileSync`** (no BOM by default).
123
+ 3. **Use PowerShell 7’s `-Encoding utf8NoBOM`** (PS 5.1 has no such option).
124
+
125
+ After writing, always check the first three bytes with Node:
126
+
127
+ ```js
128
+ const fs=require('fs');const b=fs.readFileSync(p);
129
+ console.log(b[0]===0xEF&&b[1]===0xBB&&b[2]===0xBF?'BOM!':'no-BOM');
130
+ ```
131
+
132
+ > History: a deployment between 18:06 and 18:09 wrote `desktop/package.json` with a BOM via
133
+ > PS 5.1, which crashed startup repeatedly. It was fixed by rewriting the file with Node
134
+ > (backup `package.json.bak-bomfix-*`). A scan of the whole `~/.dsh` tree found that one
135
+ > file only.
package/INSTALL.md ADDED
@@ -0,0 +1,115 @@
1
+ # dsh-codearts 安装与验证指引
2
+
3
+ [English](./INSTALL.en.md) | 中文
4
+
5
+ 把华为云 CodeArts 接入 DeepSeek Harness 桌面版(DSH)的插件。
6
+
7
+ ## 一、安装到 desktop profile
8
+
9
+ 把插件复制并登记进 DSH 的 desktop profile,共三步:
10
+
11
+ - 包本体放到 `<用户目录>\.dsh\profiles\desktop\node_modules\dsh-codearts\`
12
+ (含 `lib/`、`cordis.patch.yml`、`package.json`、`README.md`、`LICENSE`)
13
+ - 在 `profiles/desktop/package.json` 的 `dependencies` 声明 `dsh-codearts`
14
+ - 在 `node_modules/.package-map.json` 与 `node_modules/.modules.yaml` 登记
15
+
16
+ `scripts/deploy.mjs` 会自动完成这三步(并只在需要时写入 profiles 下的 manifest):
17
+
18
+ ```sh
19
+ node scripts/deploy.mjs [profileDir] # profileDir 默认 <用户目录>/.dsh/profiles/desktop
20
+ ```
21
+
22
+ > 备份:脚本改动的 profile 文件会留下 `*.bak-codearts-*` 备份,回滚直接还原即可。
23
+
24
+ > 部署是**原地覆盖**(内容没变的文件连 mtime 都不动):这样已启用的 `dsh-hmr` 模块监听才能
25
+ > 把改动当成一次变更并热重载插件。开发循环与热重载的完整说明见 [DEV.md](./DEV.md)。
26
+
27
+ ## 二、在 DSH 桌面端验证
28
+
29
+ 1. **完全退出** DeepSeek Harness(托盘退出,确保进程不在)。
30
+ 2. 重新打开 DSH。
31
+ 3. 进入 **设置 → 插件/Providers**,应当能看到 `dsh-codearts`(CodeArts Connect)已注册。
32
+ 4. 进入 **设置 → CodeArts 卡片**(或 Providers 里的 codearts):
33
+ - 点「使用华为云账号登录」→ 浏览器打开授权页 → 华为云账号登录;
34
+ - 登录成功会自动跳回本机回调,凭证写入 `<用户目录>\.dsh\.codearts-auth.json`。
35
+ 5. 新建对话,provider 选 **codearts**,模型选 `GLM-5.2`(或 `deepseek-v4-flash` 等),
36
+ 发送消息即可。
37
+
38
+ ## 三、如果 GUI 里看不到插件 / 报加载错误
39
+
40
+ 按顺序排查:
41
+
42
+ 1. 确认 DSH 完全退出后再启动(热重载不扫描新插件)。
43
+ 2. 看 `profiles/desktop/node_modules/dsh-codearts/lib/index.mjs` 是否存在。
44
+ 3. 看 DSH 启动日志(如 `profiles/desktop/.plugin-manager/logs/`)里有没有
45
+ `dsh-codearts` 相关的报错;把报错贴给开发者。
46
+ 4. 若需要彻底卸载:删除 `node_modules/dsh-codearts` 目录,并从
47
+ `profiles/desktop/package.json`、`node_modules/.package-map.json`、
48
+ `node_modules/.modules.yaml` 里移除 `dsh-codearts` 条目(备份文件在,照着删即可)。
49
+
50
+ ## 四、在其他机器 / 干净重装
51
+
52
+ 1. 构建产物:`pnpm install && pnpm run build`(输出在 `lib/`)。
53
+ 2. 把本插件的 `package.json` + `lib/` + `cordis.patch.yml` 整体复制到
54
+ 目标 DSH 的 `profiles/<profile>/node_modules/dsh-codearts/`。
55
+ 3. 在目标 profile 的 `package.json` 的 `dependencies` 增加 `"dsh-codearts": "0.1.0"`。
56
+ 4. 在 `node_modules/.package-map.json` 增加
57
+ `"dsh-codearts": {"url":"./dsh-codearts","dependencies":{"dsh-codearts":"dsh-codearts"}}`
58
+ 并在 `"."` 的 `dependencies` 里加 `"dsh-codearts":"dsh-codearts"`。
59
+ 5. 在 `node_modules/.modules.yaml` 的 `hoistedLocations` 增加
60
+ `"dsh-codearts@0.1.0": ["node_modules\\dsh-codearts"]`。
61
+ 6. 重启 DSH。
62
+
63
+ ## 五、技术链路(已独立验证通过)
64
+
65
+ | 环节 | 结果 |
66
+ |---|---|
67
+ | OAuth 登录(本地回调 + PKCE + DPoP) | ✅ 浏览器登录 → 自动换 token → 落盘 |
68
+ | 换 token(STS `/v1/oauth2/tokens` 表单 + DPoP) | ✅ 拿到 AK/SK + refresh_token |
69
+ | 聊天鉴权(`x-auth-token` + AK/SK HMAC 签名) | ✅ HTTP 200 |
70
+ | 聊天端点 `/api/v2/chat/completions` | ✅ OpenAI 兼容 SSE |
71
+ | 模型 GLM-5.2 真实响应 | ⚠️ 上游原始 SSE 已抓到(探针),但插件内的 SSE **翻译层**(累计快照→增量)尚未端到端验证 |
72
+
73
+ > 说明:上表 ✅ 均为**独立探针**(`scripts/probe-*.ts`)直连华为云所得,绕过了 DSH 宿主
74
+ > 与插件进程内链路。插件进程内(shim → adapter → 卡片)的端到端仍需在 DSH GUI 内确认,
75
+ > 无头环境无法代验。
76
+
77
+ ## 六、已知限制
78
+
79
+ - 免费/福利模型(`deepseek-v4-flash-0731`、`deepseek-v4-pro-0813`、`deepseek-v4.1-flash`、
80
+ `glm-5.3-flash`)已支持:聊天前自动 `claimBenefit`,并在签名前带 `maas_type: benefit`
81
+ 头,可零成本使用。商业模型(GLM-5.2 等)同样正常。
82
+ - 模型清单为**动态发现**(内置 `/v1/model/builtin`、限时福利 `/api/v1/gateway/config`、
83
+ agent-center 的 `gpts.models` 三路合并),启动与每小时各刷新一次;任一路失败或未登录时
84
+ 退回内置种子清单。用 `dsh-codearts models` 可看上游真实返回。
85
+ - 上游标称的 `max_tokens` 高于聊天端点实际接受的量(福利网关标 393216,端点实测 65536 就报
86
+ `InferHub.001001005.400`),插件按实测上限下发,见 `MEASURED_MAX_TOKENS`。
87
+ - 卡片与 `dsh-codearts usage` 里的用量是**本机账本**(`$DSH_HOME/.codearts-usage.json`,按天保留
88
+ 60 天):CodeArts 没有额度接口,官方明细在 `codearts.huaweicloud.com/portal/settings/personal-usage`。
89
+ - 凭证有效期约 24 小时,过期后插件会自动用 refresh_token 续期。
90
+ - 无头环境无法渲染 DSH GUI,故 GUI 内的端到端渲染需你在本机确认。
91
+
92
+ ## 七、部署禁忌(血泪教训:曾导致 DSH 启动直接崩溃)
93
+
94
+ ⚠️ **绝不能用 Windows PowerShell 5.1 的 `Set-Content` / `Out-File` 写入任何 profile manifest**
95
+ (`profiles/desktop/package.json`、`node_modules/.package-map.json`、
96
+ `node_modules/.modules.yaml` 等)。PowerShell 5.1 的文本写入默认带 **UTF-8 BOM
97
+ (EF BB BF)**,而 DSH 宿主的 `readProfileManifest` 是裸 `JSON.parse`,见 BOM 就抛
98
+ `DesktopHostFatalError`,启动直接死;其"自动恢复"又用自己的写盘路径重写一遍,BOM
99
+ 还在,于是反复弹「The recovery operation failed」无限崩溃。
100
+
101
+ **正确做法(任选其一):**
102
+ 1. **优先 `Copy-Item` 二进制复制**(如把改好的 package.json 复制进去)——`Copy-Item`
103
+ 不增删字节,不会带 BOM。
104
+ 2. **用 Node `fs.writeFileSync`**(默认无 BOM)。
105
+ 3. **用 PowerShell 7 的 `-Encoding utf8NoBOM`**(PS5.1 没有该选项)。
106
+
107
+ 写入后务必用 Node 校验前 3 字节是否为 BOM:
108
+ ```js
109
+ const fs=require('fs');const b=fs.readFileSync(p);
110
+ console.log(b[0]===0xEF&&b[1]===0xBB&&b[2]===0xBF?'BOM!':'no-BOM');
111
+ ```
112
+
113
+ > 历史事故:18:06–18:09 的部署用 PS5.1 写 `desktop/package.json` 带了 BOM,导致启动
114
+ > 连环崩溃;最终由用户侧另开 agent 用 Node 重写为无 BOM 修复(备份
115
+ > `package.json.bak-bomfix-*`)。全 `~/.dsh` 树已扫,仅此一个 BOM 文件。
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 you
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,157 @@
1
+ # DSH CodeArts Connect
2
+
3
+ English | [中文](./README.md)
4
+
5
+ Brings Huawei Cloud CodeArts into DeepSeek Harness (DSH), so the models CodeArts offers
6
+ (GLM-5.2, Pangu, Qwen, DeepSeek, …) are usable straight from the DSH chat window.
7
+
8
+ ## Features
9
+
10
+ - **Account sign-in**: click “Sign in with Huawei Cloud” once in the settings card; the
11
+ browser finishes authorization and the credential is stored automatically — no keys to
12
+ copy by hand.
13
+ - **Automatic renewal**: the credential is renewed before it expires. If renewal fails the
14
+ card tells you to sign in again instead of silently using a dead credential.
15
+ - **Multiple accounts with rotation**: store more than one account (sign in again from the
16
+ card). When one account has no free session slot, the request moves to the next account
17
+ instead of waiting — this is the actual fix for “I stopped a generation and the next
18
+ question takes half a minute”.
19
+ - **Optional models**: choose which models appear in the picker (config `enabledModels`).
20
+ Unchecking one only stops recommending it; sessions already using it keep working. An
21
+ empty selection means “offer everything”.
22
+ - **Model discovery**: the plugin fetches the account’s real model list on startup and
23
+ hourly (built-in, limited-time benefit and agent-center sources merged) and falls back to
24
+ the built-in seed list when that fails.
25
+ - **Local usage**: the card and `dsh-codearts usage` show today’s and lifetime request and
26
+ token counts (per model, per account), plus each account’s **session-slot occupancy** —
27
+ which is exactly why a turn had to wait. CodeArts exposes no quota API, so these are the
28
+ requests this machine sent through the plugin; the card links to the official usage page.
29
+ - **Image input**: models with vision support accept pasted or dropped images in a chat.
30
+ - **Fallback path**: you can also paste a bare security token in the card, for scripts or
31
+ troubleshooting.
32
+ - **CLI**: `status` / `models` / `usage` / `identity` / `doctor` / `logout` for checking
33
+ sign-in state, discovering models and reading usage without a GUI.
34
+
35
+ ## Install
36
+
37
+ See [INSTALL.en.md](./INSTALL.en.md) for the full walkthrough. In short: put
38
+ `package.json` + `lib/` + `cordis.patch.yml` into
39
+ `profiles/<profile>/node_modules/dsh-codearts/` and register the package in the profile’s
40
+ `package.json`, `node_modules/.package-map.json` and `node_modules/.modules.yaml`.
41
+
42
+ ```sh
43
+ # build from this repository
44
+ pnpm install && pnpm run build
45
+ node scripts/deploy.mjs [profileDir] # defaults to <home>/.dsh/profiles/desktop
46
+ ```
47
+
48
+ Debugging does not require restarting DSH every time: see [DEV.md](./DEV.md) (Chinese) —
49
+ host-side changes hot-reload through `dsh-hmr`, and the client card is swapped into the
50
+ browser by `dsh-client-hmr`.
51
+
52
+ ## Usage
53
+
54
+ 1. Open DSH settings → CodeArts card → “Sign in with Huawei Cloud”.
55
+ 2. The browser opens the authorization page → sign in → it redirects back to a local
56
+ callback and the credential is written to `$DSH_HOME/.codearts-auth.json`.
57
+ 3. Start a new conversation, pick the `codearts` provider, then pick a model.
58
+
59
+ ## CLI
60
+
61
+ ```sh
62
+ dsh plugin --profile <profile> exec dsh-codearts status [--json] # sign-in state + seed model list
63
+ dsh plugin --profile <profile> exec dsh-codearts models [--json] # discover the account's real models
64
+ dsh plugin --profile <profile> exec dsh-codearts usage [--json] # local usage ledger (not account quota)
65
+ dsh plugin --profile <profile> exec dsh-codearts identity [--fix] # stable account ids; merge duplicates
66
+ dsh plugin --profile <profile> exec dsh-codearts doctor # secret-free diagnostics
67
+ dsh plugin --profile <profile> exec dsh-codearts logout # drop the credential this plugin stored
68
+ ```
69
+
70
+ `models` is the first thing to run when a model is missing from the picker: it prints what
71
+ the upstream actually returns (built-in / limited-time benefit / agent-center, merged)
72
+ rather than the hand-maintained seed list.
73
+
74
+ `usage` reads the plugin’s own ledger (`$DSH_HOME/.codearts-usage.json`, kept per day for
75
+ 60 days) — **not an account balance**: CodeArts has no quota API. Official detail lives at
76
+ `codearts.huaweicloud.com/portal/settings/personal-usage`, which the card also links to.
77
+
78
+ `identity` prints the stable upstream `user_id` for each stored account (the same id twice
79
+ means a duplicate entry); `--fix` merges those onto the stable key and backfills
80
+ `user_name` / `domain_id`.
81
+
82
+ `doctor` prints only paths, booleans and versions — never token material — so its output is
83
+ safe to paste into a bug report. `logout` removes only this plugin’s own copy of the
84
+ credential; nothing outside the plugin is touched.
85
+
86
+ ## Architecture
87
+
88
+ The structure follows `dsh-codebuddy-cli`: a local loopback shim plus a pi-ai adapter.
89
+
90
+ ```
91
+ DSH chat window
92
+ │ OpenAI /chat/completions, Bearer=<per-process random secret>
93
+ ▼
94
+ dsh-codearts shim (127.0.0.1:random port)
95
+ │ injects x-auth-token: <Huawei Cloud STS security_token>
96
+ │ + Huawei Cloud AK/SK SDK-HMAC-SHA256 signature
97
+ ▼
98
+ CodeArts /api/v2/chat/completions
99
+ ```
100
+
101
+ The shim binds loopback only and validates Host, Origin and the per-process secret, so
102
+ other local processes cannot impersonate it.
103
+
104
+ ## Known limitations
105
+
106
+ - Credentials last about 2 hours and are renewed before expiry (the refresh token is bound
107
+ to the DPoP key). When renewal fails, the card asks you to sign in again.
108
+ - The upstream caps concurrent sessions **per account** at 3, and a session is only
109
+ released after about a minute — **stopping a generation does not give the slot back**:
110
+ even a run that lasted under a second occupies one for the full minute. That is upstream
111
+ behaviour and the client cannot release it early. **Adding a second account in the card
112
+ works around it**, because requests rotate to an account with room.
113
+ - With a single account, asking again right after stopping a generation may still take
114
+ about half a minute. The plugin queues and retries automatically; the UI simply shows
115
+ nothing new while it waits.
116
+ - The login response carries no user identity (measured: `user_id` / `user_name` are empty
117
+ strings), so after signing in the plugin asks
118
+ `/snap-manager/v1/current/user` for the stable `user_id` and keys accounts on that: a
119
+ re-login of the same account updates the existing entry instead of creating a new one.
120
+ When that probe fails (network, API change) the old behaviour returns — accounts keyed by
121
+ their DPoP public key, where a re-login looks like a new account and the old entry goes
122
+ stale on its own. Legacy duplicates can be merged with `dsh-codearts identity --fix`
123
+ (running it without `--fix` is read-only).
124
+ - Advertised parameters are not always usable: the benefit gateway advertises `max_tokens`
125
+ 393216, while the chat endpoint rejects anything above 65536 with
126
+ `InferHub.001001005.400`. Requests are clamped to the **measured** ceiling per model
127
+ (65536 for the free DeepSeek family, 131072 for GLM-5.2, which was measured); models
128
+ without a measurement fall back to the conservative 65536. Context length uses the
129
+ discovered value, which only affects DSH’s context meter and truncation margin.
130
+ - The usage ledger counts only requests **this machine sent through the plugin**, so its
131
+ numbers are not an account total; the card links to the official page. The ledger keeps
132
+ 60 days and is lost when you move machines or clear `$DSH_HOME`.
133
+ - Docs: README and INSTALL have English versions; [DEV.md](./DEV.md) (hot-reload dev loop)
134
+ is Chinese-only for now.
135
+
136
+ ## Disclaimer
137
+
138
+ - This project is **for personal study and research only**. It drives the user’s own
139
+ account from the user’s own machine; do not use it commercially or beyond reasonable
140
+ personal use.
141
+ - Users must comply with the Huawei Cloud and CodeArts terms of service. Any consequence of
142
+ using this project (including but not limited to account restrictions or service
143
+ interruption) is the user’s own responsibility.
144
+ - The author is not liable for any direct or indirect loss caused by using or misusing this
145
+ project.
146
+ - This project is not affiliated with, authorized by, or endorsed by Huawei or DeepSeek.
147
+ Product names appear only to describe compatibility, and their trademarks belong to their
148
+ respective owners.
149
+
150
+ ## Acknowledgements
151
+
152
+ - [dsh-codebuddy-cli](https://github.com/fu827707013/dsh-codebuddy-cli) (MIT) — this
153
+ plugin’s DSH plugin structure, loopback shim and provider registration follow it.
154
+
155
+ ## License
156
+
157
+ [MIT](./LICENSE)
package/README.md CHANGED
@@ -1,3 +1,124 @@
1
- # Temporary Holding Version
1
+ # DSH CodeArts Connect
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [English](./README.en.md) | 中文
4
+
5
+ 把华为云 CodeArts 接入 DeepSeek Harness(DSH),实现在 DSH 对话窗口里直接使用 CodeArts 提供的模型(GLM-5.2、盘古、Qwen、DeepSeek 等)。
6
+
7
+ ## 功能
8
+
9
+ - **账号登录**:在设置卡片里点一次「使用华为云账号登录」,浏览器完成授权后凭证自动落盘,无需手工填写密钥。
10
+ - **自动续期**:凭证过期前自动续期;续期失败会在卡片上提示重新登录,不会静默使用失效凭证。
11
+ - **多账号轮转**:可以存多个账号(在卡片上再登录一次即可添加)。某个账号的会话槽位被占时,
12
+ 请求会自动换到下一个账号,不必干等 —— 这是「停止生成后重新提问要等半分钟」的正解。
13
+ - **可选模型**:可在设置卡片里勾选要出现在模型选择器中的模型(配置项 `enabledModels`)。未勾选的模型只是不再被推荐,已经选定它的会话仍可继续使用;不勾选等同于提供全部模型。
14
+ - **模型清单自动发现**:启动后与每小时各拉一次账号真实可用的模型(内置 / 限时福利 / agent-center 三路合并),失败时退回内置种子清单。
15
+ - **本机用量**:卡片与 `dsh-codearts usage` 展示今日/累计的请求数与输入输出 token(按模型、按账号),以及每个账号的**会话槽位占用**(占用时正好解释了「为什么刚才要等半分钟」)。CodeArts 没有额度接口,这里只统计本机经插件发出的请求,官方额度明细见卡片上的链接。
16
+ - **图片输入**:支持视觉的模型可直接在对话里粘贴或拖入图片。
17
+ - **降级路径**:也可以在卡片里直接粘贴安全令牌,适用于脚本或排查场景。
18
+ - **命令行**:`status` / `models` / `usage` / `identity` / `doctor` / `logout` 六个子命令,便于在无图形界面时检查登录状态、发现模型与查用量。
19
+
20
+ ## 安装
21
+
22
+ 详见 [INSTALL.md](./INSTALL.md)。核心是把 `package.json` + `lib/` + `cordis.patch.yml` 放进
23
+ `profiles/<profile>/node_modules/dsh-codearts/`,并在 profile 的 `package.json`、
24
+ `node_modules/.package-map.json`、`node_modules/.modules.yaml` 登记。
25
+
26
+ ```sh
27
+ # 从本仓库构建产物
28
+ pnpm install && pnpm run build
29
+ node scripts/deploy.mjs [profileDir] # profileDir 默认 <用户目录>/.dsh/profiles/desktop
30
+ ```
31
+
32
+ 改代码调试时不必反复重启:见 [DEV.md](./DEV.md)(宿主侧走 `dsh-hmr` 模块热重载,客户端卡片走
33
+ `dsh-client-hmr` 自动替换进浏览器)。
34
+
35
+ ## 使用
36
+
37
+ 1. 打开 DSH 设置 → CodeArts 卡片 → 「使用华为云账号登录」。
38
+ 2. 浏览器打开授权页 → 登录华为云账号 → 自动跳回本机回调,凭证写入
39
+ `$DSH_HOME/.codearts-auth.json`。
40
+ 3. 新建对话,provider 选择 `codearts`,再选一个模型即可开始。
41
+
42
+ ## 命令行
43
+
44
+ ```sh
45
+ dsh plugin --profile <profile> exec dsh-codearts status [--json] # 登录状态与种子模型清单
46
+ dsh plugin --profile <profile> exec dsh-codearts models [--json] # 向上游发现当前账号的真实模型
47
+ dsh plugin --profile <profile> exec dsh-codearts usage [--json] # 本机用量账本(非账号额度)
48
+ dsh plugin --profile <profile> exec dsh-codearts identity [--fix] # 查/回填账号稳定身份,合并重复条目
49
+ dsh plugin --profile <profile> exec dsh-codearts doctor # 不涉及密钥的环境诊断
50
+ dsh plugin --profile <profile> exec dsh-codearts logout # 清除本插件保存的凭证
51
+ ```
52
+
53
+ `models` 是排查「模型选择器里怎么没有某个模型」的第一手段:它打印上游真实返回的清单
54
+ (内置 / 限时福利 / agent-center 三路合并),而不是插件里那份手维护的种子清单。
55
+
56
+ `usage` 读的是插件自己的账本(`$DSH_HOME/.codearts-usage.json`,按天保留 60 天)——
57
+ **不是账号额度**:CodeArts 没有额度接口,官方明细在
58
+ `codearts.huaweicloud.com/portal/settings/personal-usage`,卡片上也有这个链接。
59
+
60
+ `identity` 打印每个已存账号在上游的稳定 `user_id`(同一个 id 出现两次就是重复条目);
61
+ 加 `--fix` 会把它们合并到稳定键上,顺带补上 `user_name` / `domain_id`。
62
+
63
+ `doctor` 只输出路径、状态与版本,不会打印任何令牌内容,输出可以直接贴进问题反馈;`logout`
64
+ 只删除本插件自己的凭证副本,不影响账号本身。
65
+
66
+ ## 架构
67
+
68
+ 沿用 `dsh-codebuddy-cli` 的结构:本地 loopback shim + pi-ai adapter。
69
+
70
+ ```
71
+ DSH 对话窗口
72
+ │ OpenAI /chat/completions 格式, Bearer=<本进程随机密钥>
73
+ ▼
74
+ dsh-codearts shim (127.0.0.1:随机端口)
75
+ │ 注入 x-auth-token: <华为云 STS security_token>
76
+ │ + 华为云 AK/SK SDK-HMAC-SHA256 签名
77
+ ▼
78
+ CodeArts /api/v2/chat/completions
79
+ ```
80
+
81
+ shim 只监听回环地址,并校验 Host、Origin 与本进程随机密钥,其他本机进程无法冒用。
82
+
83
+ ## 已知限制
84
+
85
+ - 凭证有效期约 2 小时,过期前会自动续期(刷新令牌与 DPoP 密钥绑定);续期失败会提示重新登录。
86
+ - 上游对**每个账号**的并发会话数有硬性限制(3 个),每个会话占用约 1 分钟才释放,而且**中途
87
+ 停止生成并不减免这个占用**:即使只跑了不到一秒就停,同样会占满一个名额。这是上游行为,
88
+ 客户端无法提前释放。**在卡片上添加第二个账号即可化解** —— 插件会把请求自动轮转到还有余量
89
+ 的账号上。
90
+ - 只有一个账号时,刚停止过一次生成就立刻重新提问,仍可能要等约半分钟。插件会自动排队重试,
91
+ 不需要手动重发,但等待期间界面上不会有新内容。
92
+ - 上游的登录响应不返回用户标识(实测 `user_id` / `user_name` 都是空串),所以插件在登录完成后
93
+ 会**再问一次** `/snap-manager/v1/current/user` 拿稳定的 `user_id` 作为账号的自然键:
94
+ 同一个账号重新登录会更新原条目,不再变成新账号。这一步失败(网络/接口变动)时退回旧行为
95
+ —— 用 DPoP 公钥当键,此时**同一账号重新登录会被当成新账号**,旧条目会因凭据失效而自行停用。
96
+ 历史遗留的重复条目可以用 `dsh-codearts identity --fix` 合并到稳定键上(先不带 `--fix` 跑一次是只读的)。
97
+ - 某个模型没出现时,用 `dsh-codearts models` 看上游到底返回了什么;上游标称的参数不一定可用:
98
+ 福利网关把 `max_tokens` 标成 393216,而聊天端点实测超过 65536 就报
99
+ `InferHub.001001005.400`。插件按**实测上限**下发请求(免费 DeepSeek 系列 65536、
100
+ GLM-5.2 已实测可到 131072),没实测过的模型退回 65536 保守值。上下文长度用发现值,
101
+ 它只影响 DSH 的上下文计量与截断余量。
102
+ - 用量账本只统计**本机经插件发出**的请求:CodeArts 不提供额度/积分接口,所以卡片上的数字
103
+ 不等于账号总额度;官方明细请走卡片里的链接。账本按天保留 60 天,换机器/清 `$DSH_HOME` 即丢失。
104
+ - 文档:README 与 INSTALL 有英文版([README.en.md](./README.en.md) / [INSTALL.en.md](./INSTALL.en.md)),
105
+ [DEV.md](./DEV.md)(热重载开发循环)目前只有中文。
106
+
107
+ ## 免责声明
108
+
109
+ - 本项目**仅供个人学习和研究使用**,仅驱动使用者自己的账号在本机调用,请勿用于商业用途或
110
+ 超出个人合理使用的场景。
111
+ - 使用者需遵守华为云及 CodeArts 的服务条款;因使用本项目产生的任何后果(包括但不限于账号
112
+ 被限制、服务中断),由使用者自行承担。
113
+ - 本项目作者不对任何因使用或滥用本项目产生的直接或间接损失负责。
114
+ - 本项目与华为、DeepSeek 均无关联,未获其授权或认可;文中出现的名称仅用于描述兼容关系,
115
+ 其商标权利归各自所有。
116
+
117
+ ## 致谢
118
+
119
+ - [dsh-codebuddy-cli](https://github.com/fu827707013/dsh-codebuddy-cli)(MIT)— 本插件的
120
+ DSH 插件结构、loopback shim 与 provider 注册方式均以其为参照。
121
+
122
+ ## 许可证
123
+
124
+ [MIT](./LICENSE)