huaweicloud-devkit 1.1.7 → 1.1.8-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/package.json +2 -1
  4. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
  5. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
  6. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
  7. package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +1 -1
  8. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +1 -1
  9. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +19 -1
  10. package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
  11. package/plugins/huaweicloud-core/safety/policy.json +1 -1
  12. package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +57 -2
  13. package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +19 -16
  14. package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +1 -1
  15. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +6 -0
  16. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +1 -1
  17. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +7 -1
  18. package/plugins/huaweicloud-core/src/auth/service.mjs +36 -0
  19. package/plugins/huaweicloud-core/src/detect-framework.mjs +41 -5
  20. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +46 -7
  21. package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +94 -1
  22. package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +1 -1
  23. package/plugins/huaweicloud-core/src/safety-policy.mjs +165 -33
  24. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +181 -10
  25. package/plugins/huaweicloud-core/src/search-market.mjs +40 -1
  26. package/plugins/huaweicloud-core/src/setup-cli.mjs +34 -0
  27. package/plugins/huaweicloud-core/src/tools.mjs +266 -17
  28. package/rules/huawei-agent-rules.mdc +78 -0
@@ -566,6 +566,20 @@ function copyDir(src, dest) {
566
566
  }
567
567
  }
568
568
 
569
+ // Copy the global agent rules file (rules/huawei-agent-rules.mdc) into the
570
+ // plugin destination so installed agents can load behavioral constraints
571
+ // (#758 D4-23). Idempotent — safe to call on install and update.
572
+ const AGENT_RULES_FILE = 'huawei-agent-rules.mdc';
573
+
574
+ function injectAgentRules(pluginDest) {
575
+ const rulesSrc = join(PACKAGE_ROOT, 'rules', AGENT_RULES_FILE);
576
+ if (!existsSync(rulesSrc)) return;
577
+ const rulesDestDir = join(pluginDest, 'rules');
578
+ mkdirSync(rulesDestDir, { recursive: true });
579
+ copyFileVerified(rulesSrc, join(rulesDestDir, AGENT_RULES_FILE));
580
+ console.log(` Agent Rules -> ${join(rulesDestDir, AGENT_RULES_FILE)}`);
581
+ }
582
+
569
583
  function installRuntimeDeps(pluginsDir) {
570
584
  const pkgJson = {
571
585
  name: 'huaweicloud-devkit',
@@ -866,6 +880,7 @@ async function installOpenCode() {
866
880
  console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
867
881
  copyDir(safetyDir, join(pluginDest, 'safety'));
868
882
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
883
+ injectAgentRules(pluginDest);
869
884
  const opcPlugins = join(configRoot('opencode'), 'plugins');
870
885
  mkdirSync(opcPlugins, { recursive: true });
871
886
  copyFileSync(pluginSrc, join(opcPlugins, 'skill-tracker.js'));
@@ -945,6 +960,7 @@ async function updateOpenCode() {
945
960
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
946
961
  copyDir(safetyDir, join(pluginDest, 'safety'));
947
962
  console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
963
+ injectAgentRules(pluginDest);
948
964
  const opcPlugins = join(configRoot('opencode'), 'plugins');
949
965
  mkdirSync(opcPlugins, { recursive: true });
950
966
  copyFileSync(pluginSrc, join(opcPlugins, 'skill-tracker.js'));
@@ -1045,6 +1061,7 @@ async function installOpenClaw() {
1045
1061
  console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1046
1062
  copyDir(safetyDir, join(pluginDest, 'safety'));
1047
1063
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1064
+ injectAgentRules(pluginDest);
1048
1065
 
1049
1066
  const mcpServerAbsPath = join(pluginDest, 'src', 'mcp-server.mjs').replace(/\\/g, '/');
1050
1067
  writeMcpServersFile(pluginDest, mcpServerAbsPath, 'openclaw');
@@ -1105,6 +1122,7 @@ async function updateOpenClaw() {
1105
1122
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1106
1123
  copyDir(safetyDir, join(pluginDest, 'safety'));
1107
1124
  console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1125
+ injectAgentRules(pluginDest);
1108
1126
 
1109
1127
  const mcpServerAbsPath = join(pluginDest, 'src', 'mcp-server.mjs').replace(/\\/g, '/');
1110
1128
  writeMcpServersFile(pluginDest, mcpServerAbsPath, 'openclaw');
@@ -1135,6 +1153,7 @@ async function installCodexDesktop() {
1135
1153
  console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1136
1154
  copyDir(safetyDir, join(pluginDest, 'safety'));
1137
1155
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1156
+ injectAgentRules(pluginDest);
1138
1157
 
1139
1158
  // Copy assets (icons, logos) for Codex Desktop plugin UI
1140
1159
  const codexAssetsSrc = join(PLUGIN_ROOT, 'assets');
@@ -1193,6 +1212,7 @@ async function updateCodexDesktop() {
1193
1212
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1194
1213
  copyDir(safetyDir, join(pluginDest, 'safety'));
1195
1214
  console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1215
+ injectAgentRules(pluginDest);
1196
1216
 
1197
1217
  // Copy assets (icons, logos) for Codex Desktop plugin UI
1198
1218
  const codexAssetsSrc = join(PLUGIN_ROOT, 'assets');
@@ -1328,6 +1348,7 @@ async function installCodeArts() {
1328
1348
  console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1329
1349
  copyDir(safetyDir, join(pluginDest, 'safety'));
1330
1350
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1351
+ injectAgentRules(pluginDest);
1331
1352
 
1332
1353
  const codeartsHookDir = join(homedir(), '.codeartsdoer', 'plugins');
1333
1354
  mkdirSync(codeartsHookDir, { recursive: true });
@@ -1357,6 +1378,7 @@ async function updateCodeArts() {
1357
1378
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1358
1379
  copyDir(safetyDir, join(pluginDest, 'safety'));
1359
1380
  console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1381
+ injectAgentRules(pluginDest);
1360
1382
 
1361
1383
  const codeartsHookDir = join(homedir(), '.codeartsdoer', 'plugins');
1362
1384
  mkdirSync(codeartsHookDir, { recursive: true });
@@ -1512,6 +1534,7 @@ async function installCodeArtsWork() {
1512
1534
  console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1513
1535
  copyDir(safetyDir, join(pluginDest, 'safety'));
1514
1536
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1537
+ injectAgentRules(pluginDest);
1515
1538
 
1516
1539
  registerCodeartsWorkMcp();
1517
1540
  installRuntimeDeps(pluginDest);
@@ -1529,6 +1552,7 @@ async function updateCodeArtsWork() {
1529
1552
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1530
1553
  copyDir(safetyDir, join(pluginDest, 'safety'));
1531
1554
  console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1555
+ injectAgentRules(pluginDest);
1532
1556
  registerCodeartsWorkMcp();
1533
1557
  mkdirSync(pluginDest, { recursive: true });
1534
1558
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
@@ -1792,6 +1816,7 @@ async function installWorkBuddy() {
1792
1816
  console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1793
1817
  copyDir(safetyDir, join(pluginDest, 'safety'));
1794
1818
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
1819
+ injectAgentRules(pluginDest);
1795
1820
 
1796
1821
  ensureWorkbuddyMcpConfig();
1797
1822
  installRuntimeDeps(pluginDest);
@@ -1814,6 +1839,7 @@ async function updateWorkBuddy() {
1814
1839
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
1815
1840
  copyDir(safetyDir, join(pluginDest, 'safety'));
1816
1841
  console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
1842
+ injectAgentRules(pluginDest);
1817
1843
  ensureWorkbuddyMcpConfig();
1818
1844
  mkdirSync(pluginDest, { recursive: true });
1819
1845
  writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
@@ -1985,6 +2011,7 @@ async function installAtomCode() {
1985
2011
  console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
1986
2012
  copyDir(safetyDir, join(pluginDest, 'safety'));
1987
2013
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
2014
+ injectAgentRules(pluginDest);
1988
2015
 
1989
2016
  ensureAtomcodeMcpConfig();
1990
2017
  deployAtomcodeHooks();
@@ -2005,6 +2032,7 @@ async function updateAtomCode() {
2005
2032
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
2006
2033
  copyDir(safetyDir, join(pluginDest, 'safety'));
2007
2034
  console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
2035
+ injectAgentRules(pluginDest);
2008
2036
  ensureAtomcodeMcpConfig();
2009
2037
  deployAtomcodeHooks();
2010
2038
  mkdirSync(pluginDest, { recursive: true });
@@ -2294,6 +2322,7 @@ async function installDsh() {
2294
2322
  console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
2295
2323
  copyDir(safetyDir, join(pluginDest, 'safety'));
2296
2324
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
2325
+ injectAgentRules(pluginDest);
2297
2326
  copyFileSync(hookSrc, join(pluginDest, 'hook-plugin.mjs'));
2298
2327
  console.log(` Hook Plugin -> ${join(pluginDest, 'hook-plugin.mjs')}`);
2299
2328
  ensureDshMcpPatch();
@@ -2317,6 +2346,7 @@ async function updateDsh() {
2317
2346
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
2318
2347
  copyDir(safetyDir, join(pluginDest, 'safety'));
2319
2348
  console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
2349
+ injectAgentRules(pluginDest);
2320
2350
  copyFileSync(hookSrc, join(pluginDest, 'hook-plugin.mjs'));
2321
2351
  console.log(` Hook Plugin updated -> ${join(pluginDest, 'hook-plugin.mjs')}`);
2322
2352
  ensureDshMcpPatch();
@@ -2425,6 +2455,7 @@ async function installOfficeAce() {
2425
2455
  console.log(` MCP Server -> ${join(pluginDest, 'src')}`);
2426
2456
  copyDir(safetyDir, join(pluginDest, 'safety'));
2427
2457
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
2458
+ injectAgentRules(pluginDest);
2428
2459
 
2429
2460
  installRuntimeDeps(pluginDest);
2430
2461
  if (ensureOfficeaceMcpInSqlite() === 'owner-missing') {
@@ -2453,6 +2484,7 @@ async function updateOfficeAce() {
2453
2484
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
2454
2485
  copyDir(safetyDir, join(pluginDest, 'safety'));
2455
2486
  console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
2487
+ injectAgentRules(pluginDest);
2456
2488
  installRuntimeDeps(pluginDest);
2457
2489
  if (ensureOfficeaceMcpInSqlite() === 'owner-missing') {
2458
2490
  console.log(
@@ -2950,6 +2982,7 @@ async function installHermes() {
2950
2982
  }
2951
2983
  copyDir(safetyDir, join(pluginDest, 'safety'));
2952
2984
  console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
2985
+ injectAgentRules(pluginDest);
2953
2986
  copyDir(hooksDir, join(pluginDest, 'hooks'));
2954
2987
  console.log(` Safety Hooks -> ${join(pluginDest, 'hooks')}`);
2955
2988
  // Telemetry hook lives in integrations/hermes/hooks/ (platform-specific adapter)
@@ -2981,6 +3014,7 @@ async function updateHermes() {
2981
3014
  console.log(` MCP Server updated -> ${join(pluginDest, 'src')}`);
2982
3015
  copyDir(safetyDir, join(pluginDest, 'safety'));
2983
3016
  console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
3017
+ injectAgentRules(pluginDest);
2984
3018
  copyDir(hooksDir, join(pluginDest, 'hooks'));
2985
3019
  console.log(` Safety Hooks updated -> ${join(pluginDest, 'hooks')}`);
2986
3020
  // Telemetry hook lives in integrations/hermes/hooks/ (platform-specific adapter)
@@ -20,6 +20,7 @@ import {
20
20
  uploadProjectWithSession,
21
21
  deployNginx,
22
22
  deployCheck,
23
+ exposeTunnel,
23
24
  getCurrentWorkspaceId,
24
25
  setWorkspaceId,
25
26
  } from './sandbox/session-manager.mjs';
@@ -46,6 +47,7 @@ import {
46
47
  writeLastSync,
47
48
  readCodeArtsCredentials,
48
49
  globalCredentialsPath,
50
+ parseStsExpiry,
49
51
  resolveCredentialsWithRuntime,
50
52
  } from './auth/credentials.mjs';
51
53
  import { trackToolInvoke, trackSkillRetrieve, clearUserHash } from './telemetry/telemetry.mjs';
@@ -734,6 +736,29 @@ export const TOOL_DEFINITIONS = [
734
736
  },
735
737
  },
736
738
  },
739
+ {
740
+ name: 'huaweicloud_sandbox_expose_tunnel',
741
+ description:
742
+ 'Expose a port on the sandbox to a public DevBridge tunnel URL (devbridge 0.2.x). Authenticates automatically (probing AK/SK vs API-Key builds, using /tmp/hw_creds.sh and /tmp/hw_api_key as injected by huaweicloud_sandbox_credentials), pre-cleans stale tunnels to avoid quota errors, starts the host, extracts the public URL (https://<id>-<port>.devbridge-s2.hwtunnel.com), and health-checks it before returning. Use the ACTUAL port reported by huaweicloud_sandbox_deploy_nginx (its "port" field auto-increments on conflict) — never the originally requested port. This automates the manual "Expose via DevBridge" 5-step flow. Returns ok, publicUrl, port, tunnelId, and warnings. If the API Key is missing on a release build it returns ok:false with instructions to create one.',
743
+ inputSchema: {
744
+ type: 'object',
745
+ required: ['port'],
746
+ properties: {
747
+ port: {
748
+ type: 'number',
749
+ description:
750
+ 'Actual port nginx is listening on — use the "port" value returned by huaweicloud_sandbox_deploy_nginx, not the originally requested port.',
751
+ },
752
+ workspace_id: {
753
+ type: 'string',
754
+ description:
755
+ 'Workspace ID from huaweicloud_sandbox_connect return value. Required - must be passed explicitly when HW_WORKSPACE_ID is not set.',
756
+ },
757
+ username: { type: 'string', description: 'Login username (default: root)' },
758
+ timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 90000)' },
759
+ },
760
+ },
761
+ },
737
762
  {
738
763
  name: 'huaweicloud_sandbox_check_user',
739
764
  description:
@@ -1404,6 +1429,23 @@ export async function callTool(name, rawArgs = {}, opts = {}) {
1404
1429
  sandboxTimeout8,
1405
1430
  );
1406
1431
  }
1432
+ case 'huaweicloud_sandbox_expose_tunnel': {
1433
+ if (!args.port) {
1434
+ throw new Error(
1435
+ 'port is required. Use the ACTUAL "port" value returned by huaweicloud_sandbox_deploy_nginx (it may have been auto-incremented on a port conflict).',
1436
+ );
1437
+ }
1438
+ const sandboxWsId9 = args.workspace_id || getCurrentWorkspaceId();
1439
+ if (!sandboxWsId9) {
1440
+ throw new Error(
1441
+ 'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
1442
+ 'or set HW_WORKSPACE_ID environment variable before starting the agent.',
1443
+ );
1444
+ }
1445
+ const sandboxUser9 = args.username || 'root';
1446
+ const sandboxTimeout9 = args.timeout_ms || 90000;
1447
+ return await exposeTunnel(sandboxWsId9, { port: args.port }, sandboxUser9, sandboxTimeout9);
1448
+ }
1407
1449
  case 'huaweicloud_sandbox_check_user':
1408
1450
  return await hdkitCheckUser();
1409
1451
  case 'huaweicloud_sandbox_sign_agreement':
@@ -1750,11 +1792,80 @@ const SERVICE_EXAMPLES = {
1750
1792
  DCS: { list: 'DCS ListInstances', create: 'DCS CreateInstance', show: 'DCS ShowInstance' },
1751
1793
  };
1752
1794
 
1795
+ // DMS and DEW are Huawei Cloud aggregate service names that do not exist as
1796
+ // single KooCLI service identifiers. Each maps to real KooCLI sub-services.
1797
+ const AGGREGATE_SERVICE_MAP = {
1798
+ DMS: ['Kafka', 'RabbitMQ', 'RocketMQ'],
1799
+ DEW: ['KMS', 'CSMS'],
1800
+ };
1801
+
1802
+ // Identity-class notes per service. BSS leads: its operations are split between
1803
+ // Customer-level APIs (work with a normal IAM user) and Partner-level APIs
1804
+ // (only valid for a partner/dealer identity). Calling a Partner-level API with a
1805
+ // customer credential returns APIGW.0301 even when AK/SK are perfectly valid —
1806
+ // the common false-positive diagnosis is "cli-domain-id missing".
1807
+ const SERVICE_IDENTITY_NOTES = {
1808
+ BSS: [
1809
+ {
1810
+ identity: 'Customer-level (normal IAM user)',
1811
+ apis: [
1812
+ 'ShowCustomerAccountBalances',
1813
+ 'ListCustomerBillsFeeRecords',
1814
+ 'ListCustomerCouponChangeRecords',
1815
+ 'ListResourceUsage',
1816
+ 'ListCosts',
1817
+ ],
1818
+ note: 'Work with a normal IAM user that has BSS Administrator or Finance role.',
1819
+ },
1820
+ {
1821
+ identity: 'Partner-level (partner/dealer only)',
1822
+ apis: ['ListQuotaCoupons', 'ListSubCustomerCoupons', 'ListSubCustomerBillDetail'],
1823
+ note: 'APIGW.0301 here means the caller identity is not a partner — NOT bad AK/SK or a missing cli-domain-id.',
1824
+ },
1825
+ ],
1826
+ };
1827
+
1753
1828
  async function listOperations(service, options = {}) {
1754
1829
  const serviceName = String(service || '').trim();
1755
1830
  if (!/^[A-Za-z][A-Za-z0-9-]{1,63}$/.test(serviceName)) {
1756
1831
  throw new Error('service must be a KooCLI service name such as ECS, VPC, IMS, OBS, RDS, or CDN.');
1757
1832
  }
1833
+ const upperName = serviceName.toUpperCase();
1834
+ const subServices = AGGREGATE_SERVICE_MAP[upperName];
1835
+ if (subServices && subServices.length) {
1836
+ const results = await Promise.all(
1837
+ subServices.map(async (sub) => {
1838
+ const r = await runHcloud([sub, '--help'], {
1839
+ timeoutMs: options.timeoutMs,
1840
+ maxRetries: 0,
1841
+ });
1842
+ let result = r;
1843
+ if (!result.ok) {
1844
+ result = await runHcloud([sub, 'help'], {
1845
+ timeoutMs: options.timeoutMs,
1846
+ maxRetries: 0,
1847
+ });
1848
+ }
1849
+ return {
1850
+ subService: sub,
1851
+ command: `hcloud ${sub} --help`,
1852
+ examples: SERVICE_EXAMPLES[sub.toUpperCase()] || {
1853
+ note: `No cached examples for ${sub}. Use the help text above to discover available operations.`,
1854
+ },
1855
+ result,
1856
+ };
1857
+ }),
1858
+ );
1859
+ return {
1860
+ service: serviceName,
1861
+ aggregate: true,
1862
+ aggregatedFrom: subServices,
1863
+ selectionRule:
1864
+ 'DMS/DEW are aggregate service names. Use each sub-service help text below to select the exact KooCLI operation name.',
1865
+ identityNotes: SERVICE_IDENTITY_NOTES[upperName] || null,
1866
+ subServices: results,
1867
+ };
1868
+ }
1758
1869
  const isObs = /^obs$/i.test(serviceName);
1759
1870
  const svc = isObs ? 'obs' : serviceName;
1760
1871
  const args = isObs ? ['obs', 'help'] : [svc, '--help'];
@@ -1771,8 +1882,10 @@ async function listOperations(service, options = {}) {
1771
1882
  return {
1772
1883
  service: serviceName,
1773
1884
  command: isObs ? 'hcloud obs help' : `hcloud ${svc} --help`,
1774
- selectionRule: 'Use this help text to select the exact KooCLI operation name before planning any service command.',
1775
- examples: SERVICE_EXAMPLES[serviceName.toUpperCase()] || {
1885
+ selectionRule:
1886
+ 'Use this help text to select the exact KooCLI operation name before planning any service command. After selecting an operation, run "hcloud <Service> <Operation> --help" to confirm exact parameter names before constructing the command.',
1887
+ identityNotes: SERVICE_IDENTITY_NOTES[upperName] || null,
1888
+ examples: SERVICE_EXAMPLES[upperName] || {
1776
1889
  note: `No cached examples for ${serviceName}. Use the help text above to discover available operations.`,
1777
1890
  },
1778
1891
  result,
@@ -1816,22 +1929,79 @@ function serviceCatalog(intent = '') {
1816
1929
  const it = String(intent).toLowerCase();
1817
1930
  const routeMap = [
1818
1931
  {
1819
- keywords: ['ecs', 'server', 'vm', 'instance', 'compute', 'flavor', 'image'],
1932
+ keywords: [
1933
+ 'ecs',
1934
+ 'server',
1935
+ 'vm',
1936
+ 'instance',
1937
+ 'compute',
1938
+ 'flavor',
1939
+ 'image',
1940
+ '弹性云服务器',
1941
+ '云服务器',
1942
+ '服务器',
1943
+ '虚拟机',
1944
+ '镜像',
1945
+ ],
1820
1946
  skills: ['huawei-ecs'],
1821
1947
  services: ['ECS'],
1822
1948
  },
1823
1949
  {
1824
- keywords: ['vpc', 'subnet', 'network', 'security group', 'eip', 'nat', 'vpn', 'bandwidth'],
1950
+ keywords: [
1951
+ 'vpc',
1952
+ 'subnet',
1953
+ 'network',
1954
+ 'security group',
1955
+ 'eip',
1956
+ 'nat',
1957
+ 'vpn',
1958
+ 'bandwidth',
1959
+ '虚拟私有云',
1960
+ '子网',
1961
+ '网络',
1962
+ '安全组',
1963
+ '弹性公网IP',
1964
+ 'NAT网关',
1965
+ 'VPN网关',
1966
+ '带宽',
1967
+ ],
1825
1968
  skills: ['huawei-vpc'],
1826
1969
  services: ['VPC', 'EIP'],
1827
1970
  },
1828
1971
  {
1829
- keywords: ['obs', 'bucket', 'storage', 'object', 'static website', 'static site', 'hosting'],
1972
+ keywords: [
1973
+ 'obs',
1974
+ 'bucket',
1975
+ 'storage',
1976
+ 'object',
1977
+ 'static website',
1978
+ 'static site',
1979
+ 'hosting',
1980
+ '对象存储',
1981
+ '存储桶',
1982
+ '桶',
1983
+ '存储',
1984
+ '对象',
1985
+ '静态网站',
1986
+ '静态托管',
1987
+ ],
1830
1988
  skills: ['huawei-obs'],
1831
1989
  services: ['OBS'],
1832
1990
  },
1833
1991
  {
1834
- keywords: ['functiongraph', 'serverless', 'function', 'lambda', 'trigger', 'faas'],
1992
+ keywords: [
1993
+ 'functiongraph',
1994
+ 'serverless',
1995
+ 'function',
1996
+ 'lambda',
1997
+ 'trigger',
1998
+ 'faas',
1999
+ '函数工作流',
2000
+ '函数',
2001
+ '触发器',
2002
+ '无服务器',
2003
+ '函数计算',
2004
+ ],
1835
2005
  skills: ['huawei-functiongraph'],
1836
2006
  services: ['FunctionGraph'],
1837
2007
  },
@@ -1840,20 +2010,60 @@ function serviceCatalog(intent = '') {
1840
2010
  skills: ['huawei-cce'],
1841
2011
  services: ['CCE', 'SWR'],
1842
2012
  },
1843
- { keywords: ['apig', 'api gateway', 'publish', 'throttle'], skills: ['huawei-apig'], services: ['APIG'] },
1844
- { keywords: ['rds', 'mysql', 'postgresql', 'database', 'db'], skills: ['huawei-rds'], services: ['RDS'] },
1845
2013
  {
1846
- keywords: ['gaussdb', 'distributed', 'sharding', 'opengauss'],
2014
+ keywords: ['apig', 'api gateway', 'publish', 'throttle', 'API网关', '限流'],
2015
+ skills: ['huawei-apig'],
2016
+ services: ['APIG'],
2017
+ },
2018
+ {
2019
+ keywords: ['rds', 'mysql', 'postgresql', 'database', 'db', '关系型数据库', '数据库'],
2020
+ skills: ['huawei-rds'],
2021
+ services: ['RDS'],
2022
+ },
2023
+ {
2024
+ keywords: ['gaussdb', 'distributed', 'sharding', 'opengauss', '高斯数据库', '分布式数据库'],
1847
2025
  skills: ['huawei-gaussdb'],
1848
2026
  services: ['GaussDB'],
1849
2027
  },
1850
2028
  {
1851
- keywords: ['iam', 'permission', 'policy', 'role', 'user', 'ak/sk', 'access key', 'agency'],
2029
+ keywords: [
2030
+ 'iam',
2031
+ 'permission',
2032
+ 'policy',
2033
+ 'role',
2034
+ 'user',
2035
+ 'ak/sk',
2036
+ 'access key',
2037
+ 'agency',
2038
+ '身份认证',
2039
+ '身份管理',
2040
+ '权限',
2041
+ '策略',
2042
+ '角色',
2043
+ '用户',
2044
+ '访问密钥',
2045
+ '委托',
2046
+ ],
1852
2047
  skills: ['huawei-iam'],
1853
2048
  services: ['IAM'],
1854
2049
  },
1855
2050
  {
1856
- keywords: ['dew', 'secret', 'kms', 'encrypt', 'decrypt', 'certificate', 'csms'],
2051
+ keywords: [
2052
+ 'dew',
2053
+ 'secret',
2054
+ 'kms',
2055
+ 'encrypt',
2056
+ 'decrypt',
2057
+ 'certificate',
2058
+ 'csms',
2059
+ '数据加密服务',
2060
+ '密钥管理',
2061
+ '密钥',
2062
+ '加密',
2063
+ '解密',
2064
+ '证书',
2065
+ '凭据管理',
2066
+ ],
1857
2067
  skills: ['huawei-dew'],
1858
2068
  services: ['CSMS', 'KMS'],
1859
2069
  },
@@ -1863,7 +2073,7 @@ function serviceCatalog(intent = '') {
1863
2073
  services: ['ModelArts'],
1864
2074
  },
1865
2075
  {
1866
- keywords: ['billing', 'cost', 'bill', 'budget', 'expense', 'bss'],
2076
+ keywords: ['billing', 'cost', 'bill', 'budget', 'expense', 'bss', '计费', '费用', '账单', '预算', '开销'],
1867
2077
  skills: ['huawei-billing'],
1868
2078
  services: ['BSS'],
1869
2079
  },
@@ -1878,12 +2088,16 @@ function serviceCatalog(intent = '') {
1878
2088
  services: ['SMN', 'DMS'],
1879
2089
  },
1880
2090
  {
1881
- keywords: ['ces', 'monitor', 'alarm', 'metric', 'dashboard', 'cloud eye'],
2091
+ keywords: ['ces', 'monitor', 'alarm', 'metric', 'dashboard', 'cloud eye', '监控', '告警', '云监控'],
1882
2092
  skills: ['huawei-cloud-eye'],
1883
2093
  services: ['CES'],
1884
2094
  },
1885
- { keywords: ['cts', 'audit', 'trace', 'tracker'], skills: ['huawei-cts'], services: ['CTS'] },
1886
- { keywords: ['cbr', 'backup', 'restore', 'vault', 'snapshot'], skills: ['huawei-cbr'], services: ['CBR'] },
2095
+ { keywords: ['cts', 'audit', 'trace', 'tracker', '审计', '追踪'], skills: ['huawei-cts'], services: ['CTS'] },
2096
+ {
2097
+ keywords: ['cbr', 'backup', 'restore', 'vault', 'snapshot', '备份', '恢复', '快照', '存储库'],
2098
+ skills: ['huawei-cbr'],
2099
+ services: ['CBR'],
2100
+ },
1887
2101
  {
1888
2102
  keywords: ['deployment', 'deploy', 'ci/cd', 'pipeline', 'release'],
1889
2103
  skills: ['huawei-deployment'],
@@ -1909,10 +2123,26 @@ function serviceCatalog(intent = '') {
1909
2123
  services: ['Sandbox', 'DevStation'],
1910
2124
  },
1911
2125
  {
1912
- keywords: ['dds', 'dcs', 'mongodb', 'redis', 'memcached', 'cache', 'document db'],
2126
+ keywords: [
2127
+ 'dds',
2128
+ 'dcs',
2129
+ 'mongodb',
2130
+ 'redis',
2131
+ 'memcached',
2132
+ 'cache',
2133
+ 'document db',
2134
+ '文档数据库',
2135
+ '缓存',
2136
+ '内存数据库',
2137
+ ],
1913
2138
  skills: ['huawei-dds-dcs'],
1914
2139
  services: ['DDS', 'DCS'],
1915
2140
  },
2141
+ {
2142
+ keywords: ['elb', 'load balancer', '负载均衡', '弹性负载均衡', '监听器', '证书'],
2143
+ skills: ['huawei-vpc'],
2144
+ services: ['ELB'],
2145
+ },
1916
2146
  {
1917
2147
  keywords: ['voucher', 'coupon', 'incentive', 'credit', '领券', '代金券', '优惠券', '激励金', '领取'],
1918
2148
  skills: ['huawei-voucher'],
@@ -1923,7 +2153,9 @@ function serviceCatalog(intent = '') {
1923
2153
  const tokens = new Set(it.split(/[\s,./-]+/).filter((t) => t.length > 0));
1924
2154
  const cjk = /[\u4e00-\u9fff]/;
1925
2155
  for (const route of routeMap) {
1926
- if (route.keywords.some((kw) => (kw.includes(' ') || cjk.test(kw) ? it.includes(kw) : tokens.has(kw)))) {
2156
+ if (
2157
+ route.keywords.some((kw) => (kw.includes(' ') || cjk.test(kw) ? it.includes(kw.toLowerCase()) : tokens.has(kw)))
2158
+ ) {
1927
2159
  matched.push(route);
1928
2160
  }
1929
2161
  }
@@ -2016,6 +2248,23 @@ function explainError({ service = 'unknown', errorCode = '', message = '', reque
2016
2248
  }
2017
2249
  }
2018
2250
 
2251
+ // G: when the active credential set is a temporary STS token that has already
2252
+ // expired, surface that as the primary cause before generic auth guidance.
2253
+ let activeCreds;
2254
+ try {
2255
+ activeCreds = resolveCredentialsWithRuntime({ allowMissing: true });
2256
+ } catch {
2257
+ activeCreds = null;
2258
+ }
2259
+ if (activeCreds?.ak && activeCreds?.sk && activeCreds.securityToken) {
2260
+ const expiry = parseStsExpiry({ securityToken: activeCreds.securityToken });
2261
+ if (expiry !== null && expiry <= Date.now()) {
2262
+ suggestions.push(
2263
+ 'CREDENTIAL_EXPIRED: the temporary STS security token has expired. Re-authenticate to obtain fresh credentials (e.g. run "npx huaweicloud-devkit auth init" or re-login / restart the session).',
2264
+ );
2265
+ }
2266
+ }
2267
+
2019
2268
  if (/auth|token|credential|ak|sk|401|403|unauthorized|forbidden|Incorrect IAM/i.test(combined)) {
2020
2269
  if (svc === 'obs') {
2021
2270
  suggestions.push(
@@ -0,0 +1,78 @@
1
+ # HuaweiCloud DevKit — Agent Rules
2
+
3
+ > Agent 加载插件时自动读取,定义全局行为约束。
4
+
5
+ ---
6
+
7
+ ## Core Principles
8
+
9
+ - **Always use the Huawei MCP server first** (sandboxed, observable, auditable). Fall back to `hcloud` CLI only when MCP is unavailable.
10
+ - **Before any task, check for available skills.** Use `huaweicloud_search_docs` or `huaweicloud_retrieve_skill` via MCP. Follow skill procedures over general knowledge.
11
+ - **When uncertain** about API parameters, quotas, limits, or error codes — search documentation via MCP. Do not guess.
12
+ - **Prefer IaC over direct CLI calls** for infrastructure creation (Terraform for Huawei Cloud, or CloudFormation equivalent).
13
+ - **Never use em dashes in resource names/tags.** Use hyphens only.
14
+ - **Match user language.** Respond in the same language the user writes in.
15
+
16
+ ## Secret Safety
17
+
18
+ - **MUST load the `huawei-dew` skill** first for any secret, credential, API key, token, or password task.
19
+ - **MUST NOT call** `hcloud csms download-secret` or `hcloud csms show-secret` directly.
20
+ - **MUST NOT call** `hcloud kms decrypt` directly.
21
+ - **MUST use** `{{resolve:csms:secret-id:SecretString:key}}` with the MCP proxy — secrets resolve at runtime, never enter agent context.
22
+ - **NEVER** echo AK/SK values in conversation or logs.
23
+
24
+ ## IAM Security
25
+
26
+ - **Least privilege by default.** Start with no permissions, add only what is needed.
27
+ - **MUST NOT create IAM users** — use IAM Identity Center (OneAccess) or temporary credentials.
28
+ - **MUST NOT create long-term AK/SK** — use temporary STS tokens.
29
+ - **MUST scope resource ARNs** — no `*` wildcards.
30
+ - **Confused deputy protection:** Add `g:SourceAccount` and `g:SourceUrn` conditions to trust policies.
31
+
32
+ ## Network Security
33
+
34
+ - **MUST block public access by default** on OBS buckets.
35
+ - **MUST use security groups** (not iptables) for ECS/CCE access control.
36
+ - **SHOULD use VPC endpoints** for internal service access (no public internet).
37
+ - **MUST enable HTTPS-only** on OBS buckets and API endpoints.
38
+
39
+ ## Observability
40
+
41
+ - **SHOULD enable CTS audit logging** on all accounts.
42
+ - **SHOULD configure Cloud Eye alarms** for production workloads (CPU > 80%, memory > 85%, disk > 90%).
43
+ - **SHOULD enable OBS access logging** for audit compliance.
44
+
45
+ ## Cost Awareness
46
+
47
+ - **Before creating resources**, inform the user of estimated cost.
48
+ - **SHOULD suggest billing budgets** for production accounts.
49
+ - **SHOULD recommend CBR backup** for stateful resources (RDS, ECS system disks).
50
+
51
+ ## Naming Conventions
52
+
53
+ - Resource names: lowercase, hyphens, max 64 chars
54
+ - Tags: include Environment (dev/staging/prod), Owner, Project
55
+ - OBS bucket names: globally unique, lowercase, no underscores
56
+
57
+ ## MCP Usage Rules
58
+
59
+ - **Prefer MCP for ALL Huawei Cloud operations** — sandbox, audit, enterprise controls.
60
+ - **When MCP is unavailable:** Fall back to `hcloud` CLI. State clearly: "MCP unavailable, using local hcloud CLI."
61
+ - **`huaweicloud_run_approved_command`:** Only use when the exact planned command has been shown and the user explicitly approved that exact command.
62
+ - **`huaweicloud_run_script`:** Use for deterministic calculations (cost math, data processing). Never do arithmetic in LLM reasoning.
63
+
64
+ ## Skill Discovery Priority
65
+
66
+ 1. **`huaweicloud-core`** — always check first for routing
67
+ 2. **Service-specific skills** — load after routing via `huaweicloud_retrieve_skill`
68
+ 3. **Knowledge cards** (`references/`) — quick facts without full skill load
69
+
70
+ ## Red Flags — STOP and Re-evaluate
71
+
72
+ - Creating resources without user confirmation
73
+ - Using `hcloud csms download-secret` or `hcloud kms decrypt` directly
74
+ - Guessing API parameters without searching docs
75
+ - Creating IAM users or long-term AK/SK
76
+ - Opening OBS buckets to public access
77
+ - Skipping CTS/Cloud Eye on production workloads
78
+ - Echoing AK/SK or secret values in conversation