huaweicloud-devkit 1.1.12 → 1.1.13-next.2
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +2 -2
- 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 +58 -7
- package/plugins/huaweicloud-core/skills/huawei-sandbox/references/fullstack-postgrest.md +178 -17
- package/plugins/huaweicloud-core/skills/huawei-sandbox/references/resource-pool.md +77 -0
- package/plugins/huaweicloud-core/src/mcp-protocol.mjs +8 -1
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +48 -0
- package/plugins/huaweicloud-core/src/sandbox/sandbox-pool.mjs +104 -0
- package/plugins/huaweicloud-core/src/setup-cli.mjs +12 -5
- package/plugins/huaweicloud-core/src/telemetry/agent-detect.mjs +20 -15
- package/plugins/huaweicloud-core/src/telemetry/agent-registry.mjs +4 -0
- package/plugins/huaweicloud-core/src/telemetry/telemetry.mjs +105 -31
- package/plugins/huaweicloud-core/src/tools.mjs +131 -56
|
@@ -27,6 +27,13 @@ import {
|
|
|
27
27
|
getCurrentWorkspaceId,
|
|
28
28
|
setWorkspaceId,
|
|
29
29
|
} from './sandbox/session-manager.mjs';
|
|
30
|
+
import {
|
|
31
|
+
validatePoolType,
|
|
32
|
+
rememberWorkspacePool,
|
|
33
|
+
enrichConnectResult,
|
|
34
|
+
withPoolContext,
|
|
35
|
+
forgetWorkspacePool,
|
|
36
|
+
} from './sandbox/sandbox-pool.mjs';
|
|
30
37
|
import {
|
|
31
38
|
hdkitCheckUser,
|
|
32
39
|
hdkitSignAgreement,
|
|
@@ -54,7 +61,7 @@ import {
|
|
|
54
61
|
parseStsExpiry,
|
|
55
62
|
resolveCredentialsWithRuntime,
|
|
56
63
|
} from './auth/credentials.mjs';
|
|
57
|
-
import { trackToolInvoke, trackSkillRetrieve, clearUserHash } from './telemetry/telemetry.mjs';
|
|
64
|
+
import { trackToolInvoke, trackSkillRetrieve, clearUserHash, cacheUserHash } from './telemetry/telemetry.mjs';
|
|
58
65
|
import { fetchWithProxy } from './proxy/proxy-agent.mjs';
|
|
59
66
|
import { fingerprint, runHcloudConfigure, runHcloudProfileDelete, resolveManagedProfile } from './auth/reconcile.mjs';
|
|
60
67
|
import {
|
|
@@ -623,7 +630,8 @@ export const TOOL_DEFINITIONS = [
|
|
|
623
630
|
},
|
|
624
631
|
{
|
|
625
632
|
name: 'huaweicloud_sandbox_close_session',
|
|
626
|
-
description:
|
|
633
|
+
description:
|
|
634
|
+
'Close the persistent terminal session for a workspace. This closes the agent-side terminal session only — it does NOT delete the sandbox instance. Resource-pool sandbox deletion is developer-initiated (always ask for a second confirmation before guiding a delete) or handled by the platform resource-group idle-reclaim policy within the 7-day cap (v3 caliber); devkit never defers or performs deletion on the user behalf.',
|
|
627
635
|
inputSchema: {
|
|
628
636
|
type: 'object',
|
|
629
637
|
properties: {
|
|
@@ -876,7 +884,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
876
884
|
{
|
|
877
885
|
name: 'huaweicloud_sandbox_connect',
|
|
878
886
|
description:
|
|
879
|
-
'Connect to a sandbox via hdkitservice. One user
|
|
887
|
+
'Connect to a sandbox via hdkitservice. One instance per user per pool — reuses the same-pool sandbox if available, otherwise creates a new one. Quota: 1 instance per pool type (a user may hold 1 ephemeral AND 1 persistent simultaneously; same-type requests reuse the existing instance). On quota exhaustion the API returns HTTP 429 with a Retry-After header and a dual error code: HDKIT_QUOTA_EXCEEDED (retry useless — release or reuse the existing instance) vs HDKIT_CAPACITY_INSUFFICIENT (pool capacity; retry later / off-peak). Returns session_id, dev_stage_id, connection_id, connection_address, expiresAt (STS credential expiry), and pool_type/template_id echo. Ephemeral pool (default): idle auto-shutdown after 30min, auto-delete after 1h — do not keep important data there. Persistent pool: data disk retained (EVS 32G), idle reclaim capped at 7 days.',
|
|
880
888
|
inputSchema: {
|
|
881
889
|
type: 'object',
|
|
882
890
|
properties: {
|
|
@@ -885,6 +893,12 @@ export const TOOL_DEFINITIONS = [
|
|
|
885
893
|
description:
|
|
886
894
|
'Source identifier (default: WEB). Options: VSCODE, CLI, WEB, WEBVNC, WEBPTY, WEBIDE, CURSOR, etc.',
|
|
887
895
|
},
|
|
896
|
+
pool_type: {
|
|
897
|
+
type: 'string',
|
|
898
|
+
enum: ['ephemeral', 'persistent'],
|
|
899
|
+
description:
|
|
900
|
+
'Sandbox pool type (default ephemeral). ephemeral = non-persistent: idle auto-shutdown 30min + auto-delete 1h (platform scheduled task, verified) — save important data elsewhere; persistent = data disk retained, idle reclaim within 7 days. Each user may hold one instance per pool type (1+1). Explicit template_id overrides pool_type.',
|
|
901
|
+
},
|
|
888
902
|
template_id: { type: 'string', description: 'Template ID; overrides server default (only for new sandbox)' },
|
|
889
903
|
flavor_id: { type: 'string', description: 'Flavor ID; overrides server default (only for new sandbox)' },
|
|
890
904
|
env: { type: 'object', description: 'Environment variables to set in the sandbox (only for new sandbox)' },
|
|
@@ -1483,7 +1497,10 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1483
1497
|
const sandboxUser2 = args.username || 'root';
|
|
1484
1498
|
const sandboxTimeout2 = args.timeout_ms || 120000;
|
|
1485
1499
|
const sandboxResult2 = await execWithSession(sandboxWsId2, args.command, sandboxUser2, sandboxTimeout2);
|
|
1486
|
-
return
|
|
1500
|
+
return withPoolContext(sandboxWsId2, {
|
|
1501
|
+
stdout: sandboxResult2.stdout,
|
|
1502
|
+
exitCode: sandboxResult2.exitCode,
|
|
1503
|
+
});
|
|
1487
1504
|
}
|
|
1488
1505
|
case 'huaweicloud_sandbox_exec_one_shot': {
|
|
1489
1506
|
const sandboxWsId3 = args.workspace_id || getCurrentWorkspaceId();
|
|
@@ -1496,7 +1513,10 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1496
1513
|
const sandboxUser3 = args.username || 'root';
|
|
1497
1514
|
const sandboxTimeout3 = args.timeout_ms || 120000;
|
|
1498
1515
|
const sandboxResult3 = await execOneShot(sandboxWsId3, args.command, sandboxUser3, sandboxTimeout3);
|
|
1499
|
-
return
|
|
1516
|
+
return withPoolContext(sandboxWsId3, {
|
|
1517
|
+
stdout: sandboxResult3.stdout,
|
|
1518
|
+
exitCode: sandboxResult3.exitCode,
|
|
1519
|
+
});
|
|
1500
1520
|
}
|
|
1501
1521
|
case 'huaweicloud_sandbox_close_session': {
|
|
1502
1522
|
const sandboxWsId4 = args.workspace_id || getCurrentWorkspaceId();
|
|
@@ -1508,6 +1528,10 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1508
1528
|
}
|
|
1509
1529
|
const sandboxUser4 = args.username || 'root';
|
|
1510
1530
|
const closed = await closeSession(sandboxWsId4, sandboxUser4);
|
|
1531
|
+
if (closed) {
|
|
1532
|
+
// 池归属缓存随会话关闭清理:长驻进程防只增不减/陈旧池归属
|
|
1533
|
+
forgetWorkspacePool(sandboxWsId4);
|
|
1534
|
+
}
|
|
1511
1535
|
return closed ? 'ok' : 'not_connected';
|
|
1512
1536
|
}
|
|
1513
1537
|
case 'huaweicloud_sandbox_upload_file': {
|
|
@@ -1523,12 +1547,9 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1523
1547
|
}
|
|
1524
1548
|
const sandboxUser5 = args.username || 'root';
|
|
1525
1549
|
const sandboxTimeout5 = args.timeout_ms || 120000;
|
|
1526
|
-
return
|
|
1550
|
+
return withPoolContext(
|
|
1527
1551
|
sandboxWsId5,
|
|
1528
|
-
args.local_path,
|
|
1529
|
-
args.remote_path,
|
|
1530
|
-
sandboxUser5,
|
|
1531
|
-
sandboxTimeout5,
|
|
1552
|
+
await uploadFileWithSession(sandboxWsId5, args.local_path, args.remote_path, sandboxUser5, sandboxTimeout5),
|
|
1532
1553
|
);
|
|
1533
1554
|
}
|
|
1534
1555
|
case 'huaweicloud_sandbox_upload_project': {
|
|
@@ -1544,16 +1565,12 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1544
1565
|
}
|
|
1545
1566
|
const sandboxUser6 = args.username || 'root';
|
|
1546
1567
|
const sandboxTimeout6 = args.timeout_ms || 120000;
|
|
1547
|
-
return
|
|
1568
|
+
return withPoolContext(
|
|
1548
1569
|
sandboxWsId6,
|
|
1549
|
-
args.local_dir,
|
|
1550
|
-
args.remote_dir,
|
|
1551
|
-
sandboxUser6,
|
|
1552
|
-
sandboxTimeout6,
|
|
1553
|
-
{
|
|
1570
|
+
await uploadProjectWithSession(sandboxWsId6, args.local_dir, args.remote_dir, sandboxUser6, sandboxTimeout6, {
|
|
1554
1571
|
exclude: args.exclude,
|
|
1555
1572
|
extract: args.extract,
|
|
1556
|
-
},
|
|
1573
|
+
}),
|
|
1557
1574
|
);
|
|
1558
1575
|
}
|
|
1559
1576
|
case 'huaweicloud_sandbox_deploy_nginx': {
|
|
@@ -1569,19 +1586,22 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1569
1586
|
}
|
|
1570
1587
|
const sandboxUser7 = args.username || 'root';
|
|
1571
1588
|
const sandboxTimeout7 = args.timeout_ms || 60000;
|
|
1572
|
-
return
|
|
1589
|
+
return withPoolContext(
|
|
1573
1590
|
sandboxWsId7,
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1591
|
+
await deployNginx(
|
|
1592
|
+
sandboxWsId7,
|
|
1593
|
+
{
|
|
1594
|
+
nginxType: args.nginx_type,
|
|
1595
|
+
port: args.port,
|
|
1596
|
+
project: args.project,
|
|
1597
|
+
outputDir: args.output_dir,
|
|
1598
|
+
nodePort: args.node_port,
|
|
1599
|
+
publicPort: args.public_port,
|
|
1600
|
+
configName: args.config_name,
|
|
1601
|
+
},
|
|
1602
|
+
sandboxUser7,
|
|
1603
|
+
sandboxTimeout7,
|
|
1604
|
+
),
|
|
1585
1605
|
);
|
|
1586
1606
|
}
|
|
1587
1607
|
case 'huaweicloud_sandbox_deploy_check': {
|
|
@@ -1597,16 +1617,19 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1597
1617
|
}
|
|
1598
1618
|
const sandboxUser8 = args.username || 'root';
|
|
1599
1619
|
const sandboxTimeout8 = args.timeout_ms || 30000;
|
|
1600
|
-
return
|
|
1620
|
+
return withPoolContext(
|
|
1601
1621
|
sandboxWsId8,
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1622
|
+
await deployCheck(
|
|
1623
|
+
sandboxWsId8,
|
|
1624
|
+
{
|
|
1625
|
+
port: args.port,
|
|
1626
|
+
project: args.project,
|
|
1627
|
+
outputDir: args.output_dir,
|
|
1628
|
+
frameworkType: args.framework_type,
|
|
1629
|
+
},
|
|
1630
|
+
sandboxUser8,
|
|
1631
|
+
sandboxTimeout8,
|
|
1632
|
+
),
|
|
1610
1633
|
);
|
|
1611
1634
|
}
|
|
1612
1635
|
case 'huaweicloud_sandbox_expose_tunnel': {
|
|
@@ -1624,11 +1647,14 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1624
1647
|
}
|
|
1625
1648
|
const sandboxUser9 = args.username || 'root';
|
|
1626
1649
|
const sandboxTimeout9 = args.timeout_ms || 90000;
|
|
1627
|
-
return
|
|
1650
|
+
return withPoolContext(
|
|
1628
1651
|
sandboxWsId9,
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1652
|
+
await exposeTunnel(
|
|
1653
|
+
sandboxWsId9,
|
|
1654
|
+
{ port: args.port, expiryHours: args.expiry_hours, keepalive: args.keepalive },
|
|
1655
|
+
sandboxUser9,
|
|
1656
|
+
sandboxTimeout9,
|
|
1657
|
+
),
|
|
1632
1658
|
);
|
|
1633
1659
|
}
|
|
1634
1660
|
case 'huaweicloud_sandbox_renew_tunnel': {
|
|
@@ -1644,11 +1670,14 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1644
1670
|
'or set HW_WORKSPACE_ID environment variable before starting the agent.',
|
|
1645
1671
|
);
|
|
1646
1672
|
}
|
|
1647
|
-
return
|
|
1673
|
+
return withPoolContext(
|
|
1648
1674
|
sandboxWsId10,
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1675
|
+
await renewTunnel(
|
|
1676
|
+
sandboxWsId10,
|
|
1677
|
+
{ tunnelId: args.tunnel_id, expiryHours: args.expiry_hours },
|
|
1678
|
+
args.username || 'root',
|
|
1679
|
+
args.timeout_ms || 60000,
|
|
1680
|
+
),
|
|
1652
1681
|
);
|
|
1653
1682
|
}
|
|
1654
1683
|
case 'huaweicloud_sandbox_keepalive_watchdog': {
|
|
@@ -1665,11 +1694,14 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1665
1694
|
);
|
|
1666
1695
|
}
|
|
1667
1696
|
const tunnels11 = args.tunnels.map((t) => ({ tunnelId: t.tunnel_id || t.tunnelId, url: t.url }));
|
|
1668
|
-
return
|
|
1697
|
+
return withPoolContext(
|
|
1669
1698
|
sandboxWsId11,
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1699
|
+
await installKeepaliveWatchdog(
|
|
1700
|
+
sandboxWsId11,
|
|
1701
|
+
{ tunnels: tunnels11 },
|
|
1702
|
+
args.username || 'root',
|
|
1703
|
+
args.timeout_ms || 60000,
|
|
1704
|
+
),
|
|
1673
1705
|
);
|
|
1674
1706
|
}
|
|
1675
1707
|
case 'huaweicloud_sandbox_check_user':
|
|
@@ -1677,10 +1709,45 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1677
1709
|
case 'huaweicloud_sandbox_sign_agreement':
|
|
1678
1710
|
return await hdkitSignAgreement();
|
|
1679
1711
|
case 'huaweicloud_sandbox_connect': {
|
|
1680
|
-
|
|
1712
|
+
// 两池:客户端校验 pool_type(非法值在本地即报错,不发无效请求);未传交由服务端默认 ephemeral
|
|
1713
|
+
const connectArgs = { ...args };
|
|
1714
|
+
let poolType;
|
|
1715
|
+
try {
|
|
1716
|
+
poolType = connectArgs.pool_type !== undefined ? validatePoolType(connectArgs.pool_type) : null;
|
|
1717
|
+
if (poolType) connectArgs.pool_type = poolType;
|
|
1718
|
+
} catch (error) {
|
|
1719
|
+
// 非法 pool_type 结构化返回:MCP 错误信封只携带 code+message,指引字段在 throw 路径会丢失(参照 voucher 模式)
|
|
1720
|
+
return { ok: false, code: 'HDKIT_INVALID_POOL_TYPE', message: error.message };
|
|
1721
|
+
}
|
|
1722
|
+
let connectResult;
|
|
1723
|
+
try {
|
|
1724
|
+
connectResult = await hdkitConnect(connectArgs);
|
|
1725
|
+
} catch (error) {
|
|
1726
|
+
// 业务预期失败(429 双码 / 烧核 / 凭证失效)转结构化返回,remediation 才能穿透 MCP 边界;
|
|
1727
|
+
// 其余错误维持抛出(MCP 层透传 code+message)
|
|
1728
|
+
if (
|
|
1729
|
+
error?.code === 'HDKIT_QUOTA_EXCEEDED' ||
|
|
1730
|
+
error?.code === 'HDKIT_CAPACITY_INSUFFICIENT' ||
|
|
1731
|
+
error?.code === 'HDKIT_CRED_INVALID' ||
|
|
1732
|
+
error?.coreHoursExhausted
|
|
1733
|
+
) {
|
|
1734
|
+
return {
|
|
1735
|
+
ok: false,
|
|
1736
|
+
code: error.code,
|
|
1737
|
+
message: error.message,
|
|
1738
|
+
...(error.retryAfter !== undefined ? { retryAfter: error.retryAfter } : {}),
|
|
1739
|
+
...(error.coreHoursExhausted ? { coreHoursExhausted: true } : {}),
|
|
1740
|
+
...(error.remediation ? { remediation: error.remediation } : {}),
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
throw error;
|
|
1744
|
+
}
|
|
1681
1745
|
const devStageId = connectResult?.dev_stage_id || connectResult?.devStageId;
|
|
1682
1746
|
if (devStageId) {
|
|
1683
1747
|
setWorkspaceId(devStageId);
|
|
1748
|
+
// 池归属缓存:exec/upload/deploy 等工具返回值据此附带池模式提示(缺口#2)
|
|
1749
|
+
// 双读兜底与 devStageId 同型(防服务端 wire 命名漂移,QA #1082 配对复审教训)
|
|
1750
|
+
rememberWorkspacePool(devStageId, connectResult?.pool_type || connectResult?.poolType);
|
|
1684
1751
|
try {
|
|
1685
1752
|
await execOneShot(devStageId, 'devbridge delete-all 2>/dev/null || true', 'root', 15000);
|
|
1686
1753
|
} catch {}
|
|
@@ -1694,7 +1761,8 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1694
1761
|
} catch {}
|
|
1695
1762
|
await transferGitRepo(args, devStageId, connectResult);
|
|
1696
1763
|
}
|
|
1697
|
-
|
|
1764
|
+
// TTL 播报(缺口#1):服务端回显 pool_type 才附带生命周期提示
|
|
1765
|
+
return enrichConnectResult(connectResult);
|
|
1698
1766
|
}
|
|
1699
1767
|
case 'huaweicloud_sandbox_credentials': {
|
|
1700
1768
|
const devStageId = args.dev_stage_id || getCurrentWorkspaceId();
|
|
@@ -1785,10 +1853,17 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
|
1785
1853
|
}
|
|
1786
1854
|
case 'huaweicloud_voucher_status':
|
|
1787
1855
|
return await hdkitVoucherStatus(args.domain_id);
|
|
1788
|
-
case 'huaweicloud_voucher_claim':
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1856
|
+
case 'huaweicloud_voucher_claim': {
|
|
1857
|
+
const result = await hdkitVoucherClaim(args.domain_id);
|
|
1858
|
+
// #1014 P0: 领券成功后补缓存 userHash(响应携带时),flush 前兜底补齐队列缺失字段。
|
|
1859
|
+
if (result?.userHash) cacheUserHash(result.userHash);
|
|
1860
|
+
return result;
|
|
1861
|
+
}
|
|
1862
|
+
case 'huaweicloud_competition_voucher_claim': {
|
|
1863
|
+
const result = await hdkitCompetitionVoucherClaim(args.domain_id);
|
|
1864
|
+
if (result?.userHash) cacheUserHash(result.userHash);
|
|
1865
|
+
return result;
|
|
1866
|
+
}
|
|
1792
1867
|
case 'huaweicloud_check_update':
|
|
1793
1868
|
return await handleCheckUpdate(args, { sessionId: opts?.sessionId, doQuery: opts?.doQuery });
|
|
1794
1869
|
case 'huaweicloud_upgrade':
|