huaweicloud-devkit 1.1.13-next.3 → 1.1.13-next.5

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/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.13-next.3",
4
+ "version": "1.1.13-next.5",
5
5
  "kooCliVersion": "7.3.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.13-next.3",
20
+ "version": "1.1.13-next.5",
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.13-next.3",
23
+ "version": "1.1.13-next.5",
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.13-next.3",
20
+ "version": "1.1.13-next.5",
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.13-next.3",
3
+ "version": "1.1.13-next.5",
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.13-next.3",
3
+ "version": "1.1.13-next.5",
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.13-next.3",
5
+ "version": "1.1.13-next.5",
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.",
@@ -49,14 +49,14 @@ Domain expertise for Huawei Cloud Sandbox (DevStation) instances and workspace t
49
49
 
50
50
  ## 两池沙箱(pool_type)
51
51
 
52
- > 口径基调:一切以实测为准,口头支持不作数(下表生命周期为实测/平台侧现状,平台调参后本节随之更新)。
52
+ > 口径基调:一切以实测为准,口头支持不作数(下表生命周期为实测/平台侧现状;数值为服务端配置下发的默认口径,平台调参以服务端回显为准)。
53
53
 
54
54
  `huaweicloud_sandbox_connect` 用 `pool_type` 选择资源池(服务端权威映射模板;**没有 persist_mode 参数**——上游参数面仅 template_id/flavor_id):
55
55
 
56
- | pool_type | 语义 | 生命周期(已实测) | 适用 |
57
- | ------------------- | ------------------ | ---------------------------------------------------------- | --------------------------- |
58
- | `ephemeral`(默认) | 非持久沙箱 | 闲置 30 分钟自动关机 + 闲置 1 小时自动删除(平台定时任务) | 临时运行/预览,勿存重要数据 |
59
- | `persistent` | 持久沙箱(EVS 盘) | 数据跨关机保留;闲置回收保留期上限 7 天 | 需要跨会话保留数据 |
56
+ | pool_type | 语义 | 生命周期(平台策略) | 适用 |
57
+ | ------------------- | ------------------ | ------------------------------------------------------------ | --------------------------- |
58
+ | `ephemeral`(默认) | 非持久沙箱 | 闲置 30 分钟自动关机 + 闲置 1 小时自动删除(平台定时任务) | 临时运行/预览,勿存重要数据 |
59
+ | `persistent` | 持久沙箱(EVS 盘) | 数据跨关机保留;闲置 1 小时自动关机;闲置回收保留期上限 7 天 | 需要跨会话保留数据 |
60
60
 
61
61
  Selection rules(agent 按意图自动选池,开发者可覆盖;意图不明确时呈现两类供选择):
62
62
 
