huaweicloud-devkit 1.0.2-dev.9 → 1.1.2-next.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/README.md CHANGED
@@ -1,17 +1,18 @@
1
1
  # HuaweiCloud DevKit
2
2
 
3
+ [![Discussions](https://img.shields.io/badge/Discussions-Join%20the%20discussion-blue)](https://github.com/huaweicloud/huaweicloud-devkit/discussions)
3
4
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
4
- [![CI](https://github.com/huaweicloud/HuaweiCloud-Devkit/actions/workflows/ci.yml/badge.svg)](https://github.com/huaweicloud/HuaweiCloud-Devkit/actions/workflows/ci.yml)
5
+ [![CI](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml/badge.svg)](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml)
5
6
 
6
7
  **[中文](README.zh-CN.md) | English**
7
8
 
8
9
  Help AI coding agents use Huawei Cloud safely and accurately — a single integration that gives agents cloud knowledge, CLI tooling, and safety guardrails.
9
10
 
10
- Supports OpenCode, Codex, CodeArts Agent, and WorkBuddy.
11
+ Supports OpenCode, Codex, CodeArts Agent, WorkBuddy, and DeepSeek Harness (DSH).
11
12
 
12
13
  ## Prerequisites
13
14
 
14
- - Node.js >= 22
15
+ - Node.js >= 20
15
16
 
16
17
  ## Quick Start
17
18
 
@@ -84,6 +85,29 @@ npx --yes huaweicloud-devkit uninstall --target workbuddy
84
85
 
85
86
  > **Updating**: `update` is incremental per agent — it refreshes only the installed files and leaves your config untouched. Use `update --target all` to update every installed agent at once.
86
87
 
88
+ ### DeepSeek Harness (DSH)
89
+
90
+ ```bash
91
+ npx --yes huaweicloud-devkit install --target dsh
92
+ ```
93
+
94
+ The installer copies Skills to `$DSH_HOME/skills`, installs the MCP server and
95
+ safety policy under `$DSH_HOME/huaweicloud-plugins`, and maintains
96
+ `$DSH_HOME/profiles/web/cordis.patch.yml`. **Restart the DSH session** after
97
+ installation.
98
+
99
+ ```bash
100
+ npx --yes huaweicloud-devkit doctor
101
+ npx --yes huaweicloud-devkit status --target dsh
102
+ npx --yes huaweicloud-devkit update --target dsh
103
+ npx --yes huaweicloud-devkit uninstall --target dsh
104
+ ```
105
+
106
+ > DSH V1 reuses the existing MCP server through
107
+ > `@deepseek-ai/dsh-mcp-client`. If the installer reports that the DSH MCP
108
+ > client is not detected, run:
109
+ > `npx @deepseek-ai/dsh plugin --profile web add @deepseek-ai/dsh-mcp-client`.
110
+
87
111
  ### Other Agents
88
112
 
89
113
  For agents that support the Model Context Protocol (MCP):
@@ -106,7 +130,7 @@ Then install:
106
130
  npx --yes huaweicloud-devkit install
107
131
  ```
108
132
 
109
- > **Prerequisite:** Node.js >= 22. Run `npx huaweicloud-devkit auth init` for unified credentials.
133
+ > **Prerequisite:** Node.js >= 20. Run `npx huaweicloud-devkit auth init` for unified credentials.
110
134
 
111
135
  ## What It Does
112
136
 
@@ -124,6 +148,7 @@ ECS, OBS, VPC, IAM, RDS, GaussDB, FunctionGraph, APIG, CCE, SMN/DMS, ModelArts,
124
148
  - [Architecture](docs/architecture.md)
125
149
  - [Safety Model](docs/safety-model.md)
126
150
  - [Hook Rule Model](docs/hook-rule-model.md)
151
+ - [DeepSeek Harness Integration](docs/dsh-integration.md)
127
152
  - [Changelog](docs/CHANGELOG.md)
128
153
  - [KooCLI official docs](https://support.huaweicloud.com/qs-hcli/hcli_02_003.html)
129
154
 
package/README.zh-CN.md CHANGED
@@ -1,17 +1,18 @@
1
1
  # HuaweiCloud DevKit
2
2
 
3
+ [![参与讨论](https://img.shields.io/badge/参与讨论-Join%20the%20discussion-blue)](https://github.com/huaweicloud/huaweicloud-devkit/discussions)
3
4
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
4
- [![CI](https://github.com/huaweicloud/HuaweiCloud-Devkit/actions/workflows/ci.yml/badge.svg)](https://github.com/huaweicloud/HuaweiCloud-Devkit/actions/workflows/ci.yml)
5
+ [![CI](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml/badge.svg)](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml)
5
6
 
6
7
  **中文 | [English](README.md)**
7
8
 
8
9
  帮助 AI 编码助手安全、准确地使用华为云——一站式集成云知识、CLI 工具和安全护栏。
9
10
 
10
- 支持 OpenCode、Codex、码道(CodeArts Agent)、WorkBuddy
11
+ 支持 OpenCode、Codex、码道(CodeArts Agent)、WorkBuddy、DeepSeek Harness(DSH)。
11
12
 
12
13
  ## 前置条件
13
14
 
14
- - Node.js >= 22
15
+ - Node.js >= 20
15
16
 
16
17
  ## 快速开始
17
18
 
@@ -84,6 +85,27 @@ npx --yes huaweicloud-devkit uninstall --target workbuddy
84
85
 
85
86
  > **更新机制**:`update` 按 agent 增量更新,只刷新必要文件、不动你的配置文件。`update --target all` 可一次更新所有已安装的 agent。
86
87
 
88
+ ### DeepSeek Harness(DSH)
89
+
90
+ ```bash
91
+ npx --yes huaweicloud-devkit install --target dsh
92
+ ```
93
+
94
+ 安装器会把 Skills 写入 `$DSH_HOME/skills`,把 MCP Server 和安全策略写入
95
+ `$DSH_HOME/huaweicloud-plugins`,并自动维护
96
+ `$DSH_HOME/profiles/web/cordis.patch.yml`。安装后**重启 DSH 会话**。
97
+
98
+ ```bash
99
+ npx --yes huaweicloud-devkit doctor
100
+ npx --yes huaweicloud-devkit status --target dsh
101
+ npx --yes huaweicloud-devkit update --target dsh
102
+ npx --yes huaweicloud-devkit uninstall --target dsh
103
+ ```
104
+
105
+ > DSH V1 通过 `@deepseek-ai/dsh-mcp-client` 复用现有 MCP Server。
106
+ > 如果安装器提示 DSH MCP 客户端未检测到,请按提示执行
107
+ > `npx @deepseek-ai/dsh plugin --profile web add @deepseek-ai/dsh-mcp-client`。
108
+
87
109
  ### 其他 Agent
88
110
 
89
111
  支持 MCP 协议的 Agent,手动配置:
@@ -106,7 +128,7 @@ npx --yes huaweicloud-devkit uninstall --target workbuddy
106
128
  npx --yes huaweicloud-devkit install
107
129
  ```
108
130
 
109
- > **前置条件:** Node.js >= 22。执行 `npx huaweicloud-devkit auth init` 完成统一认证。
131
+ > **前置条件:** Node.js >= 20。执行 `npx huaweicloud-devkit auth init` 完成统一认证。
110
132
 
111
133
  ## 功能特性
112
134
 
@@ -124,6 +146,7 @@ ECS、OBS、VPC、IAM、RDS、GaussDB、FunctionGraph、APIG、CCE、SMN/DMS、M
124
146
  - [架构](docs/architecture.md)
125
147
  - [安全模型](docs/safety-model.md)
126
148
  - [Hook 规则模型](docs/hook-rule-model.md)
149
+ - [DeepSeek Harness 集成](docs/dsh-integration.md)
127
150
  - [变更记录](docs/CHANGELOG.md)
128
151
  - [KooCLI 官方文档](https://support.huaweicloud.com/qs-hcli/hcli_02_003.html)
129
152
 
package/bin/setup.cjs CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.0.2-dev.9",
3
+ "version": "1.1.2-next.0",
4
4
  "description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
5
5
  "type": "module",
6
6
  "files": [
@@ -33,6 +33,8 @@
33
33
  "codex",
34
34
  "opencode",
35
35
  "workbuddy",
36
+ "dsh",
37
+ "deepseek-harness",
36
38
  "mcp",
37
39
  "koocli",
38
40
  "hcloud"
@@ -20,7 +20,7 @@
20
20
  "mcpServers": "./.mcp.json",
21
21
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
22
22
  "skills": "./skills/",
23
- "version": "1.0.2-dev.9",
23
+ "version": "1.1.2-next.0",
24
24
  "author": {
25
25
  "name": "HuaweiCloud Mate",
26
26
  "url": "https://github.com/huaweicloud"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-core",
3
- "version": "1.0.2-dev.9",
3
+ "version": "1.1.2-next.0",
4
4
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
5
5
  "author": {
6
6
  "name": "HuaweiCloud Mate",
@@ -20,7 +20,7 @@
20
20
  "mcpServers": "./.mcp.json",
21
21
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
22
22
  "skills": "./skills/",
23
- "version": "1.0.2-dev.9",
23
+ "version": "1.1.2-next.0",
24
24
  "author": {
25
25
  "name": "HuaweiCloud Mate",
26
26
  "url": "https://github.com/huaweicloud"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-core",
3
- "version": "1.0.0",
3
+ "version": "1.1.2-next.0",
4
4
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
5
5
  "author": {
6
6
  "name": "HuaweiCloud Mate",
@@ -8,6 +8,12 @@ version: 1
8
8
 
9
9
  **STOP - Do not answer from general knowledge.** Follow the procedure below.
10
10
 
11
+ ## Routing Guard: Deploy vs Store
12
+
13
+ - If the developer's goal is to **deploy, host, or preview a web app / static website** (temporary hosting, quick preview), do NOT default to OBS. Present deployment-target options with the sandbox first: ① huawei-sandbox (recommended), ② OBS static hosting, ③ ECS, ④ CCE. Ask: "建议优先部署到沙箱(临时运行环境,可预览访问),也可选 OBS 静态托管/ECS/CCE,你想部署到哪里?"
14
+ - Proceed with OBS only when the developer selects OBS, explicitly asks for OBS, or the intent is long-term static hosting / CDN / file storage.
15
+ - OBS is a storage service; it is not a general web-hosting default.
16
+
11
17
  ## Critical: OBS Command Syntax
12
18
 
13
19
  KooCLI OBS uses **obsutil-style** commands, NOT API-style operations. Always run `hcloud OBS help` (no `--`) before constructing commands:
@@ -15,6 +15,7 @@ Domain expertise for Huawei Cloud Sandbox (DevStation) instances and workspace t
15
15
  ## Activation
16
16
 
17
17
  - **Proactive offering**: when the developer's task needs a temporary runtime (e.g. "deploy this web app", "run this app and preview it"), offer the sandbox proactively — the developer never has to say "use sandbox". Prompt: "This task can be satisfied by a sandbox — use it?"
18
+ - **Static website hosting — present options, sandbox first**: when the developer asks to deploy/host/publish a static website or web app without naming a target, do NOT silently pick a service. Present the target options in this order: ① 沙箱 (recommended — temporary runtime, instant preview URL) ② OBS 静态托管 (long-term static hosting/CDN) ③ ECS ④ CCE, then follow the developer's choice. Recommended prompt: "建议优先部署到沙箱(临时运行环境,可预览访问),也可选择 OBS 静态托管、ECS 或 CCE,你想部署到哪里?" If the developer has no preference, proceed with the sandbox.
18
19
  - **Detect web apps in deploy-a-repo prompts**: prompts like "部署 GitHub - <owner>/<repo>" or "deploy <github repo>" do not say "web app". Clone or inspect the repository first (package.json with dev/serve/build scripts, index.html, frontend framework dependencies such as vue/react/angular/vite, static sites, Dockerfile running a web server). If it is a web application, offer the sandbox first: "检测到该项目是 Web 应用,建议优先使用沙箱部署(临时运行环境,可预览访问),是否使用?"
19
20
  - **Clone into the sandbox workspace directory**: always put project code under `/workspace/<repo-name>` (create the directory if missing) — `/workspace` is the sandbox's dedicated workspace mount at the filesystem root, not `$HOME/workspace`. Never use `/tmp` or other ephemeral locations. This keeps the project with the sandbox session, is easy to reference for serving/exposing, and survives session-level restarts of the shell.
20
21
  - **Deployment must end with a public URL**: after deploying and exposing the app with DevBridge, always return the tunnel URL to the developer as the final result — a deployment without an accessible link is incomplete.
@@ -48,13 +49,17 @@ Domain expertise for Huawei Cloud Sandbox (DevStation) instances and workspace t
48
49
 
49
50
  Setup is a **plugin-side preflight** — the developer should be asked a question only once, when the agreement actually needs signing:
50
51
 
51
- 1. **Check user** (transparent): `huaweicloud_sandbox_check_user` — verify `realname_verified` and `agreement_signed`
52
- 2. **Real-name verification** (only if `realname_verified=false`): tell the developer once, "Huawei Cloud requires real-name verification before using the sandbox." and stop — do not retry `connect` in a loop
53
- 3. **Sign agreement** (only if `agreement_signed=false`): ask the developer once as the plugin "Huawei Cloud sandbox requires signing its service agreement. May I sign it on your behalf?" — then call `huaweicloud_sandbox_sign_agreement`. Do not expose the underlying sandbox/DevBridge service as a separate entity the developer must understand or sign up for
54
- 4. **Connect**: `huaweicloud_sandbox_connect` — returns `session_id`, `dev_stage_id`, `connection_id`, `connection_address`
55
- 5. **Inject credentials** (optional): `huaweicloud_sandbox_credentials`enables cloud API access from sandbox
56
- 6. **Execute commands**: `huaweicloud_sandbox_exec_with_session` for interactive work
57
- 7. **Expose the deployed app and return the public URL**: set up a public access link for the app port and give the URL to the developer as the final result (see "Expose the deployed app (public URL)")this step uses an internal tunnel mechanism that must never be mentioned to the developer
52
+ 1. **Check user** (transparent): `huaweicloud_sandbox_check_user` — returns `realnameVerified`/`agreementSigned` (200) when all good, OR throws a 403 error with one of these codes:
53
+ - `HDKIT_NOT_REALNAME` real-name missing only go to step 2
54
+ - `HDKIT_NOT_AGREEMENT` — latest agreement not signed only go to step 3
55
+ - `HDKIT_NOT_REALNAME_AND_AGREEMENT` — both missing go to step 4
56
+ 2. **Real-name verification only** (`HDKIT_NOT_REALNAME`): tell the developer once, "Huawei Cloud requires real-name verification before using the sandbox please complete it in the Huawei Cloud console (实名认证)." and stop — do not retry `connect` in a loop
57
+ 3. **Sign agreement only** (`HDKIT_NOT_AGREEMENT`): **STOP and do NOT sign on your own.** Ask the developer: "Huawei Cloud sandbox requires signing the latest developer service agreement. May I sign it for you?" Then **wait for the developer to explicitly agree** (e.g. "签署" / "确认" / "sign it"). Only after explicit consent call `huaweicloud_sandbox_sign_agreement` and return its result (`signed`/`signedCount`) to the developer. **Never sign a legal agreement on the developer's behalf without their explicit, unambiguous consent.** Do not expose the underlying sandbox/DevBridge service as a separate entity the developer must understand or sign up for
58
+ 4. **Both missing** (`HDKIT_NOT_REALNAME_AND_AGREEMENT`): present **both** requirements together in one message the real-name verification steps (console, step 2) **and** the agreement-signing request (step 3, wait for explicit consent) — so the developer can complete both at once
59
+ 5. **Connect**: `huaweicloud_sandbox_connect` — returns `session_id`, `dev_stage_id`, `connection_id`, `connection_address`
60
+ 6. **Inject credentials** (optional): `huaweicloud_sandbox_credentials` — enables cloud API access from sandbox
61
+ 7. **Execute commands**: `huaweicloud_sandbox_exec_with_session` for interactive work
62
+ 8. **Expose the deployed app and return the public URL**: set up a public access link for the app port and give the URL to the developer as the final result (see "Expose the deployed app (public URL)") — this step uses an internal tunnel mechanism that must never be mentioned to the developer
58
63
 
59
64
  ## Expose the deployed app (public URL)
60
65
 
@@ -97,7 +102,7 @@ sleep 10 && cat /tmp/host.log
97
102
  | Trap | Why |
98
103
  |------|-----|
99
104
  | Agreement required first | `sandbox_connect` fails if the agreement isn't signed; the `sandbox_check_user` preflight detects this, so surface it to the developer only when signing is needed |
100
- | Real-name required | `sandbox_connect` fails if `realname_verified=false`; tell the developer once and stop, don't loop on connect |
105
+ | Real-name required | `sandbox_connect` fails if `realnameVerified=false`; tell the developer once and stop, don't loop on connect |
101
106
  | Never expose tunnel details | Do not mention "DevBridge"/"tunnel"/"devbridge" to the developer — say "正在生成访问地址..." and hand over only the URL |
102
107
  | Login needs `--huaweicloud` | `devbridge auth login --access-key/--secret-key` without `--huaweicloud` falls back to interactive browser login, which fails in the sandbox |
103
108
  | CLI PATH | The installer only writes `~/.bashrc`; run `export PATH=$PATH:$HOME/.huawei/bin` in the session before using `devbridge` |
@@ -22,6 +22,10 @@ Use this skill to turn vague developer intent into a precise Huawei Cloud capabi
22
22
  8. Treat Terraform as a secondary V1 path for reviewed IaC, not the default.
23
23
  9. When no built-in devkit skill matches, browse the community skill marketplace at https://github.com/huaweicloud/huaweicloud-skills. Fetch the index from https://raw.githubusercontent.com/huaweicloud/huaweicloud-skills/master/skills-index/index.json.
24
24
 
25
+ ## Deployment Target Options
26
+
27
+ When the intent is to **deploy, host, or preview a web app or static website** and the developer has not named a target, do NOT default to OBS or any single service. Present the options and let the developer choose, recommending the sandbox first: ① huawei-sandbox (recommended — temporary runtime, instant preview URL) ② huawei-obs (long-term static hosting/CDN) ③ huawei-ecs ④ huawei-cce. Only follow an explicit target when the developer names one.
28
+
25
29
  ## Region Intent
26
30
 
27
31
  - Extract region intent from the user's words before querying. Examples: Singapore -> `ap-southeast-3` first, Hong Kong -> `ap-southeast-2`, Beijing -> `cn-north-4`, Shanghai -> `cn-east-3`.
@@ -26,7 +26,7 @@ Do not rely on training data for facts. Huawei Cloud services, pricing, quotas,
26
26
  |----|------|----------------|------------|
27
27
  | select | Service Selection | which service, what should I use, compare, recommend | Read references/select.md | select
28
28
  | compute | Compute Routing | server, VM, instance, ECS, BMS, GPU, HPC | Handoff to huawei-ecs or huawei-cce |
29
- | storage | Storage Routing | store files, bucket, OBS, backup, disk, EVS, SFS | Handoff to huawei-obs or huawei-cbr |
29
+ | storage | Storage Routing | store files, bucket, OBS, backup, disk, EVS, SFS (long-term storage/CDN intent only) | Handoff to huawei-obs or huawei-cbr |
30
30
  | database | Database Routing | database, SQL, NoSQL, cache, RDS, GaussDB, DDS, DCS | Handoff to huawei-rds or huawei-gaussdb |
31
31
  | network | Network Routing | VPC, subnet, security group, EIP, NAT, VPN | Handoff to huawei-vpc |
32
32
  | serverless | Serverless Routing | function, Lambda, serverless, FunctionGraph, API gateway | Handoff to huawei-functiongraph or huawei-apig |
@@ -36,7 +36,7 @@ Do not rely on training data for facts. Huawei Cloud services, pricing, quotas,
36
36
  | observability | Observability Routing | monitor, alarm, log, audit, trace, Cloud Eye | Handoff to huawei-cloud-eye or huawei-cts |
37
37
  | billing | Billing Routing | cost, bill, budget, spending, invoice | Handoff to huawei-billing |
38
38
  | iam | IAM Routing | permission, policy, role, user, group, AK/SK | Handoff to huawei-iam |
39
- | deployment | Deployment Routing | deploy, CI/CD, pipeline, release | CI/CD pipeline -> huawei-deployment; deploy a web app or a GitHub repo (no CI/CD target) -> huawei-sandbox |
39
+ | deployment | Deployment Routing | deploy, host, publish, CI/CD, pipeline, release | CI/CD pipeline -> huawei-deployment; deploy/host/preview a web app or static website (no CI/CD target) -> present deployment-target options first, see "Deployment Target Options" below |
40
40
  | sandbox | Sandbox Routing | sandbox, DevStation, workspace, terminal, preview, temporary runtime | Handoff to huawei-sandbox |
41
41
  | cli | CLI and Auth Routing | install hcloud, configure KooCLI, AK/SK setup | Handoff to huaweicloud-cli-and-auth |
42
42
  | safety | Safety Routing | is this safe, approve command, risk review | Handoff to huaweicloud-safety |
@@ -50,7 +50,7 @@ Do not rely on training data for facts. Huawei Cloud services, pricing, quotas,
50
50
  |----------|----------------|-------|
51
51
  | Web application hosting | ECS | huawei-ecs |
52
52
  | Containerized microservices | CCE | huawei-cce |
53
- | Static file storage / CDN | OBS | huawei-obs |
53
+ | Static file storage / CDN (long-term) | OBS | huawei-obs |
54
54
  | Relational database (MySQL/PG) | RDS | huawei-rds |
55
55
  | Distributed SQL database | GaussDB | huawei-gaussdb |
56
56
  | Document database (MongoDB API) | DDS | huawei-dds-dcs |
@@ -71,6 +71,21 @@ Do not rely on training data for facts. Huawei Cloud services, pricing, quotas,
71
71
  | Temporary runtime / web app preview | Sandbox (DevStation) | huawei-sandbox |
72
72
  | Getting started | Account setup | huaweicloud-cli-and-auth |
73
73
 
74
+ ## Deployment Target Options
75
+
76
+ When the intent is to **deploy, host, or preview a web app or static website** (no explicit target named), NEVER default to a single service such as OBS. Ask the developer to choose a target and present the options in this order:
77
+
78
+ | # | Option | When to choose | Skill |
79
+ |---|--------|----------------|-------|
80
+ | 1 | **Sandbox (DevStation) — recommended** | Temporary runtime, quick preview, demo; instant public URL | huawei-sandbox |
81
+ | 2 | OBS static website hosting | Long-term static hosting / CDN, no backend | huawei-obs |
82
+ | 3 | ECS | Needs a server runtime (backend, custom server) | huawei-ecs |
83
+ | 4 | CCE | Containerized workloads, scaling | huawei-cce |
84
+
85
+ - Recommended prompt: "这个项目是 Web 应用/静态网站,建议优先部署到沙箱(临时运行环境,立即可预览)。也可选择 OBS 静态托管、ECS 或 CCE,你想部署到哪里?"
86
+ - If the developer explicitly names a target (e.g. "deploy to OBS"), follow that target and hand off to the corresponding service skill.
87
+ - If the developer has no preference, recommend the sandbox first.
88
+
74
89
  ## Capability Sources
75
90
 
76
91
  - Huawei Cloud Skills: scenario workflows and recipes (check first)
@@ -3,7 +3,7 @@ import { join } from 'node:path';
3
3
  import { homedir } from 'node:os';
4
4
  import { spawnSync } from 'node:child_process';
5
5
 
6
- export const SUPPORTED_AGENT_TARGETS = ['opencode', 'codex', 'codex-desktop', 'codearts', 'workbuddy'];
6
+ export const SUPPORTED_AGENT_TARGETS = ['opencode', 'codex', 'codex-desktop', 'codearts', 'workbuddy', 'dsh'];
7
7
 
8
8
  function baseHome() {
9
9
  return process.env.HUAWEICLOUD_HOME || homedir();
@@ -71,6 +71,23 @@ function workbuddyRegistered() {
71
71
  return Boolean(cfg?.mcpServers?.['huaweicloud-devkit']);
72
72
  }
73
73
 
74
+ function dshRoot() {
75
+ return process.env.DSH_HOME || join(baseHome(), '.dsh');
76
+ }
77
+
78
+ function dshRegistered() {
79
+ const patchPath = join(dshRoot(), 'profiles', 'web', 'cordis.patch.yml');
80
+ if (!existsSync(patchPath)) return false;
81
+ try {
82
+ const patch = readFileSync(patchPath, 'utf8');
83
+ return patch.includes('id: mcp-huaweicloud')
84
+ && patch.includes("@deepseek-ai/dsh-mcp-client")
85
+ && patch.includes('serverName: huaweicloud');
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+
74
91
  export function getAgentRegistrationStatuses(target = 'all') {
75
92
  const requested = target === 'all' ? SUPPORTED_AGENT_TARGETS : [target];
76
93
  const result = { target, agents: {} };
@@ -81,6 +98,7 @@ export function getAgentRegistrationStatuses(target = 'all') {
81
98
  if (agent === 'codex') configured = codexCliRegistered();
82
99
  if (agent === 'codearts') configured = codeartsRegistered();
83
100
  if (agent === 'workbuddy') configured = workbuddyRegistered();
101
+ if (agent === 'dsh') configured = dshRegistered();
84
102
  result.agents[agent] = { configured };
85
103
  }
86
104
  return result;
@@ -0,0 +1,155 @@
1
+ import { getProxyUrlForTarget } from './proxy-config.mjs';
2
+
3
+ let cachedDispatcher = undefined;
4
+ let cachedDispatcherProxyUrl = null;
5
+
6
+ export async function getProxyDispatcher(targetUrl) {
7
+ const proxyUrl = getProxyUrlForTarget(targetUrl);
8
+ if (!proxyUrl) return undefined;
9
+
10
+ if (cachedDispatcher && cachedDispatcherProxyUrl === proxyUrl) {
11
+ return cachedDispatcher;
12
+ }
13
+
14
+ const { ProxyAgent } = await import('undici');
15
+ cachedDispatcher = new ProxyAgent(proxyUrl);
16
+ cachedDispatcherProxyUrl = proxyUrl;
17
+ return cachedDispatcher;
18
+ }
19
+
20
+ export function clearProxyDispatcherCache() {
21
+ cachedDispatcher = undefined;
22
+ cachedDispatcherProxyUrl = null;
23
+ }
24
+
25
+ export async function createProxyWebSocket(url, protocols) {
26
+ const proxyUrl = getProxyUrlForTarget(url);
27
+ if (!proxyUrl) {
28
+ return new globalThis.WebSocket(url, protocols);
29
+ }
30
+
31
+ const dispatcher = await getProxyDispatcher(url);
32
+ const { WebSocket: UndiciWebSocket } = await import('undici');
33
+
34
+ const wsOptions = { dispatcher };
35
+ if (protocols) {
36
+ if (Array.isArray(protocols)) {
37
+ wsOptions.protocols = protocols;
38
+ } else {
39
+ wsOptions.protocols = [protocols];
40
+ }
41
+ }
42
+
43
+ return new UndiciWebSocket(url, wsOptions);
44
+ }
45
+
46
+ export function getWebSocketImpl(targetUrl) {
47
+ const proxyUrl = getProxyUrlForTarget(targetUrl);
48
+ if (!proxyUrl) return globalThis.WebSocket;
49
+
50
+ return (url, protocols) => {
51
+ const proxyUrlForTarget = getProxyUrlForTarget(url);
52
+ if (!proxyUrlForTarget) {
53
+ return new globalThis.WebSocket(url, protocols);
54
+ }
55
+
56
+ const wsPromise = (async () => {
57
+ const dispatcher = await getProxyDispatcher(url);
58
+ const { WebSocket: UndiciWebSocket } = await import('undici');
59
+
60
+ const wsOptions = { dispatcher };
61
+ if (protocols) {
62
+ if (Array.isArray(protocols)) {
63
+ wsOptions.protocols = protocols;
64
+ } else {
65
+ wsOptions.protocols = [protocols];
66
+ }
67
+ }
68
+
69
+ return new UndiciWebSocket(url, wsOptions);
70
+ })();
71
+
72
+ return createSyncProxyWebSocketWrapper(wsPromise);
73
+ };
74
+ }
75
+
76
+ function createSyncProxyWebSocketWrapper(wsPromise) {
77
+ const pendingEvents = [];
78
+ const eventHandlers = new Map();
79
+ let ws = null;
80
+ let settled = false;
81
+ let readyState = 0;
82
+ let binaryType = 'arraybuffer';
83
+
84
+ const wrapper = {
85
+ get readyState() { return ws ? ws.readyState : readyState; },
86
+ get url() { return ws ? ws.url : ''; },
87
+ get protocol() { return ws ? ws.protocol : ''; },
88
+ get binaryType() { return ws ? ws.binaryType : binaryType; },
89
+ set binaryType(v) {
90
+ binaryType = v;
91
+ if (ws) ws.binaryType = v;
92
+ },
93
+
94
+ send(data, callback) {
95
+ if (ws) {
96
+ if (ws.send.length >= 2) return ws.send(data, callback);
97
+ try { ws.send(data); if (callback) callback(null); } catch (e) { if (callback) callback(e); }
98
+ return;
99
+ }
100
+ pendingEvents.push({ type: 'send', data, callback });
101
+ },
102
+
103
+ close(code, reason) {
104
+ if (ws) { ws.close(code, reason); return; }
105
+ readyState = 3;
106
+ pendingEvents.push({ type: 'close', code, reason });
107
+ },
108
+
109
+ addEventListener(type, handler) {
110
+ if (!eventHandlers.has(type)) eventHandlers.set(type, new Set());
111
+ eventHandlers.get(type).add(handler);
112
+ if (ws) ws.addEventListener(type, handler);
113
+ },
114
+
115
+ removeEventListener(type, handler) {
116
+ const handlers = eventHandlers.get(type);
117
+ if (handlers) handlers.delete(handler);
118
+ if (ws) ws.removeEventListener(type, handler);
119
+ },
120
+
121
+ on(type, handler) { wrapper.addEventListener(type, handler); },
122
+ off(type, handler) { wrapper.removeEventListener(type, handler); },
123
+ };
124
+
125
+ function flushPendingEvents() {
126
+ for (const evt of pendingEvents) {
127
+ if (evt.type === 'send') {
128
+ if (ws.send.length >= 2) ws.send(evt.data, evt.callback);
129
+ else { try { ws.send(evt.data); if (evt.callback) evt.callback(null); } catch (e) { if (evt.callback) evt.callback(e); } }
130
+ } else if (evt.type === 'close') {
131
+ ws.close(evt.code, evt.reason);
132
+ }
133
+ }
134
+ pendingEvents.length = 0;
135
+ }
136
+
137
+ wsPromise.then((resolvedWs) => {
138
+ ws = resolvedWs;
139
+ if ('binaryType' in ws) ws.binaryType = binaryType;
140
+ for (const [type, handlers] of eventHandlers) {
141
+ for (const handler of handlers) {
142
+ ws.addEventListener(type, handler);
143
+ }
144
+ }
145
+ flushPendingEvents();
146
+ }).catch((err) => {
147
+ readyState = 3;
148
+ const handlers = eventHandlers.get('error');
149
+ if (handlers) for (const h of handlers) h(err instanceof Error ? err : new Error(String(err)));
150
+ const closeHandlers = eventHandlers.get('close');
151
+ if (closeHandlers) for (const h of closeHandlers) h({ code: 1006, reason: err.message || String(err) });
152
+ });
153
+
154
+ return wrapper;
155
+ }
@@ -0,0 +1,87 @@
1
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+
5
+ function baseHome() {
6
+ return process.env.HUAWEICLOUD_HOME || homedir();
7
+ }
8
+
9
+ export function proxyConfigPath() {
10
+ return join(baseHome(), '.config', 'huaweicloud', 'proxy.json');
11
+ }
12
+
13
+ export function readProxyConfig() {
14
+ const path = proxyConfigPath();
15
+ if (!existsSync(path)) return null;
16
+ try {
17
+ return JSON.parse(readFileSync(path, 'utf8'));
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ export function writeProxyConfig(config = {}) {
24
+ const path = proxyConfigPath();
25
+ mkdirSync(dirname(path), { recursive: true });
26
+ const payload = {
27
+ https_proxy: String(config.https_proxy || ''),
28
+ http_proxy: String(config.http_proxy || ''),
29
+ no_proxy: String(config.no_proxy || ''),
30
+ };
31
+ writeFileSync(path, JSON.stringify(payload, null, 2), { encoding: 'utf8' });
32
+ return path;
33
+ }
34
+
35
+ export function clearProxyConfig() {
36
+ const path = proxyConfigPath();
37
+ if (!existsSync(path)) return false;
38
+ rmSync(path);
39
+ return true;
40
+ }
41
+
42
+ function shouldBypassProxy(hostname, noProxyList) {
43
+ if (!noProxyList.length) return false;
44
+ const lower = hostname.toLowerCase();
45
+ for (const pattern of noProxyList) {
46
+ const p = pattern.trim().toLowerCase();
47
+ if (!p) continue;
48
+ if (p === lower) return true;
49
+ if (p === '*') return true;
50
+ if (p.startsWith('.') && lower.endsWith(p)) return true;
51
+ if (lower.endsWith('.' + p)) return true;
52
+ }
53
+ return false;
54
+ }
55
+
56
+ export function getProxySettings(targetUrl) {
57
+ const envHttps = process.env.HTTPS_PROXY || process.env.https_proxy || '';
58
+ const envHttp = process.env.HTTP_PROXY || process.env.http_proxy || '';
59
+ const envNoProxy = process.env.NO_PROXY || process.env.no_proxy || '';
60
+
61
+ const fileConfig = readProxyConfig();
62
+
63
+ const https_proxy = envHttps || (fileConfig?.https_proxy || '');
64
+ const http_proxy = envHttp || (fileConfig?.http_proxy || '');
65
+ const no_proxy = envNoProxy || (fileConfig?.no_proxy || '');
66
+
67
+ if (!https_proxy && !http_proxy) return null;
68
+
69
+ const noProxyList = no_proxy.split(',').map((s) => s.trim()).filter(Boolean);
70
+
71
+ if (targetUrl) {
72
+ const parsed = new URL(targetUrl);
73
+ if (shouldBypassProxy(parsed.hostname, noProxyList)) return null;
74
+ const isHttps = parsed.protocol === 'https:';
75
+ const proxyUrl = isHttps ? https_proxy : http_proxy;
76
+ if (!proxyUrl) return null;
77
+ return { proxyUrl, noProxyList, targetProtocol: parsed.protocol };
78
+ }
79
+
80
+ return { https_proxy, http_proxy, no_proxy, noProxyList };
81
+ }
82
+
83
+ export function getProxyUrlForTarget(targetUrl) {
84
+ const settings = getProxySettings(targetUrl);
85
+ if (!settings) return null;
86
+ return settings.proxyUrl || null;
87
+ }
@@ -1,4 +1,5 @@
1
1
  import { getCredentials } from './hwlink-api.mjs';
2
+ import { getProxyDispatcher } from '../proxy/proxy-agent.mjs';
2
3
 
3
4
  const HDKIT_BASE_URL =
4
5
  process.env.HDKITSERVICE_ENDPOINT ||
@@ -22,12 +23,15 @@ async function hdkitRequest(method, path, body, timeoutMs = 300000) {
22
23
 
23
24
  let resp;
24
25
  try {
25
- resp = await fetch(url, {
26
+ const dispatcher = await getProxyDispatcher(url);
27
+ const fetchOpts = {
26
28
  method,
27
29
  headers,
28
30
  body: body ? JSON.stringify(body) : undefined,
29
31
  signal: controller.signal,
30
- });
32
+ };
33
+ if (dispatcher) fetchOpts.dispatcher = dispatcher;
34
+ resp = await fetch(url, fetchOpts);
31
35
  } finally {
32
36
  clearTimeout(timer);
33
37
  }
@@ -46,7 +50,7 @@ async function hdkitRequest(method, path, body, timeoutMs = 300000) {
46
50
  );
47
51
  err.code = data.code;
48
52
  err.status = resp.status;
49
- err.traceId = data.trace_id;
53
+ err.traceId = data.traceId; // 后端实际返回驼峰 traceId
50
54
  throw err;
51
55
  }
52
56