huaweicloud-devkit 1.1.5 → 1.1.6-next.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
@@ -4,7 +4,7 @@
4
4
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
5
5
  [![CI](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml/badge.svg)](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml)
6
6
  [![npm version](https://img.shields.io/npm/v/huaweicloud-devkit)](https://www.npmjs.com/package/huaweicloud-devkit)
7
- [![Beta](https://img.shields.io/badge/beta-v1.1.5-orange)](https://github.com/huaweicloud/huaweicloud-devkit)
7
+ [![Beta](https://img.shields.io/badge/beta-v1.1.6-orange)](https://github.com/huaweicloud/huaweicloud-devkit)
8
8
 
9
9
  **[中文](README.zh-CN.md) | English**
10
10
 
package/README.zh-CN.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
5
5
  [![CI](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml/badge.svg)](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml)
6
6
  [![npm version](https://img.shields.io/npm/v/huaweicloud-devkit)](https://www.npmjs.com/package/huaweicloud-devkit)
7
- [![Beta](https://img.shields.io/badge/beta-v1.1.5-orange)](https://github.com/huaweicloud/huaweicloud-devkit)
7
+ [![Beta](https://img.shields.io/badge/beta-v1.1.6-orange)](https://github.com/huaweicloud/huaweicloud-devkit)
8
8
 
9
9
  **中文 | [English](README.md)**
10
10
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
3
  "mcpName": "io.github.huaweicloud/huaweicloud-devkit",
4
- "version": "1.1.5",
4
+ "version": "1.1.6-next.1",
5
5
  "kooCliVersion": "7.2.12",
6
6
  "description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
7
7
  "type": "module",
@@ -17,7 +17,7 @@
17
17
  "mcpServers": "./.mcp.json",
18
18
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
19
19
  "skills": "./skills/",
20
- "version": "1.1.5",
20
+ "version": "1.1.6-next.1",
21
21
  "author": {
22
22
  "name": "HuaweiCloud Mate",
23
23
  "url": "https://github.com/huaweicloud"
@@ -20,7 +20,7 @@
20
20
  "mcpServers": "./.mcp.json",
21
21
  "description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
22
22
  "skills": "./skills/",
23
- "version": "1.1.5",
23
+ "version": "1.1.6-next.1",
24
24
  "author": {
25
25
  "name": "HuaweiCloud Mate",
26
26
  "url": "https://github.com/huaweicloud"
@@ -17,7 +17,7 @@
17
17
  "mcpServers": "./.mcp.json",
18
18
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
19
19
  "skills": "./skills/",
20
- "version": "1.1.5",
20
+ "version": "1.1.6-next.1",
21
21
  "author": {
22
22
  "name": "HuaweiCloud Mate",
23
23
  "url": "https://github.com/huaweicloud"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.1.5",
3
+ "version": "1.1.6-next.1",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.1.5",
3
+ "version": "1.1.6-next.1",
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",
@@ -2,7 +2,7 @@
2
2
  "name": "huaweicloud-devkit",
3
3
  "id": "huaweicloud-devkit",
4
4
  "displayName": "HuaweiCloud DevKit",
5
- "version": "1.1.5",
5
+ "version": "1.1.6-next.1",
6
6
  "family": "bundle-plugin",
7
7
  "bundleFormat": "codex",
8
8
  "description": "Guide coding agents to use Huawei Cloud safely — KooCLI, APIs, SDKs, 28 MCP tools, skills, and safety guardrails.",
@@ -18,8 +18,10 @@ Domain expertise for Huawei Cloud CloudDeploy. Covers application creation, depl
18
18
 
19
19
  | Trap | Why |
20
20
  | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
21
+ | Service name is `CodeArtsDeploy` | `hcloud CloudDeploy` returns `[USE_ERROR]不支持的服务名称`. The KooCLI service name is **CodeArtsDeploy** (product name CloudDeploy). Run `hcloud --help` to verify. |
22
+ | Wrong operation names fail | The per-action names historically assumed (Start/List/Create/Delete + `Task`/`Tasks`) do NOT exist — KooCLI returns `[USE_ERROR]不支持的operation`. Use the verified names in Common Workflows. |
23
+ | `project_id` is the CodeArts project ID | Not the IAM project. If CodeArts is not enabled for the account, calls fail with `Deploy.00016902 项目不存在`. |
21
24
  | **Flyway SQL dialect mismatch (H2 dev → MySQL prod)** | Spring Boot apps commonly develop with H2 in-memory DB, then deploy to RDS MySQL. Flyway migrations using H2-specific syntax (e.g. `DATEADD`, `CHARACTER_LENGTH`, `BOOLEAN`) silently succeed on H2 but fail on MySQL. Before deploying, audit `V*__*.sql` migration files: replace `DATEADD` with `DATE_ADD`, `BOOLEAN` with `TINYINT(1)`, remove `characterEncoding=utf8mb4` from Spring Boot datasource URL (KooCLI RDS CreateInstance sets charset at the instance level). Use `Flyway.validate-on-migrate=true` in CI to catch dialect issues early. |
22
- | Service name may be `CodeArtsDeploy` | hcloud service name for deployment may be `CodeArtsDeploy` instead of `CloudDeploy`. Run `hcloud --help` to verify |
23
25
  | Deployment hosts need agent | Install CloudDeploy agent on target hosts first |
24
26
  | Task must reference application first | Create application before task |
25
27
  | Artifact source defaults to OBS | Most deployment tasks pull artifacts from OBS. Ensure bucket and object exist |
@@ -27,22 +29,25 @@ Domain expertise for Huawei Cloud CloudDeploy. Covers application creation, depl
27
29
 
28
30
  ## Common Workflows
29
31
 
30
- | Task | Operation |
31
- | ---------------------- | ---------------------------------------------------------------- |
32
- | Create application | `CreateApp --name=<n> --platform=<p> --cli-region=<r>` |
33
- | Create deployment task | `CreateTask --name=<n> --app_id=<id> --artifact_source_type=OBS` |
34
- | Start deployment | `StartTask --task_id=<id>` |
35
- | List tasks | `ListTasks --app_id=<id>` |
36
- | Delete task | `DeleteTask --task_id=<id>` |
32
+ | Task | Operation(省略参数用 `<placeholder>`,参数以 `hcloud CodeArtsDeploy <Op> --help` 为准) |
33
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
34
+ | List applications | `ListAllApp --cli-region=<r> --project_id=<id> --page=1 --size=10`(`ListDeployTasks` 已由云侧于 2024-09-30 弃用,推荐本接口) |
35
+ | Show app detail | `ShowAppDetailById --cli-region=<r> --app_id=<id>` |
36
+ | Create application | `CreateApp --cli-region=<r> --name=<n> --create_type=template --project_id=<id> --is_draft=false`(`--create_type` 仅有 `template` 一个值) |
37
+ | Create deployment task | `CreateDeployTaskByTemplate --cli-region=<r> --template_id=<id> --task_name=<n>`(deprecated since 2024-09-30 — `--help` recommends `CreateApp`) |
38
+ | Start deployment | `StartDeployTask --cli-region=<r> --task_id=<id>` |
39
+ | Delete task | `DeleteDeployTask --cli-region=<r> --task_id=<id>`(deprecated since 2024-09-30 — `--help` recommends `DeleteApplication --app_id=<id>`) |
37
40
 
38
41
  ## Troubleshooting
39
42
 
40
- | Error | Fix |
41
- | ------------------ | ----------------------------------------------------------------------------- |
42
- | Agent offline | Check agent service on target host, network connectivity, firewall rules |
43
- | Deployment timeout | Check artifact size, increase task timeout, verify target host resources |
44
- | Artifact not found | Verify OBS bucket and object path, check artifact permissions |
45
- | Permission denied | Verify IAM roles for deployment: `CodeArtsDeploy FullAccess` or custom policy |
43
+ | Error | Fix |
44
+ | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
45
+ | Agent offline | Check agent service on target host, network connectivity, firewall rules |
46
+ | Deployment timeout | Check artifact size, increase task timeout, verify target host resources |
47
+ | Artifact not found | Verify OBS bucket and object path, check artifact permissions |
48
+ | Permission denied | Verify IAM roles for deployment: `CodeArtsDeploy FullAccess` or custom policy |
49
+ | `Deploy.00016902` 项目不存在 | CodeArts not enabled or wrong project type: use the **CodeArts project ID** (found in CodeArts console), not the IAM project ID; enable CodeArts first |
50
+ | `APIGW.0301` Incorrect IAM authentication information | Credentials lack CodeArtsDeploy access — use AK/SK with CodeArtsDeploy permissions (STS scoped credentials may be rejected) |
46
51
 
47
52
  ## Security
48
53
 
@@ -222,29 +222,84 @@ Expose a deployed web app to a public URL and return that URL to the developer.
222
222
 
223
223
  **User-facing language rule**: the tunnel mechanism below is an internal implementation detail. Never mention "DevBridge", "tunnel", or the tunnel CLI to the developer. Tell the developer only: "正在生成访问地址..." while exposing, then hand over the final URL.
224
224
 
225
- **Install** (internal tooling; skip if `devbridge version` already works):
225
+ ### Step 0: Ensure DevBridge CLI 0.2.x (install or upgrade in place)
226
+
227
+ **Check the version first.** Sandboxes created before Sep 2026 ship devbridge 0.1.13, whose hardcoded gateway (`cn-north-4-bridge.myhuaweicloud.com`) was migrated and now serves a 「服务已迁移」 placeholder page — 0.1.x can no longer connect:
226
228
 
227
229
  ```bash
228
- if command -v devbridge >/dev/null 2>&1; then
229
- echo "devbridge already installed"
230
- else
231
- curl -fsSL https://res-hd.hc-cdn.cn/sharedata/hdspace/devbridge/install.sh | bash
230
+ export PATH="$HOME/.huawei/bin:$PATH" # PREPEND — appending lets a stale image binary in /usr/local/bin win
231
+ devbridge version 2>/dev/null || echo "not installed"
232
+ ```
233
+
234
+ - `0.2.x-release` → continue to Step 1.
235
+ - `0.1.x-release` or not installed → upgrade in place (replaces the binary — keep NO second copy):
236
+
237
+ ```bash
238
+ # Official installer (GitHub can be slow/flaky from the sandbox — force HTTP/1.1 and retry all errors)
239
+ curl -fsSL --http1.1 --retry 3 --retry-all-errors --max-time 120 \
240
+ https://github.com/huaweicloud/devspace-devbridge/releases/latest/download/install.sh \
241
+ -o /tmp/devbridge-install.sh
242
+
243
+ # Preferred: install via the GitCode mirror (domestic CDN, much faster).
244
+ # Extract the version baked into the installer, then point -u at the matching GitCode tag.
245
+ # Note: GitCode repo is named "devbrige" (missing 'd') — this is the actual repo name, not a typo.
246
+ # GitHub repo is "devspace-devbridge" (correct spelling). Do NOT "fix" the GitCode URL.
247
+ DB_TARGET=$(grep -m1 'DEFAULT_VERSION=' /tmp/devbridge-install.sh | cut -d'"' -f2)
248
+ if ! bash /tmp/devbridge-install.sh -s -u "https://gitcode.com/CloudDeveloperDepartment/devbrige/releases/download/${DB_TARGET}" -v "${DB_TARGET}"; then
249
+ bash /tmp/devbridge-install.sh -s # fallback: GitHub (baked-in URL)
250
+ fi
251
+ export PATH="$HOME/.huawei/bin:$PATH" # PREPEND (session shells do not re-source ~/.bashrc)
252
+ devbridge version # must print 0.2.x-release
253
+
254
+ # Remove the stale image-installed binary so only ONE version remains.
255
+ # (Image sandboxes ship 0.1.x at /usr/local/bin/devbridge; the installer writes ~/.huawei/bin.
256
+ # Compare against the explicit ~/.huawei/bin path — never `command -v`, which can resolve
257
+ # to the stale binary itself when PATH order differs.)
258
+ if [ -x "$HOME/.huawei/bin/devbridge" ] && [ -f /usr/local/bin/devbridge ] && [ "$(readlink -f /usr/local/bin/devbridge)" != "$(readlink -f "$HOME/.huawei/bin/devbridge")" ]; then
259
+ rm -f /usr/local/bin/devbridge && echo "stale /usr/local/bin/devbridge removed"
232
260
  fi
233
- export PATH=$PATH:$HOME/.huawei/bin # installer only writes ~/.bashrc; session shells do not re-source it
234
261
  ```
235
262
 
236
- **Login** (non-interactive; credentials from `huaweicloud_sandbox_credentials` are available via `/tmp/hw_creds.sh`). If `source /tmp/hw_creds.sh` returns empty, the credentials injection has expired (sandbox session reconnection resets them) — re-run `huaweicloud_sandbox_credentials` first:
263
+ - `-s` (silent) is **required** in the sandbox — without it the installer blocks reading `/dev/tty`.
264
+ - **Old tunnels do not survive the upgrade**: 0.1.x-created tunnels are not registered on the s2 gateway — always rebuild the tunnel after upgrading (the expose flow below does this via pre-cleanup).
265
+ - If `10006: quota exceeded` appears after upgrading, stale pre-upgrade tunnels still count against the quota — `devbridge delete-all` and retry.
266
+
267
+ ### Step 1: Authenticate with an API Key (0.2.x removed AK/SK login)
268
+
269
+ devbridge 0.2.x removed `--access-key/--secret-key/--huaweicloud`. The only non-interactive auth is a DevBridge API Key. AK/SK (`/tmp/hw_creds.sh`) remains valid for hcloud, but not for devbridge.
270
+
271
+ **Check, then login if the key exists** (the API Key is a long-lived account-level credential stored in its own file `/tmp/hw_api_key`, separate from the temporary AK/SK in `/tmp/hw_creds.sh` — never echo its value):
237
272
 
238
273
  ```bash
239
274
  source /tmp/hw_creds.sh 2>/dev/null
240
- devbridge auth login --huaweicloud --access-key "$HW_ACCESS_KEY" --secret-key "$HW_SECRET_KEY"
275
+ source /tmp/hw_api_key 2>/dev/null
276
+ if [ -n "$HW_API_KEY" ]; then
277
+ devbridge auth login --api-key "$HW_API_KEY" && devbridge auth status
278
+ else
279
+ echo "NO_API_KEY"
280
+ fi
241
281
  ```
242
282
 
243
- - The `--huaweicloud` flag is required for AK/SK login; without it the CLI tries an interactive browser login, which fails in the sandbox.
244
- - Credentials are stored in `/tmp/hw_creds.sh` (chmod 600) — source it before login, never echo the values.
245
- - Verify with `devbridge auth status`. If `$HW_ACCESS_KEY` is empty, ensure `huaweicloud_sandbox_credentials` was called first.
283
+ **If `NO_API_KEY`** — STOP and guide the developer through creating one (wait for the key before continuing):
284
+
285
+ 1. **Why (one sentence)**: "沙箱的地址生成服务已升级到 0.2.x,新版本仅支持 API Key 登录(官方变更)。API Key 按账号管理,创建一次长期可用、所有沙箱通用。"
286
+ 2. **Where (exact steps)**: open https://devstation.connect.huaweicloud.com/space/devbridge/apikey → 登录控制台 → 选择 DevBridge 场景 → 点击"创建"。**完整值仅在创建时展示一次,立即复制**(`devbridge_` 开头)。
287
+ 3. **How to hand it over** (present both, recommend the first):
288
+ - **Recommended**: 在本地终端执行 `export HW_API_KEY=<粘贴Key>`(或写入 shell profile),然后告知"已设置" — Key 不经过对话记录;随后 agent 重新调用 `huaweicloud_sandbox_credentials`(本地 `HW_API_KEY` 会被自动透传注入沙箱)。
289
+ - **Alternative**: 直接把 Key 粘贴在对话中 — agent 通过 `huaweicloud_sandbox_credentials` 的 `api_key` 参数注入,**绝不回显、不复述、不写入日志**。
290
+ 4. **Security**: 不要提交到代码库或分享;怀疑泄露时在同页面删除并重建(1 分钟)。
246
291
 
247
- **Expose** (run the web server and the tunnel in the background, then read the URL from the log; the app lives in the workspace mount, e.g. `/workspace/<repo-name>`):
292
+ **Failure paths**:
293
+
294
+ | Symptom | Guidance |
295
+ | -------------------------------------- | ---------------------------------------------------- |
296
+ | `auth login` returns 401/403 | Key 已被删除或禁用 — 引导在同页面删除旧 Key 重新创建 |
297
+ | 无法访问 API Key 管理页面 | 账号无该页面权限 — 引导联系账号管理员处理 |
298
+ | Key 带空格/换行/缺失 `devbridge_` 前缀 | 引导重新完整复制粘贴 |
299
+
300
+ ### Step 2: Expose
301
+
302
+ Run the web server and the tunnel in the background, then read the URL from the log; the app lives in the workspace mount, e.g. `/workspace/<repo-name>`:
248
303
 
249
304
  ```bash
250
305
  # 0. Pre-cleanup: kill old processes and stale tunnels
@@ -273,13 +328,15 @@ else
273
328
  fi
274
329
  ```
275
330
 
276
- **Never return a tunnel URL without verifying it first** — a stale URL (from a killed tunnel process) will silently fail. Always curl-check before giving the URL to the developer.
331
+ **Never return a tunnel URL without verifying it first** — a stale URL (from a killed tunnel process) will silently fail. Always curl-check before giving the URL to the developer. Note: a migrated gateway serves its placeholder page with **HTTP 200** — a bare status check is not enough when diagnosing "200 but wrong content".
277
332
 
278
333
  **Quota recovery**: if the tunnel creation fails with `10006: quota exceeded`:
279
334
 
280
335
  ```bash
281
336
  # Step A: List all tunnels (both active and stale)
282
- devbridge list -j
337
+ # 0.2.x: `devbridge list` (plain table; data rows start with the 8-char tunnel ID)
338
+ # 0.1.x (legacy): `devbridge list -j` (JSON)
339
+ devbridge list
283
340
  # Step B: Remove all stale tunnels
284
341
  devbridge delete-all
285
342
  # Step C: Retry tunnel creation
@@ -289,10 +346,10 @@ sleep 10 && cat /tmp/host.log
289
346
 
290
347
  This eliminates the most common deployment failure — historical tunnels from previous sessions accumulating past the max=10 quota.
291
348
 
292
- - The public URL has the form `https://<id>-<port>.cn-north-4-bridge.myhuaweicloud.com` (from the `Tunnel URL:` line).
349
+ - The public URL has the form `https://<id>-<port>.devbridge-s2.hwtunnel.com` (from the `Tunnel URL:` line; 8-char lowercase base32 id). The pre-migration bridge domain `cn-north-4-bridge.myhuaweicloud.com` is dead — treat any URL on it as broken and never hand one to the developer.
293
350
  - **Return this URL to the developer as the deployment result link.** Keep the host process running (do not close the session before handing over the URL).
294
351
  - Tunnel `description` (`-d`) accepts only Chinese characters, letters, and digits (0-64). Symbols such as `-`/`_`/spaces are rejected (`Invalid tunnel description`).
295
- - Internal docs: https://huaweicloud.github.io/devspace-devbridge/
352
+ - Official docs: https://huaweicloud.github.io/devspace-devbridge/
296
353
 
297
354
  **No local downgrade**: if the tunnel tooling cannot be installed in the sandbox, STOP and report a generic error ("无法生成访问地址") without technical detail. Never install it on the developer's local machine — a local install would defeat the purpose of sandbox deployment.
298
355
 
@@ -446,7 +503,7 @@ For each check, parse the output: if stdout contains `MISSING:` or the tool wasn
446
503
  | pnpm | `npm i -g pnpm` | Same |
447
504
  | yarn | `npm i -g yarn` | Same |
448
505
  | Hugo | `curl -fsSL https://github.com/gohugoio/hugo/releases/download/v0.140.0/hugo_extended_0.140.0_linux-amd64.tar.gz -o /tmp/hugo.tar.gz && sudo tar -xzf /tmp/hugo.tar.gz -C /usr/local/bin hugo && rm /tmp/hugo.tar.gz` | Same |
449
- | DevBridge | `curl -fsSL https://res-hd.hc-cdn.cn/sharedata/hdspace/devbridge/install.sh \| bash && export PATH=$PATH:$HOME/.huawei/bin` | Same |
506
+ | DevBridge | Follow [Step 0 of "Expose the deployed app"](#step-0-ensure-devbridge-cli-02x-install-or-upgrade-in-place) — checks version, installs or upgrades in place via the official installer | Same |
450
507
 
451
508
  **If Node.js is missing**, install it first — all build workflows depend on it. Stop and report to the developer if Node.js installation fails.
452
509
 
@@ -782,13 +839,15 @@ If the status code is not 2xx/3xx:
782
839
 
783
840
  ### Step 7: Expose via DevBridge [REQUIRED — deployment incomplete without this]
784
841
 
842
+ When `deploy_check` returns `nextStep: expose_via_devbridge`, it also returns an executable `remediation` string — run those devbridge commands in the sandbox (after `source /tmp/hw_creds.sh`). Until a tunnel exists, `deploy_check` returns no `publicUrl`; do not fabricate a URL.
843
+
785
844
  Follow the standard [Expose the deployed app](#expose-the-deployed-app-public-url) procedure. The app is already running on the detected port — only DevBridge tunnel setup is needed.
786
845
 
787
846
  Use `exec_with_session` to background DevBridge. For SSR, DevBridge tunnels the nginx public port (not the Node port directly).
788
847
 
789
- **Pre-flight**: always run `devbridge delete-all` before creating a new tunnel to prevent `10006: quota exceeded` from accumulated stale tunnels. If you still get quota error, list tunnels with `devbridge list -j`, delete stale ones, and retry.
848
+ **Pre-flight**: always run `devbridge delete-all` before creating a new tunnel to prevent `10006: quota exceeded` from accumulated stale tunnels. If you still get quota error, list tunnels with `devbridge list` (0.2.x plain table; 0.1.x legacy `-j` JSON), delete stale ones, and retry.
790
849
 
791
- Extract the tunnel URL from DevBridge output. The public URL has the form `https://<id>-<port>.cn-north-4-bridge.myhuaweicloud.com`. **Return this URL to the developer as the deployment result.**
850
+ Extract the tunnel URL from DevBridge output. The public URL has the form `https://<id>-<port>.devbridge-s2.hwtunnel.com`. **Return this URL to the developer as the deployment result.**
792
851
 
793
852
  #### Cross-platform H5 QR code
794
853
 
@@ -830,8 +889,8 @@ The tool checks:
830
889
 
831
890
  - **nginx_serving** — nginx responds with 2xx/3xx on the app port
832
891
  - **output_dir** — build output directory exists and is non-empty
833
- - **devbridge_tunnel** — DevBridge tunnel is active
834
- - **tunnel_url_accessible** — tunnel URL returns 200/304
892
+ - **devbridge_tunnel** — DevBridge tunnel is active (version-aware: 0.1.x JSON / 0.2.x table)
893
+ - **tunnel_url_accessible** — tunnel URL returns 200/304 with real content; a gateway migration placeholder page (「服务已迁移」 with HTTP 200) counts as FAIL
835
894
  - **qr_code** (cross-platform only) — QR image exists in output dir
836
895
 
837
896
  Returns `complete: true/false`, `score`, and `nextStep` to fix missing items.
@@ -854,32 +913,36 @@ Returns `complete: true/false`, `score`, and `nextStep` to fix missing items.
854
913
 
855
914
  ## Critical Warnings
856
915
 
857
- | Trap | Why |
858
- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
859
- | Target not confirmed | "部署到华为云" without a named target is NOT a go signal. You MUST run the Target-Selection Gate and get an explicit choice before calling any sandbox lifecycle tool. Skipping it and defaulting to the sandbox is a violation. |
860
- | 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 |
861
- | Real-name required | `sandbox_connect` fails if `realnameVerified=false`; tell the developer once and stop, don't loop on connect |
862
- | Never expose tunnel details | Do not mention "DevBridge"/"tunnel"/"devbridge" to the developer — say "正在生成访问地址..." and hand over only the URL |
863
- | Login needs `--huaweicloud` | `devbridge auth login --access-key/--secret-key` without `--huaweicloud` falls back to interactive browser login, which fails in the sandbox |
864
- | CLI PATH | The installer only writes `~/.bashrc`; run `export PATH=$PATH:$HOME/.huawei/bin` in the session before using `devbridge` |
865
- | Never install tunnel tooling locally | If the sandbox cannot install it, report a generic error and stop — installing on the developer's machine defeats sandbox deployment |
866
- | Return the deployment URL | Always hand the public URL from the host log to the developer as the final result |
867
- | Deploy is not just nginx | Configuring nginx does NOT complete the deployment. Steps 7 (DevBridge expose) and deploy_check are REQUIRED — `deploy_nginx` returns `nextStep: expose_via_devbridge` as a reminder. Do not stop after nginx. |
868
- | Call deploy_check before success | Always call `huaweicloud_sandbox_deploy_check` before reporting deployment success. A green nginx status does not mean the tunnel is accessible — verify end-to-end with the tool. |
869
- | Session state persists | `exec_with_session` preserves `cd`, env vars, aliases between calls |
870
- | Long commands prefer one-shot | `exec_one_shot` creates a fresh connection per call — more stable for builds, installs, and scripts >30s. See [Tool Selection Guide](#tool-selection-guide). |
871
- | SSR nginx/Node ports must differ | nginx `proxy_pass` targets `<nodePort>`, not `<port>`. `deploy_nginx` auto-defaults `nodePort` to `<port>+1` — always start the Node process with `PORT=<nodePort>` to match. Same-port = EADDRINUSE. |
872
- | HTTP 200 ≠ correct content | A green HTTP check does not guarantee the right project is serving — old processes from a previous session bound to the same port will still return 200. `deploy_check` verifies the deployment fingerprint to catch this. |
873
- | Destructive commands blocked | `rm -rf /`, `mkfs`, `dd if=`, fork bombs are denied by safety policy |
874
- | Workspace ID = dev_stage_id | Use `dev_stage_id` from `sandbox_connect` as `workspace_id` for terminal exec |
875
- | Projects live in `/workspace` | Clone/install project code under `/workspace/<repo-name>` (filesystem-root workspace mount, not `$HOME/workspace`), never in `/tmp` — ephemeral locations lose the project when the sandbox session restarts |
876
- | Upload project for local code | Use `sandbox_upload_project` to transfer local projects — packages as tar.gz, uploads via HTTP tunnel, extracts on sandbox. Much faster than base64 for multi-file projects |
877
- | Upload file for single files | Use `sandbox_upload_file` for individual files — base64 chunked, reliable for small files (<1MB) |
878
- | Node.js >= 22 required | Sandbox terminal uses built-in WebSocket (globalThis.WebSocket); if Node.js is missing, install it from the Huawei Cloud mirror (see "Node.js in the sandbox") |
879
- | Sandbox restart kills processes | After sandbox restarts, all user processes (nginx, Node.js, Python servers) are stopped. Re-run startup commands and verify ports are listening before proceeding. |
880
- | Cross-platform binaries incompatible | The sandbox runs Linux. Native binaries built on Windows/macOS (e.g., Prisma client, `node_modules/.prisma/`, platform-specific native addons) will not execute. Always install and build dependencies inside the sandbox, not locally. |
881
- | Cross-platform needs QR code | When `detect_framework` returns `type: "cross-platform"` (Taro, uni-app), generating a QR code image is **mandatory** — the deployment is incomplete without it. Check the Deployment Completion Check table in Step 7. |
882
- | Build fails do NOT auto-fix | When a build exits with non-zero exit code, STOP and present the error + fix options to the developer. Do not silently retry, modify configs, or change source files without explicit approval. See 4c-aux. |
916
+ | Trap | Why |
917
+ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
918
+ | Target not confirmed | "部署到华为云" without a named target is NOT a go signal. You MUST run the Target-Selection Gate and get an explicit choice before calling any sandbox lifecycle tool. Skipping it and defaulting to the sandbox is a violation. |
919
+ | 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 |
920
+ | Real-name required | `sandbox_connect` fails if `realnameVerified=false`; tell the developer once and stop, don't loop on connect |
921
+ | Never expose tunnel details | Do not mention "DevBridge"/"tunnel"/"devbridge" to the developer — say "正在生成访问地址..." and hand over only the URL |
922
+ | devbridge 0.2.x needs an API Key | 0.2.x removed AK/SK login (`--access-key/--secret-key/--huaweicloud` are gone). Login with `--api-key "$HW_API_KEY"` from `/tmp/hw_api_key` (long-lived credential, stored separately from `/tmp/hw_creds.sh`). If missing, guide the developer to create one (see Step 1 of "Expose the deployed app") |
923
+ | devbridge 0.1.x is dead | Sandboxes created before Sep 2026 ship 0.1.13, which connects to a migrated gateway serving a 「服务已迁移」 placeholder with HTTP 200. Check `devbridge version` first and upgrade in place (Step 0) — old tunnels never survive the upgrade |
924
+ | CLI PATH | The installer only writes `~/.bashrc`; run `export PATH="$HOME/.huawei/bin:$PATH"` (prepend) in the session before using `devbridge` — appending lets a stale image binary in `/usr/local/bin` win |
925
+ | Never install tunnel tooling locally | If the sandbox cannot install it, report a generic error and stop — installing on the developer's machine defeats sandbox deployment |
926
+ | Return the deployment URL | Always hand the public URL from the host log to the developer as the final result |
927
+ | Deploy is not just nginx | Configuring nginx does NOT complete the deployment. Steps 7 (DevBridge expose) and deploy_check are REQUIRED — `deploy_nginx` returns `nextStep: expose_via_devbridge` as a reminder. Do not stop after nginx. |
928
+ | Call deploy_check before success | Always call `huaweicloud_sandbox_deploy_check` before reporting deployment success. A green nginx status does not mean the tunnel is accessible — verify end-to-end with the tool. |
929
+ | Session state persists | `exec_with_session` preserves `cd`, env vars, aliases between calls |
930
+ | Long commands prefer one-shot | `exec_one_shot` creates a fresh connection per call — more stable for builds, installs, and scripts >30s. See [Tool Selection Guide](#tool-selection-guide). |
931
+ | SSR nginx/Node ports must differ | nginx `proxy_pass` targets `<nodePort>`, not `<port>`. `deploy_nginx` auto-defaults `nodePort` to `<port>+1` — always start the Node process with `PORT=<nodePort>` to match. Same-port = EADDRINUSE. |
932
+ | HTTP 200 ≠ correct content | A green HTTP check does not guarantee the right project is serving — old processes from a previous session bound to the same port will still return 200. `deploy_check` verifies the deployment fingerprint to catch this. |
933
+ | Destructive commands blocked | `rm -rf /`, `mkfs`, `dd if=`, fork bombs are denied by safety policy |
934
+ | Workspace ID = dev_stage_id | Use `dev_stage_id` from `sandbox_connect` as `workspace_id` for terminal exec |
935
+ | Projects live in `/workspace` | Clone/install project code under `/workspace/<repo-name>` (filesystem-root workspace mount, not `$HOME/workspace`), never in `/tmp` — ephemeral locations lose the project when the sandbox session restarts |
936
+ | Upload project for local code | Use `sandbox_upload_project` to transfer local projects — packages as tar.gz, uploads via HTTP tunnel, extracts on sandbox. Much faster than base64 for multi-file projects |
937
+ | Upload file for single files | Use `sandbox_upload_file` for individual files — base64 chunked, reliable for small files (<1MB) |
938
+ | Node.js >= 22 required | Sandbox terminal uses built-in WebSocket (globalThis.WebSocket); if Node.js is missing, install it from the Huawei Cloud mirror (see "Node.js in the sandbox") |
939
+ | Sandbox restart kills processes | After sandbox restarts, all user processes (nginx, Node.js, Python servers) are stopped. Re-run startup commands and verify ports are listening before proceeding. |
940
+ | Cross-platform binaries incompatible | The sandbox runs Linux. Native binaries built on Windows/macOS (e.g., Prisma client, `node_modules/.prisma/`, platform-specific native addons) will not execute. Always install and build dependencies inside the sandbox, not locally. |
941
+ | Cross-platform needs QR code | When `detect_framework` returns `type: "cross-platform"` (Taro, uni-app), generating a QR code image is **mandatory** — the deployment is incomplete without it. Check the Deployment Completion Check table in Step 7. |
942
+ | Build fails do NOT auto-fix | When a build exits with non-zero exit code, STOP and present the error + fix options to the developer. Do not silently retry, modify configs, or change source files without explicit approval. See 4c-aux. |
943
+ | Tunnel description: no spaces/hyphens | `devbridge create <name> -d 'hello world'` fails with `Invalid tunnel description: only Chinese characters, digits, letters, length 0-64`. The message is misleading — the real rule is no spaces/hyphens. Omit `-d` or use bare letters/digits. |
944
+ | Use `host`, never `connect` | `devbridge connect` is the sender side (for on-prem machines dialing out). For public preview it loops `Connection failed, retrying...` forever with no hint. Always use `devbridge host <tunnelId> -p <port>`. |
945
+ | Port drift detaches the tunnel | When deploy_nginx auto-increments to a free port, an existing tunnel stays bound to the old port — re-bind: `devbridge port create <tunnelId> -p <newPort> --protocol http -a` and restart `devbridge host`. deploy_nginx emits a warning when this happens. |
883
946
 
884
947
  ## Node.js in the sandbox
885
948
 
@@ -896,11 +959,12 @@ node --version
896
959
 
897
960
  ## Environment Variables
898
961
 
899
- | Variable | Required | Description |
900
- | ----------------------- | -------- | --------------------------------------------------------------- |
901
- | `HW_ACCESS_KEY` | Yes | Huawei Cloud AK |
902
- | `HW_SECRET_KEY` | Yes | Huawei Cloud SK |
903
- | `HW_SECURITY_TOKEN` | No | STS security token |
904
- | `HW_WORKSPACE_ID` | No | Default workspace ID |
905
- | `HDKITSERVICE_ENDPOINT` | No | hdkitservice API endpoint (default: devkit.huaweicloud.com) |
906
- | `HWLINK_ENDPOINT` | No | DevStation API endpoint (default: devstation.myhuaweicloud.com) |
962
+ | Variable | Required | Description |
963
+ | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
964
+ | `HW_ACCESS_KEY` | Yes | Huawei Cloud AK |
965
+ | `HW_SECRET_KEY` | Yes | Huawei Cloud SK |
966
+ | `HW_SECURITY_TOKEN` | No | STS security token |
967
+ | `HW_API_KEY` | No | DevBridge API Key (`devbridge_...`) — required for devbridge 0.2.x tunnel login; injected into the sandbox via `huaweicloud_sandbox_credentials` (`api_key` param or local env) |
968
+ | `HW_WORKSPACE_ID` | No | Default workspace ID |
969
+ | `HDKITSERVICE_ENDPOINT` | No | hdkitservice API endpoint (default: devkit.huaweicloud.com) |
970
+ | `HWLINK_ENDPOINT` | No | DevStation API endpoint (default: devstation.myhuaweicloud.com) |
@@ -75,10 +75,10 @@ These tools should be available in the sandbox image. Verify with the pre-flight
75
75
 
76
76
  Tools that may need installation in the sandbox before building:
77
77
 
78
- | Tool | Trigger | Install Command |
79
- | --------- | ------------------------------------ | ------------------------------------------------------------------------------------ |
80
- | yarn | `yarn.lock` detected | `npm i -g yarn` |
81
- | Hugo | `hugo.toml` / `config.toml` detected | Download Hugo extended binary (see below) |
82
- | DevBridge | Tunnel exposure step | `curl -fsSL https://res-hd.hc-cdn.cn/sharedata/hdspace/devbridge/install.sh \| bash` |
78
+ | Tool | Trigger | Install Command |
79
+ | --------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------- |
80
+ | yarn | `yarn.lock` detected | `npm i -g yarn` |
81
+ | Hugo | `hugo.toml` / `config.toml` detected | Download Hugo extended binary (see below) |
82
+ | DevBridge | Tunnel exposure step | Official installer with version check + in-place upgrade (see SKILL.md "Expose the deployed app" Step 0) |
83
83
 
84
84
  > Hugo download: `curl -fsSL https://github.com/gohugoio/hugo/releases/download/v0.140.0/hugo_extended_0.140.0_linux-amd64.tar.gz -o /tmp/hugo.tar.gz && sudo tar -xzf /tmp/hugo.tar.gz -C /usr/local/bin hugo && rm /tmp/hugo.tar.gz`
@@ -1,25 +1,9 @@
1
- import { readFileSync } from 'node:fs';
2
- import { resolve, dirname, join } from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
-
5
1
  import { TOOL_DEFINITIONS, callTool } from './tools.mjs';
6
- import { peekCachedUpdateInfo, applyUpdateHint } from './update-check.mjs';
2
+ import { peekCachedUpdateInfo, applyUpdateHint, readInstalledVersion } from './update-check.mjs';
7
3
  import { initTelemetry } from './telemetry/telemetry.mjs';
8
4
  import { detectAgent } from './telemetry/agent-detect.mjs';
9
5
 
10
- const __dirname = dirname(fileURLToPath(import.meta.url));
11
- const pluginRoot = resolve(__dirname, '..');
12
- const packageRoot = resolve(pluginRoot, '..', '..');
13
- let pkgVersion = '0.0.0';
14
- for (const base of [pluginRoot, packageRoot]) {
15
- try {
16
- const version = JSON.parse(readFileSync(join(base, 'package.json'), 'utf8')).version;
17
- if (version) {
18
- pkgVersion = version;
19
- break;
20
- }
21
- } catch {}
22
- }
6
+ const pkgVersion = readInstalledVersion() || '0.0.0';
23
7
 
24
8
  // 会话内首个非 check/upgrade 工具调用附加 _updateInfo,只消费一次。
25
9
  // 按会话隔离:同进程内不同会话(A/B)各自首次提示;stdio 用固定 'stdin'。
@@ -75,6 +59,24 @@ export async function dispatch(method, params, opts = {}) {
75
59
  }
76
60
 
77
61
  if (method === 'tools/call') {
62
+ const tool = TOOL_DEFINITIONS.find((t) => t.name === params.name);
63
+ if (!tool) {
64
+ // JSON-RPC 2.0: an unknown tool name is a client-side parameter error,
65
+ // not a server fault (#704 D9-2).
66
+ const unknownToolError = new Error(`Unknown tool: ${params.name}`);
67
+ unknownToolError.code = -32602;
68
+ throw unknownToolError;
69
+ }
70
+ const missing = (tool.inputSchema?.required || []).filter(
71
+ (key) => !params.arguments || !Object.hasOwn(params.arguments, key),
72
+ );
73
+ if (missing.length > 0) {
74
+ const invalidParamsError = new Error(
75
+ `Invalid params: missing required field(s) ${missing.map((key) => JSON.stringify(key)).join(', ')} for tool "${params.name}".`,
76
+ );
77
+ invalidParamsError.code = -32602;
78
+ throw invalidParamsError;
79
+ }
78
80
  const result = await callTool(params.name, params.arguments || {});
79
81
  const decorated = _decorateResult(sessionId, params.name, result);
80
82
  return {
@@ -128,7 +128,13 @@ function runStdioServer() {
128
128
  useContentLengthFraming = false;
129
129
  const line = buffer.subarray(0, lf).toString('utf8').trim();
130
130
  buffer = buffer.subarray(lf + 1);
131
- if (line) void handleMessage(JSON.parse(line));
131
+ if (line) {
132
+ try {
133
+ void handleMessage(JSON.parse(line));
134
+ } catch {
135
+ writeParseError();
136
+ }
137
+ }
132
138
  continue;
133
139
  }
134
140
 
@@ -149,11 +155,21 @@ function runStdioServer() {
149
155
  if (buffer.length < bodyEnd) return false;
150
156
  const body = buffer.subarray(bodyStart, bodyEnd).toString('utf8');
151
157
  buffer = buffer.subarray(bodyEnd);
152
- void handleMessage(JSON.parse(body));
158
+ try {
159
+ void handleMessage(JSON.parse(body));
160
+ } catch {
161
+ writeParseError();
162
+ }
153
163
  return true;
154
164
  }
155
165
 
156
166
  async function handleMessage(message) {
167
+ // Valid JSON that is not an object (null / array / string) is an invalid
168
+ // request per JSON-RPC 2.0 — reply -32600 rather than silently dropping it.
169
+ if (!message || typeof message !== 'object' || Array.isArray(message)) {
170
+ writeJsonRpcError(-32600, 'Invalid Request');
171
+ return;
172
+ }
157
173
  if (!Object.hasOwn(message, 'id')) {
158
174
  if (message.method === 'notifications/initialized') return;
159
175
  return;
@@ -181,4 +197,16 @@ function runStdioServer() {
181
197
  stdout.write(json + '\n');
182
198
  }
183
199
  }
200
+
201
+ function writeJsonRpcError(code, message) {
202
+ writeMessage({
203
+ jsonrpc: '2.0',
204
+ id: null,
205
+ error: { code, message },
206
+ });
207
+ }
208
+
209
+ function writeParseError() {
210
+ writeJsonRpcError(-32700, 'Parse error');
211
+ }
184
212
  }