@@ -70,8 +70,8 @@ Selection rules(agent 按意图自动选池,开发者可覆盖;意图不
70
70
  | 跨日评审 / 展示 | persistent | 评审者后续日期回访;实例保留 |
71
71
 
72
72
  - **配额**:每用户每类型 1 台(1+1 可并存);同池 connect 自动复用已有实例
73
- - **429 双错误码**:`HDKIT_QUOTA_EXCEEDED`(账号配额满,重试无效→引导释放/复用);`HDKIT_CAPACITY_INSUFFICIENT`(池容量不足→建议错峰重试)。响应带 `Retry-After`
74
- - **TTL 播报**:connect 与执行类工具返回值带池模式/生命周期提示——**ephemeral 池部署的公网地址会随沙箱闲置关机失效**,向开发者交付 URL 时必须附带该提示
73
+ - **429 双错误码**:`HDKIT_QUOTA_EXCEEDED`(账号配额满,重试无效→引导释放/复用);`HDKIT_CAPACITY_INSUFFICIENT`(池容量不足,服务端直接返回最终文案“DevKit 免费资源当前使用人数较多,建议您错峰使用,感谢理解!”,客户端不再附加引导)。响应带 `Retry-After`
74
+ - **TTL 播报**:仅 connect(会话首次连接 / 复用被唤醒 `started=true`)与 expose_tunnel(交付地址时)返回生命周期提示;其余执行类工具不逐次播报。**ephemeral 池部署的公网地址会随沙箱闲置关机失效**,向开发者交付 URL 时必须转述 `urlLifetimeHint` / `warnings` 中的存活期提示(隧道有效期 ≠ 沙箱存活期)。数值以服务端回显为准,缺失时用默认口径兜底
75
75
  - **烧核播报口径**:沙箱运行消耗核时,播报用「平台赠送 + 活动赚取」口径(非直接计费);账号核时耗尽(`HD.97520422`)时工具返回值给出友好引导
76
76
  - **删除纪律**:不向开发者暴露生命周期删除 API(回收走平台闲置策略 G1);开发者主动删除时必须二次确认(确认要删除?数据不可恢复);绝不做延后代删
77
77
  - 显式 `template_id` 优先于 `pool_type`(自定义模板场景)
@@ -208,11 +208,11 @@ Setup is a **plugin-side preflight** — the developer should be asked a questio
208
208
 
209
209
  If `source /tmp/hw_creds.sh` returns empty, the credentials injection may not have completed — re-run `huaweicloud_sandbox_credentials`.
210
210
 
211
- **DevBridge API Key 前置引导(before expose, not mid-expose)**: the address-generation service (DevBridge 0.2.x) requires an API Key. Recent sandbox images auto-acquire one from the platform keyring — but if the sandbox was created on an older image (cannot auto-acquire), prepare the key at THIS stage instead of being interrupted halfway through the expose flow: create it once at https://devstation.connect.huaweicloud.com/space/devbridge/apikey (value shown only once, `devbridge_` prefix), hand it over via `export HW_API_KEY=<key>` locally (preferred) or paste it in the conversation for the `api_key` param, then run `huaweicloud_sandbox_credentials`. Full why/where/how guide: [Step 1, NO_CREDENTIAL branch](#step-1-authenticate--verified-waterfall-keyring-first).
211
+ **DevBridge API Key 前置引导(before expose, not mid-expose)**: the address-generation service (DevBridge 0.2.x) requires an API Key. Recent sandbox images auto-acquire one from the platform keyring — but if the sandbox was created on an older image (cannot auto-acquire), prepare the key at THIS stage instead of being interrupted halfway through the expose flow: create it once at https://devstation.connect.huaweicloud.com/space/devbridge/apikey (value shown only once, `devbridge_` prefix), hand it over via `export HW_API_KEY=<key>` locally (preferred) or paste it in the conversation for the `api_key` param, then run `huaweicloud_sandbox_credentials`. Full why/where/how guide: [Step 1, NO_CREDENTIAL branch](#step-1-authenticate--api-key-only-aksk-removed-auto-create-fallback).
212
212
 
213
213
  8. **Execute commands**: use `huaweicloud_sandbox_exec_with_session` for interactive work (cd, env setup), or `huaweicloud_sandbox_exec_one_shot` for long-running builds/installs. See [Tool Selection Guide](#tool-selection-guide).
214
214
  9. **Upload project** (if deploying local code): `huaweicloud_sandbox_upload_project` to transfer a local project directory into `/workspace/<name>` — uses HTTP tunnel, much faster than base64 for multi-file projects. Use `huaweicloud_sandbox_upload_file` for single files.
215
- 10. **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
215
+ 10. **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. **Every URL handoff MUST include the sandbox-lifetime notice from `urlLifetimeHint` (or `warnings`)**: the address dies when the sandbox idles out (ephemeral: shutdown/delete; persistent: shutdown, data retained), and the tunnel expiry is not the sandbox lifetime
216
216
 
217
217
  ## File Transfer (local → sandbox)
218
218
 
@@ -371,60 +371,52 @@ fi
371
371
 
372
372
  Measured (2026-10-22): an overnight reboot reverted `~/.huawei/bin/devbridge` 0.2.2 to the image's 0.1.13 — **yesterday's successful operation replayed as today's unexplained failure**. Recovery after a reboot: re-run the Step 0 install (or just re-export the PATH to the surviving `/workspace/.local/bin`), re-run `huaweicloud_sandbox_credentials` to re-inject `/tmp` credentials, and re-run `huaweicloud_sandbox_keepalive_watchdog` to restore the keepalive loop.
373
373
 
374
- ### Step 1: Authenticate — verified waterfall, keyring first
374
+ ### Step 1: Authenticate — API Key only (AK/SK removed, auto-create fallback)
375
375
 
376
- Authentication tries three credential sources in order, each verified with a real login + `auth status` before use; a failure falls through to the next tier:
376
+ DevBridge authentication is **fixed to API Key** (定稿决策,2026-11-04)。AK/SK login (`--access-key/--secret-key`) has been REMOVED — the auth waterfall no longer reads `/tmp/hw_creds.sh` for devbridge purposes.
377
377
 
378
- - **Tier 1 — keyring API Key** (preferred): recent sandbox images (hd-space-ai-shell line) get `HW_DEVBRIDGE_API_KEY` auto-injected by the platform into the sandbox keyring — **zero user action, long-lived key**. Read it via `hwcloud keyring get HW_DEVBRIDGE_API_KEY 2>&1` (⚠ the value goes to **stderr** — `2>&1` is mandatory or you silently capture an empty string) and accept only values starting with `devbridge_` (`(not found)` and error text are rejected). If the first read fails, a light revive (restart the D-Bus session bus at `unix:path=/run/dbus-session`, replace a locked keyring daemon with `printf '\n' | gnome-keyring-daemon --unlock`) retries once — no package installs.
379
- - **Tier 2 — AK/SK** (image builds, legacy sandboxes): image builds keep `--access-key/--secret-key`. STS credentials **must** include `--security-token "$HW_SECURITY_TOKEN"` or the gateway rejects the request with 401 `APIGW.0301`. **The `--huaweicloud` SSO login flag does NOT exist in sandbox devbridge 0.2.x binaries (e.g. 0.2.2)** — 文档超前于二进制:不要尝试 `--huaweicloud`,直接使用 `devbridge auth login --access-key/--secret-key`(上方脚本以 `auth login --help` 探测为准)。0.1.x binaries additionally advertised `--huaweicloud` (use it only when their help advertises it).
380
- - **Tier 3 — `/tmp/hw_api_key`** (release builds, user-provided one-time): the file is written by `huaweicloud_sandbox_credentials` from the local `HW_API_KEY` env (preferred) or the `api_key` param.
378
+ Authentication tries sources in order, each verified with a real `devbridge auth login --api-key` + `auth status` before use. Note: the probe tests keyring and file in one exec — file hits before auto-create triggers (if a user-provided key exists, it wins over auto-creation):
381
379
 
382
- `huaweicloud_sandbox_expose_tunnel` runs this waterfall automatically and reports the winning mode (`API_KEY_KEYRING` / `AKSK_SUPPORTED` / `API_KEY_FILE`) or `NO_CREDENTIAL` with a non-sensitive per-tier `attempts` diagnostic. Manual equivalent:
380
+ - **Tier 1 — keyring API Key** (preferred): read via `hwcloud keyring get HW_DEVBRIDGE_API_KEY 2>&1` (⚠ stderr). Sources: (a) self-injected by the plugin's auto-create (devbridge-key.mjs, key names `devkit-ephemeral`/`devkit-persistent` per pool); (b) platform legacy injection (compatible, not relied upon). A light revive (D-Bus restart + keyring unlock) retries once.
381
+ - **Tier 2 — `/tmp/hw_api_key` file** (manual channel): written by `huaweicloud_sandbox_credentials` from the local `HW_API_KEY` env or the `api_key` param. If a file exists, it wins over auto-create.
382
+ - **Tier 3 — auto-create fallback** (host-orchestrated): keyring/file both empty → plugin calls the DevStation relay API (`POST /api-key` via hdkitservice proxy) to create a key → injects into sandbox keyring (file fallback if keyring unavailable) → re-runs probe → reports `API_KEY_AUTO`.
383
+ - **Self-healing**: key invalid (login fails) → clear cache → recreate → inject → retry probe.
384
+
385
+ `huaweicloud_sandbox_expose_tunnel` runs this waterfall automatically and reports the winning mode (`API_KEY_KEYRING` / `API_KEY_AUTO` / `API_KEY_FILE`) or `NO_CREDENTIAL`.
386
+
387
+ **Key lifecycle** (per account × pool, fixed name):
388
+
389
+ - ephemeral pool → `devkit-ephemeral`
390
+ - persistent pool → `devkit-persistent`
391
+ - Local cache at `~/.config/huaweicloud/devbridge-api-keys.json` (0600) stores the full key (list API only returns mask — the full key is visible only at creation time)
392
+ - Cache miss → list (delete same-name orphan) → create → cache
393
+ - Key invalid (login fails) → clear cache → recreate
394
+
395
+ **Quota handling** (实测 2026-11-04): 20 key limit per account, error code `13002` → `429 HDKIT_DEVBRIDGE_KEY_QUOTA_EXCEEDED`. The structured fallback includes the current key list (with `last_used_at` for stale detection) and the console cleanup URL: https://devstation.connect.huaweicloud.com/space/devbridge/apikey. `devkit-*` prefixed keys are plugin-managed (auto-recreated after deletion) — advise users to clean OTHER stale keys first.
396
+
397
+ Manual equivalent (for debugging, not for agent use):
383
398
 
384
399
  ```bash
385
400
  export PATH="/workspace/.local/bin:$HOME/.huawei/bin:$PATH"
386
401
  export HOME="${HOME:-/root}"
387
- # Tier 1: platform-injected keyring key (stderr! prefix filter!)
388
402
  export DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-unix:path=/run/dbus-session}"
403
+ # Tier 1: keyring (stderr! prefix filter!)
389
404
  HW_DB_KEY=$(hwcloud keyring get HW_DEVBRIDGE_API_KEY 2>&1 || true)
390
405
  case "$HW_DB_KEY" in
391
406
  devbridge_*) devbridge auth login --api-key "$HW_DB_KEY" ;;
392
- *) # Tier 2: AK/SK (image builds; STS needs --security-token)
393
- source /tmp/hw_creds.sh 2>/dev/null
394
- if devbridge auth login --help 2>&1 | grep -q -- '--access-key'; then
395
- devbridge auth login --access-key "$HW_ACCESS_KEY" --secret-key "$HW_SECRET_KEY" \
396
- ${HW_SECURITY_TOKEN:+--security-token "$HW_SECURITY_TOKEN"}
397
- else # Tier 3: user-provided API Key file
398
- source /tmp/hw_api_key 2>/dev/null
399
- if [ -n "$HW_API_KEY" ]; then devbridge auth login --api-key "$HW_API_KEY"; fi
400
- fi ;;
407
+ *) # Tier 3: file fallback
408
+ source /tmp/hw_api_key 2>/dev/null
409
+ if [ -n "$HW_API_KEY" ]; then devbridge auth login --api-key "$HW_API_KEY"; fi ;;
401
410
  esac
402
- devbridge auth status # separate step: a status failure must not mask the login result
411
+ devbridge auth status
403
412
  ```
404
413
 
405
- - **`API_KEY_KEYRING`** → done, nothing was asked of the user (recent images only; the platform injects and rotates the key).
406
- - **`AKSK_SUPPORTED`** → done. The temporary AK/SK injected by `huaweicloud_sandbox_credentials` is used directly (validated against IAM before injection). If login fails, surface the CLI error — expired STS token → re-run `huaweicloud_sandbox_credentials`.
407
- - **`NO_CREDENTIAL`** → first re-run `huaweicloud_sandbox_credentials` and retry (refreshes STS, most common quick fix). Still failing means an older sandbox whose image cannot auto-acquire (images are frozen at sandbox creation and cannot be upgraded in place) — guide the developer through a one-time API Key creation (wait for the key before continuing):
408
-
409
- **沙箱凭证与 devbridge 版本差异陷阱(实测 #935 坑2/坑3)**:
410
-
411
- - 沙箱内 devbridge 0.2.2 无技能文档所述 `--huaweicloud` 登录标志(文档超前于二进制)—— 应直接使用 `devbridge auth login --access-key/--secret-key`(`auth login --help` 探测到 `--access-key` 支持时走该分支,见上方手动等价脚本)。
412
- - 沙箱内 MCP 注入的 STS 凭证为 obsutil 加密态存储:devbridge 读到密文凭证会报 `APIGW.0301`。需先上传本地 DevKit 明文凭证库(AK 20 位 / SK 40 位,通过 `huaweicloud_sandbox_credentials` 注入),再创建隧道。
413
-
414
- 1. **Why (one sentence)**: "沙箱的地址生成服务发布版构建已移除 AK/SK 登录,仅支持 API Key(镜像内置构建仍支持 AK/SK,Agent 已自动探测)。API Key 按账号管理,创建一次长期可用、所有沙箱通用。"
415
- 2. **Where (exact steps)**: open https://devstation.connect.huaweicloud.com/space/devbridge/apikey → 登录控制台 → 选择 DevBridge 场景 → 点击"创建"。**完整值仅在创建时展示一次,立即复制**(`devbridge_` 开头)。
416
- 3. **How to hand it over** (present both, recommend the first):
417
- - **Recommended**: 在本地终端执行 `export HW_API_KEY=<粘贴Key>`(或写入 shell profile),然后告知"已设置" — Key 不经过对话记录;随后 agent 重新调用 `huaweicloud_sandbox_credentials`(本地 `HW_API_KEY` 会被自动透传注入沙箱)。
418
- - **Alternative**: 直接把 Key 粘贴在对话中 — agent 通过 `huaweicloud_sandbox_credentials` 的 `api_key` 参数注入,**绝不回显、不复述、不写入日志**。
419
- 4. **Security**: 不要提交到代码库或分享;怀疑泄露时在同页面删除并重建(1 分钟)。
420
-
421
- **Failure paths (API Key branch)**:
422
-
423
- | Symptom | Guidance |
424
- | -------------------------------------- | ---------------------------------------------------- |
425
- | `auth login` returns 401/403 | Key 已被删除或禁用 — 引导在同页面删除旧 Key 重新创建 |
426
- | 无法访问 API Key 管理页面 | 账号无该页面权限 — 引导联系账号管理员处理 |
427
- | Key 带空格/换行/缺失 `devbridge_` 前缀 | 引导重新完整复制粘贴 |
414
+ - **`API_KEY_KEYRING`** → done (zero user action; key is ours or platform legacy).
415
+ - **`API_KEY_AUTO`** → done (plugin auto-created and injected; zero user action).
416
+ - **`API_KEY_FILE`** → done (user-provided or auto-injected file fallback).
417
+ - **`NO_CREDENTIAL`** → auto-create failed. Check the error:
418
+ - **Quota full** (`HDKIT_DEVBRIDGE_KEY_QUOTA_EXCEEDED`): guide the user to clean unused keys at https://devstation.connect.huaweicloud.com/space/devbridge/apikey (the `currentKeys` field shows the list). `devkit-*` keys auto-recreate — clean other stale keys first.
419
+ - **Other errors**: one-time manual fallback — create a key at the console page, `export HW_API_KEY=<key>` locally, re-run `huaweicloud_sandbox_credentials`, then retry.
428
420
 
429
421
  ### Step 2: Expose
430
422
 
@@ -1091,7 +1083,7 @@ Returns `complete: true/false`, `score`, and `nextStep` to fix missing items.
1091
1083
  | 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 |
1092
1084
  | Real-name required | `sandbox_connect` fails if `realnameVerified=false`; tell the developer once and stop, don't loop on connect |
1093
1085
  | Never expose tunnel details | Do not mention "DevBridge"/"tunnel"/"devbridge" to the developer — say "正在生成访问地址..." and hand over only the URL |
1094
- | devbridge auth: probe the build | `0.2.2-release` has two builds: image builds retain AK/SK login + env auto-read (fully automatic via `/tmp/hw_creds.sh`); release builds accept only API Key (`/tmp/hw_api_key`). Never assume — probe with `devbridge auth login --help 2>&1 \| grep -q -- '--access-key'` (Step 1 of "Expose the deployed app") |
1086
+ | devbridge auth: API Key only | DevBridge auth is fixed to API Key (AK/SK login removed, 2026-11-04). The waterfall: keyring (ours + platform legacy) → auto-create fallback → file. Use `devbridge auth login --api-key` only — never `--access-key/--secret-key`. |
1095
1087
  | 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 |
1096
1088
  | CLI PATH | The installer only writes `~/.bashrc`; run `export PATH="/workspace/.local/bin:$HOME/.huawei/bin:$PATH"` (prepend) in the session before using `devbridge` — appending lets a stale image binary in `/usr/local/bin` win. `/workspace/.local/bin` survives sandbox reboots; `~/.huawei/bin` is on the system disk and is wiped on reboot (#999) |
1097
1089
  | 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 |
@@ -13,23 +13,26 @@
13
13
 
14
14
  ## 两类沙箱
15
15
 
16
- | 维度 | 非持久化 (ephemeral) | 持久化 (persistent) |
17
- | -------- | --------------------------------------- | -------------------------------------------- |
18
- | 定位 | 预览/试用/一次性任务/敏感即焚 | 开发工作台/带数据库项目/跨日评审展示 |
19
- | 生命周期 | 闲置 30min 自动关机 + 1h 删除(已实测) | 闲置回收保留期上限 7 天(资源组 DB 硬约束) |
20
- | 存储 | 无持久盘 | 挂 EVS 32G |
21
- | 配额 | 每用户 1 台(同类复用) | 每用户 1 台(同类复用;可与 1 台非持久并存) |
16
+ | 维度 | 非持久化 (ephemeral) | 持久化 (persistent) |
17
+ | -------- | --------------------------------------- | --------------------------------------------------------------------- |
18
+ | 定位 | 预览/试用/一次性任务/敏感即焚 | 开发工作台/带数据库项目/跨日评审展示 |
19
+ | 生命周期 | 闲置 30min 自动关机 + 1h 删除(已实测) | 闲置 1h 自动关机(实例与数据保留);回收上限 7 天(资源组 DB 硬约束) |
20
+ | 存储 | 无持久盘 | 挂 EVS 32G |
21
+ | 配额 | 每用户 1 台(同类复用) | 每用户 1 台(同类复用;可与 1 台非持久并存) |
22
22
 
23
23
  ## 生命周期(实测口径)
24
24
 
25
25
  | 项 | 口径 | 状态 |
26
26
  | --------------- | ---------------------------------------------- | ------------- |
27
27
  | 非持久闲置策略 | 30min 自动关机 + 1h 删除(平台定时任务) | ✅ 已验证生效 |
28
+ | 持久闲置策略 | 闲置 1h 自动关机(实例与数据盘保留) | 平台侧 |
28
29
  | 持久闲置回收 | 保留期上限 7 天(原 14 天口径作废) | 平台侧 |
29
30
  | 活性检测 | 当前仅 connect 单信号(三信号为后续诉求 G2) | 现状 |
30
31
  | 删除/关机前通知 | 不支持(G6 诉求中)→ 客户端 TTL 播报为补偿方案 | 现状 |
31
32
  | 两池实际差异 | 仅 EVS 32GiB 数据盘(非持久池无 disks 字段) | ✅ 已实证 |
32
33
 
34
+ > 生命周期数值是**播报口径**,由 hdkitservice 配置(`POOL_EPHEMERAL_IDLE_SHUTDOWN_MINUTES` / `POOL_EPHEMERAL_IDLE_DELETE_MINUTES` / `POOL_PERSISTENT_IDLE_SHUTDOWN_MINUTES` / `POOL_PERSISTENT_RECLAIM_MINUTES`,统一以**分钟**为单位;7 天 = 10080)随 connect 响应回显;客户端以回显为准,缺失时按上表默认值兜底。实际关机/删除策略由平台资源池执行,hdkitservice 不控制。
35
+
33
36
  ## 容量规划(原始申请口径)
34
37
 
35
38
  | 资源 | 申请容量 |
@@ -47,7 +50,7 @@
47
50
  - 每用户每类型 1 台(同类复用,ephemeral + persistent 可并存 1+1);账号并发上限 5
48
51
  - 429 双错误码(均带 `Retry-After: 60`):
49
52
  - `HDKIT_QUOTA_EXCEEDED`:用户配额满(上游 `HD.98320022`)→ 重试无效,引导释放/复用已有实例
50
- - `HDKIT_CAPACITY_INSUFFICIENT`:池容量不足(上游 `HD.99003001`/`HD.99008001`/`HD.98330021`)→ 建议错峰重试
53
+ - `HDKIT_CAPACITY_INSUFFICIENT`:池容量不足(上游 `HD.99003001`/`HD.99008001`/`HD.98330021`)→ 服务端直接返回最终文案“DevKit 免费资源当前使用人数较多,建议您错峰使用,感谢理解!”,客户端原样透传不再附加引导
51
54
  - 烧核:`HD.97520422` = 账号核心时长耗尽(沙箱运行消耗核时;客户端对含该码的错误输出友好播报)
52
55
 
53
56
  ## 烧核播报口径
@@ -57,8 +60,9 @@
57
60
  ## 客户端行为(本插件)
58
61
 
59
62
  - `sandbox_connect` 传 `pool_type`;非法值本地即报错(不发无效请求);未传由服务端默认 ephemeral
60
- - connect 返回值富化 `pool` 块(TTL 播报,仅服务端回显 pool_type 时);exec/upload/deploy/expose 返回值带 `poolMode`/`persistenceHint`
61
- - 429 结构化 `remediation`(配额满/容量不足差异化引导步骤)+ `retryAfter`
63
+ - 生命周期提示只在两个时机播报:connect(会话首次连接,或复用实例被唤醒 `started=true`)与 `expose_tunnel`(交付地址时,返回 `urlLifetimeHint` + `warnings`);其余 exec/upload/deploy 工具不逐次播报
64
+ - connect 返回值富化 `pool` 块(数值取服务端回显,缺失时用默认口径);生命周期口径按 workspace 缓存到 `~/.config/huaweicloud/sandbox-pools.json`(0600,7 天过期),进程重启后仍可播报
65
+ - 429:配额满给 `remediation` 引导;容量不足只透传服务端最终文案 + `retryAfter`
62
66
  - 钉扎(可选,运维场景):`HW_POOL_TEMPLATE_EPHEMERAL`/`HW_POOL_TEMPLATE_PERSISTENT` 环境变量——设置后客户端附带映射 template_id 上送;日常部署无需配置(模板 ID 由服务端权威维护)
63
67
 
64
68
  ## 平台侧关键依赖(G 清单,非 devkit 交付)
@@ -0,0 +1,187 @@
1
+ // DevBridge API Key 管理:本地缓存 + 孤儿清理 + 自动创建 + 两池分流。
2
+ //
3
+ // 认证架构(定稿):
4
+ // - devbridge 固定使用 API Key 认证(AK/SK 登录已移除)
5
+ // - key 来源优先级:keyring(我们注入的为主,平台遗留兼容命中)
6
+ // → 自动创建兜底 → /tmp/hw_api_key 文件(手动通道最后)
7
+ // - key 生命周期:每账号 × 每池恰 1 个固定 name key
8
+ // - ephemeral → devkit-ephemeral
9
+ // - persistent → devkit-persistent(未知池归 ephemeral)
10
+ // - 配额上限 20 个/账号(上游错误码 13002)
11
+ //
12
+ // 缓存文件 ~/.config/huaweicloud/devbridge-api-keys.json(0600):
13
+ // { [akFingerprint]: { ephemeral: "devbridge_...", persistent: "devbridge_..." } }
14
+ // 完整 key 只在创建时可见(list 仅 mask),缓存是唯一复用途径。
15
+
16
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from 'node:fs';
17
+ import { join, dirname } from 'node:path';
18
+ import { homedir } from 'node:os';
19
+ import { createHash } from 'node:crypto';
20
+
21
+ import { hdkitCreateApiKey, hdkitListApiKeys, hdkitDeleteApiKey } from './hdkitservice-api.mjs';
22
+ import { getCredentials } from './hwlink-api.mjs';
23
+
24
+ const POOL_KEY_NAMES = {
25
+ ephemeral: 'devkit-ephemeral',
26
+ persistent: 'devkit-persistent',
27
+ };
28
+ const DEFAULT_POOL = 'ephemeral';
29
+ const CACHE_FILE = join(homedir(), '.config', 'huaweicloud', 'devbridge-api-keys.json');
30
+
31
+ /** pool → 固定 key name */
32
+ export function keyNameForPool(poolType) {
33
+ return POOL_KEY_NAMES[poolType] || POOL_KEY_NAMES[DEFAULT_POOL];
34
+ }
35
+
36
+ /** AK 指纹(缓存键,不落明文 AK) */
37
+ function akFingerprint(ak) {
38
+ return createHash('sha256').update(String(ak)).digest('hex').slice(0, 16);
39
+ }
40
+
41
+ // ── 本地缓存 ──
42
+
43
+ function readCache() {
44
+ try {
45
+ return JSON.parse(readFileSync(CACHE_FILE, 'utf8'));
46
+ } catch {
47
+ return {};
48
+ }
49
+ }
50
+
51
+ function writeCache(cache) {
52
+ const dir = dirname(CACHE_FILE);
53
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
54
+ writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), { mode: 0o600 });
55
+ try {
56
+ chmodSync(CACHE_FILE, 0o600);
57
+ } catch {
58
+ // Windows: chmod 可能不可用
59
+ }
60
+ }
61
+
62
+ /** 读缓存中的完整 key(按 AK + 池) */
63
+ export function getCachedKey(ak, poolType) {
64
+ const cache = readCache();
65
+ return cache[akFingerprint(ak)]?.[keyNameForPool(poolType)] || '';
66
+ }
67
+
68
+ /** 写缓存(按 AK + 池) */
69
+ function setCachedKey(ak, poolType, apiKey) {
70
+ const fp = akFingerprint(ak);
71
+ const cache = readCache();
72
+ if (!cache[fp]) cache[fp] = {};
73
+ cache[fp][keyNameForPool(poolType)] = apiKey;
74
+ writeCache(cache);
75
+ }
76
+
77
+ /** 清除缓存(key 失效时自愈用) */
78
+ export function clearCachedKey(ak, poolType) {
79
+ const fp = akFingerprint(ak);
80
+ const cache = readCache();
81
+ if (cache[fp]?.[keyNameForPool(poolType)]) {
82
+ delete cache[fp][keyNameForPool(poolType)];
83
+ writeCache(cache);
84
+ }
85
+ }
86
+
87
+ // ── 自动创建链路 ──
88
+
89
+ /**
90
+ * 确保当前账号在指定池有一个可用的 DevBridge API Key。
91
+ *
92
+ * 流程:缓存命中 → 直接返回
93
+ * 无缓存 → list 删同名孤儿 → 创建 → 缓存 → 返回
94
+ *
95
+ * @param {string} poolType ephemeral/persistent(未知归 ephemeral)
96
+ * @returns {Promise<{apiKey: string, mode: string, ak: string}>}
97
+ * @throws Error 配额满(code=HDKIT_DEVBRIDGE_KEY_QUOTA_EXCEEDED)或上游错误
98
+ */
99
+ export async function ensureDevbridgeKey(poolType) {
100
+ const { ak } = getCredentials();
101
+ const pool = POOL_KEY_NAMES[poolType] ? poolType : DEFAULT_POOL;
102
+ const name = keyNameForPool(pool);
103
+
104
+ // 1. 缓存命中
105
+ const cached = getCachedKey(ak, pool);
106
+ if (cached) {
107
+ return { apiKey: cached, mode: 'CACHE_HIT', ak };
108
+ }
109
+
110
+ // 2. list → 删同名孤儿(配额安全:每池恰 1 个,1+1 配额保证同名 key 不可能被另一台活沙箱使用)
111
+ try {
112
+ const keys = await hdkitListApiKeys();
113
+ const orphan = Array.isArray(keys) ? keys.find((k) => k.name === name) : null;
114
+ if (orphan) {
115
+ await hdkitDeleteApiKey(orphan.id);
116
+ }
117
+ } catch {
118
+ // list/delete 失败不阻断创建(可能权限或网络问题,继续尝试创建)
119
+ }
120
+
121
+ // 3. 创建
122
+ const created = await hdkitCreateApiKey(name, 'devbridge');
123
+
124
+ // 4. 缓存
125
+ setCachedKey(ak, pool, created.api_key);
126
+
127
+ return { apiKey: created.api_key, mode: 'CREATED', ak };
128
+ }
129
+
130
+ /**
131
+ * key 失效自愈:清除缓存 → 走完整重建链路。
132
+ * 由沙箱内 devbridge auth login 失败触发。
133
+ */
134
+ export async function recreateDevbridgeKey(poolType) {
135
+ const { ak } = getCredentials();
136
+ clearCachedKey(ak, poolType);
137
+ return ensureDevbridgeKey(poolType);
138
+ }
139
+
140
+ // ── 配额降级数据 ──
141
+
142
+ /**
143
+ * 获取配额降级所需的 key 清单(list 成功时返回数组,含 last_used_at)。
144
+ * 用于配额满时给用户展示可清理的 key。
145
+ */
146
+ export async function listKeysForQuotaHint() {
147
+ try {
148
+ const keys = await hdkitListApiKeys();
149
+ if (!Array.isArray(keys)) return [];
150
+ return keys.map((k) => ({
151
+ name: k.name,
152
+ id: k.id,
153
+ lastUsed: k.last_used_at || '',
154
+ }));
155
+ } catch {
156
+ return [];
157
+ }
158
+ }
159
+
160
+ // ── keyring 注入命令构建 ──
161
+
162
+ /**
163
+ * 构建沙箱内 keyring 注入命令(exec 到沙箱执行)。
164
+ * keyring 不可用时降级写 /tmp/hw_api_key 文件。
165
+ */
166
+ export function buildKeyringInjectScript(apiKey) {
167
+ return [
168
+ `export PATH="/workspace/.local/bin:$HOME/.huawei/bin:$PATH"`,
169
+ `export HOME="\${HOME:-/root}"`,
170
+ // 尝试 keyring set(hwcloud 支持 set 时优先)
171
+ `KEY_INJECTED=0`,
172
+ `if command -v hwcloud >/dev/null 2>&1; then`,
173
+ ` export DBUS_SESSION_BUS_ADDRESS="\${DBUS_SESSION_BUS_ADDRESS:-unix:path=/run/dbus-session}"`,
174
+ ` if ! pgrep -x dbus-daemon >/dev/null 2>&1 && command -v dbus-daemon >/dev/null 2>&1; then`,
175
+ ` dbus-daemon --session --address="$DBUS_SESSION_BUS_ADDRESS" --fork --nopidfile >/dev/null 2>&1 || true`,
176
+ ` sleep 1`,
177
+ ` fi`,
178
+ ` hwcloud keyring set HW_DEVBRIDGE_API_KEY '${apiKey}' 2>/dev/null && KEY_INJECTED=1`,
179
+ `fi`,
180
+ // keyring 不可用 → 降级写文件(0600,与手动通道同路径但内容是我们注入的自动 key)
181
+ `if [ "$KEY_INJECTED" -eq 0 ]; then`,
182
+ ` echo "export HW_API_KEY='${apiKey}'" > /tmp/hw_api_key`,
183
+ ` chmod 600 /tmp/hw_api_key`,
184
+ `fi`,
185
+ `echo "DB_KEY_INJECTED=$KEY_INJECTED"`,
186
+ ].join('\n');
187
+ }
@@ -79,8 +79,9 @@ async function hdkitRequest(method, path, body, timeoutMs = 300000) {
79
79
  ],
80
80
  };
81
81
  }
