huaweicloud-devkit 1.1.0-next.4 → 1.1.0-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 +1 -1
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +19 -17
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +34 -11
- package/plugins/huaweicloud-core/src/tools.mjs +10 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.1.0-next.
|
|
3
|
+
"version": "1.1.0-next.5",
|
|
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": [
|
|
@@ -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.0-next.
|
|
20
|
+
"version": "1.1.0-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.0-next.
|
|
23
|
+
"version": "1.1.0-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.0-next.
|
|
20
|
+
"version": "1.1.0-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.0-next.
|
|
3
|
+
"version": "1.1.0-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.0-next.
|
|
3
|
+
"version": "1.1.0-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.0-next.
|
|
5
|
+
"version": "1.1.0-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.",
|
|
@@ -335,23 +335,25 @@ Extract the tunnel URL from DevBridge output and return it to the developer. For
|
|
|
335
335
|
|
|
336
336
|
## Critical Warnings
|
|
337
337
|
|
|
338
|
-
| Trap | Why
|
|
339
|
-
| ------------------------------------ |
|
|
340
|
-
| 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
|
|
341
|
-
| Real-name required | `sandbox_connect` fails if `realnameVerified=false`; tell the developer once and stop, don't loop on connect
|
|
342
|
-
| Never expose tunnel details | Do not mention "DevBridge"/"tunnel"/"devbridge" to the developer — say "正在生成访问地址..." and hand over only the URL
|
|
343
|
-
| Login needs `--huaweicloud` | `devbridge auth login --access-key/--secret-key` without `--huaweicloud` falls back to interactive browser login, which fails in the sandbox
|
|
344
|
-
| CLI PATH | The installer only writes `~/.bashrc`; run `export PATH=$PATH:$HOME/.huawei/bin` in the session before using `devbridge`
|
|
345
|
-
| 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
|
|
346
|
-
| Return the deployment URL | Always hand the public URL from the host log to the developer as the final result
|
|
347
|
-
| Session state persists | `exec_with_session` preserves `cd`, env vars, aliases between calls
|
|
348
|
-
| 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).
|
|
349
|
-
| Destructive commands blocked | `rm -rf /`, `mkfs`, `dd if=`, fork bombs are denied by safety policy
|
|
350
|
-
| Workspace ID = dev_stage_id | Use `dev_stage_id` from `sandbox_connect` as `workspace_id` for terminal exec
|
|
351
|
-
| 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
|
|
352
|
-
| 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
|
|
353
|
-
| Upload file for single files | Use `sandbox_upload_file` for individual files — base64 chunked, reliable for small files (<1MB)
|
|
354
|
-
| 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")
|
|
338
|
+
| Trap | Why |
|
|
339
|
+
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
340
|
+
| 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 |
|
|
341
|
+
| Real-name required | `sandbox_connect` fails if `realnameVerified=false`; tell the developer once and stop, don't loop on connect |
|
|
342
|
+
| Never expose tunnel details | Do not mention "DevBridge"/"tunnel"/"devbridge" to the developer — say "正在生成访问地址..." and hand over only the URL |
|
|
343
|
+
| Login needs `--huaweicloud` | `devbridge auth login --access-key/--secret-key` without `--huaweicloud` falls back to interactive browser login, which fails in the sandbox |
|
|
344
|
+
| CLI PATH | The installer only writes `~/.bashrc`; run `export PATH=$PATH:$HOME/.huawei/bin` in the session before using `devbridge` |
|
|
345
|
+
| 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 |
|
|
346
|
+
| Return the deployment URL | Always hand the public URL from the host log to the developer as the final result |
|
|
347
|
+
| Session state persists | `exec_with_session` preserves `cd`, env vars, aliases between calls |
|
|
348
|
+
| 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). |
|
|
349
|
+
| Destructive commands blocked | `rm -rf /`, `mkfs`, `dd if=`, fork bombs are denied by safety policy |
|
|
350
|
+
| Workspace ID = dev_stage_id | Use `dev_stage_id` from `sandbox_connect` as `workspace_id` for terminal exec |
|
|
351
|
+
| 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 |
|
|
352
|
+
| 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 |
|
|
353
|
+
| Upload file for single files | Use `sandbox_upload_file` for individual files — base64 chunked, reliable for small files (<1MB) |
|
|
354
|
+
| 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") |
|
|
355
|
+
| 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. |
|
|
356
|
+
| 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. |
|
|
355
357
|
|
|
356
358
|
## Node.js in the sandbox
|
|
357
359
|
|
|
@@ -155,7 +155,9 @@ export async function execWithSession(workspaceId, command, username, timeoutMs)
|
|
|
155
155
|
|
|
156
156
|
export const UPLOAD_CHUNK_SIZE = 30000;
|
|
157
157
|
|
|
158
|
-
export const UPLOAD_BATCH_SIZE =
|
|
158
|
+
export const UPLOAD_BATCH_SIZE = 2;
|
|
159
|
+
|
|
160
|
+
export const UPLOAD_MAX_RETRIES = 3;
|
|
159
161
|
|
|
160
162
|
export function splitBase64Chunks(base64, chunkSize = UPLOAD_CHUNK_SIZE) {
|
|
161
163
|
const chunks = [];
|
|
@@ -189,11 +191,21 @@ export async function uploadFileWithSession(workspaceId, localPath, remotePath,
|
|
|
189
191
|
const batchNum = Math.floor(batchStart / UPLOAD_BATCH_SIZE) + 1;
|
|
190
192
|
const totalBatches = Math.ceil(chunks.length / UPLOAD_BATCH_SIZE);
|
|
191
193
|
const cmd = `printf '%s' '${combinedChunk}' >> "${tmp}"`;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
);
|
|
194
|
+
|
|
195
|
+
let batchOk = false;
|
|
196
|
+
let lastError;
|
|
197
|
+
for (let retry = 0; retry < UPLOAD_MAX_RETRIES; retry++) {
|
|
198
|
+
const res = await execWithSession(workspaceId, cmd, username, timeoutMs);
|
|
199
|
+
if (res.exitCode === 0) {
|
|
200
|
+
batchOk = true;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
lastError = res.stdout || res.error || res.exitCode;
|
|
204
|
+
console.error(` upload retry ${retry + 1}/${UPLOAD_MAX_RETRIES} for batch ${batchNum}/${totalBatches}`);
|
|
205
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
206
|
+
}
|
|
207
|
+
if (!batchOk) {
|
|
208
|
+
throw new Error(`sandbox upload: failed writing batch ${batchNum}/${totalBatches}: ${lastError}`);
|
|
197
209
|
}
|
|
198
210
|
if (batchNum % 10 === 0 || batchNum === totalBatches) {
|
|
199
211
|
console.error(` upload progress: batch ${batchNum}/${totalBatches}`);
|
|
@@ -556,14 +568,25 @@ export async function uploadProjectWithSession(
|
|
|
556
568
|
uploadLog(`uploadProject: ${localDir} -> ${archiveRemotePath} (archive=${archiveSize} bytes, md5=${expectedMd5})`);
|
|
557
569
|
|
|
558
570
|
let result;
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
571
|
+
let tunnelError;
|
|
572
|
+
for (let attempt = 0; attempt < UPLOAD_MAX_RETRIES; attempt++) {
|
|
573
|
+
try {
|
|
574
|
+
await cleanupFileServer(workspaceId, username).catch(() => {});
|
|
575
|
+
result = await uploadViaHttpTunnel(workspaceId, archivePath, archiveRemotePath, username, timeoutMs, options);
|
|
576
|
+
tunnelError = null;
|
|
577
|
+
break;
|
|
578
|
+
} catch (error) {
|
|
579
|
+
tunnelError = error;
|
|
580
|
+
uploadLog(`uploadProject: attempt ${attempt + 1}/${UPLOAD_MAX_RETRIES} failed: ${error.message}`);
|
|
581
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (tunnelError) {
|
|
585
|
+
uploadLog(`uploadProject: all ${UPLOAD_MAX_RETRIES} attempts failed: ${tunnelError.message}`);
|
|
563
586
|
uploadLog(`uploadProject: NOT falling back to base64 (removed). Rethrowing with diagnostics.`);
|
|
564
587
|
cleanupLocalArchive(archivePath);
|
|
565
588
|
throw new Error(
|
|
566
|
-
`sandbox upload failed: HTTP tunnel could not transfer the project archive. ` +
|
|
589
|
+
`sandbox upload failed after ${UPLOAD_MAX_RETRIES} attempts: HTTP tunnel could not transfer the project archive. ` +
|
|
567
590
|
`Archive size: ${(archiveSize / 1024).toFixed(1)}KB. ` +
|
|
568
591
|
`Root cause: ${tunnelError.message}. ` +
|
|
569
592
|
`Diagnostic log: ${UPLOAD_LOG_PATH}`,
|
|
@@ -648,21 +648,27 @@ export const TOOL_DEFINITIONS = [
|
|
|
648
648
|
},
|
|
649
649
|
{
|
|
650
650
|
name: 'huaweicloud_voucher_status',
|
|
651
|
-
description: '
|
|
651
|
+
description: 'Query the Huawei Cloud incentive voucher claim status.',
|
|
652
652
|
inputSchema: {
|
|
653
653
|
type: 'object',
|
|
654
654
|
properties: {
|
|
655
|
-
domain_id: {
|
|
655
|
+
domain_id: {
|
|
656
|
+
type: 'string',
|
|
657
|
+
description: 'Optional. Leave empty in production — account is resolved from IAM automatically.',
|
|
658
|
+
},
|
|
656
659
|
},
|
|
657
660
|
},
|
|
658
661
|
},
|
|
659
662
|
{
|
|
660
663
|
name: 'huaweicloud_voucher_claim',
|
|
661
|
-
description: '
|
|
664
|
+
description: 'Claim the Huawei Cloud incentive voucher. Automatically checks if already claimed.',
|
|
662
665
|
inputSchema: {
|
|
663
666
|
type: 'object',
|
|
664
667
|
properties: {
|
|
665
|
-
domain_id: {
|
|
668
|
+
domain_id: {
|
|
669
|
+
type: 'string',
|
|
670
|
+
description: 'Optional. Leave empty in production — account is resolved from IAM automatically.',
|
|
671
|
+
},
|
|
666
672
|
},
|
|
667
673
|
},
|
|
668
674
|
},
|