82
- // 429 双错误码结构化处理(两池配额契约):配额满(用户侧,重试无效)与
83
- // 池容量不足(平台侧,错峰重试)给出差异化引导;携带服务端 Retry-After
82
+ // 429 双错误码(两池配额契约):配额满给出释放/复用引导;池容量不足只透传服务端
83
+ // 最终文案(“DevKit 免费资源当前使用人数较多,建议您错峰使用,感谢理解!”),
84
+ // 不再附加额外引导——该文案即最终展示内容。两者均携带服务端 Retry-After。
84
85
  if (code === 'HDKIT_QUOTA_EXCEEDED') {
85
86
  err.retryAfter = resp.headers.get('Retry-After') || undefined;
86
87
  err.remediation = {
@@ -94,13 +95,8 @@ async function hdkitRequest(method, path, body, timeoutMs = 300000) {
94
95
  }
95
96
  if (code === 'HDKIT_CAPACITY_INSUFFICIENT') {
96
97
  err.retryAfter = resp.headers.get('Retry-After') || undefined;
97
- err.remediation = {
98
- hint: '平台池容量暂时不足(非账号问题):',
99
- steps: [
100
- '稍后错峰重试(devkit提供的免费资源紧张,建议错峰使用)',
101
- '急需使用时可尝试另一 pool_type(另一池可能有余量)',
102
- ],
103
- };
98
+ // 该文案即最终展示内容(不带 "CODE: " 前缀、不附加引导)
99
+ err.message = data.message || err.message;
104
100
  }
105
101
  // 烧核播报(缺口#5 代码层):账号核心时长耗尽——上游码 HD.97520422 经
106
102
  // hdkitservice 以 HDKIT_UPSTREAM_ERROR 包装透传,客户端按消息内错误码识别。
@@ -168,6 +164,28 @@ export async function hdkitCredentials(sessionId, devStageId, enableSts = true)
168
164
  return await hdkitRequest('POST', 'credentials', body);
169
165
  }
170
166
 
167
+ // ── DevBridge API Key relay 代理(经 hdkitservice 透传 DevStation relay 接口)──
168
+ // 凭证由 hdkitRequest 内部 getCredentials() 统一解析,调用方无需传
169
+
170
+ /**
171
+ * 创建 DevBridge API Key。响应含完整 api_key(仅创建时可见)。
172
+ * 配额满(13002)时服务端返回 429 HDKIT_DEVBRIDGE_KEY_QUOTA_EXCEEDED,
173
+ * hdkitRequest 抛错 err.code 携带该码。
174
+ */
175
+ export async function hdkitCreateApiKey(name, scope = 'devbridge') {
176
+ return await hdkitRequest('POST', 'api-key', { name, scope });
177
+ }
178
+
179
+ /** 列出当前账号所有 API Key(仅 mask + last_used_at)。 */
180
+ export async function hdkitListApiKeys() {
181
+ return await hdkitRequest('GET', 'api-keys', undefined, 30000);
182
+ }
183
+
184
+ /** 按 keyId 删除 API Key(上游 DELETE 方法)。 */
185
+ export async function hdkitDeleteApiKey(keyId) {
186
+ return await hdkitRequest('DELETE', `api-keys/${encodeURIComponent(keyId)}`, undefined, 30000);
187
+ }
188
+
171
189
  export async function hdkitVoucherStatus(domainId) {
172
190
  try {
173
191
  const path = domainId ? `voucher/status?domain_id=${encodeURIComponent(domainId)}` : 'voucher/status';