huaweicloud-devkit 1.1.2-next.0 → 1.1.2-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.
Files changed (125) hide show
  1. package/.agents/plugins/marketplace.json +18 -18
  2. package/README.md +182 -38
  3. package/README.zh-CN.md +180 -36
  4. package/bin/dsh-postinstall.cjs +77 -0
  5. package/bin/setup.cjs +7 -4
  6. package/cordis.patch.yml +13 -0
  7. package/integrations/atomcode/hooks/huaweicloud-telemetry.js +181 -0
  8. package/integrations/atomcode/hooks.json +9 -0
  9. package/integrations/dsh/hook-plugin.mjs +148 -0
  10. package/integrations/hermes/hooks/huaweicloud-telemetry.py +238 -0
  11. package/integrations/hermes/manifest.yaml +43 -0
  12. package/integrations/opencode/hooks/skill-tracker.js +115 -0
  13. package/integrations/opencode/opencode.json +1 -4
  14. package/integrations/workbuddy/hooks/telemetry-tracker.py +226 -0
  15. package/package.json +74 -43
  16. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +4 -17
  17. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +23 -33
  18. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +4 -17
  19. package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +30 -0
  20. package/plugins/huaweicloud-core/.mcp.json +2 -5
  21. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +4 -18
  22. package/plugins/huaweicloud-core/assets/icon.png +0 -0
  23. package/plugins/huaweicloud-core/assets/logo-dark.png +0 -0
  24. package/plugins/huaweicloud-core/assets/logo.png +0 -0
  25. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +104 -28
  26. package/plugins/huaweicloud-core/openclaw.plugin.json +20 -0
  27. package/plugins/huaweicloud-core/safety/policy.json +75 -99
  28. package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +196 -35
  29. package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +38 -34
  30. package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +21 -20
  31. package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +19 -19
  32. package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +38 -32
  33. package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +30 -30
  34. package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +20 -20
  35. package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +31 -31
  36. package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +21 -20
  37. package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +40 -31
  38. package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +8 -1
  39. package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +6 -0
  40. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +45 -37
  41. package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +57 -6
  42. package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +11 -9
  43. package/plugins/huaweicloud-core/skills/huawei-ecs/references/hc-activity.md +27 -0
  44. package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +13 -13
  45. package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +44 -39
  46. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +12 -10
  47. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +21 -20
  48. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +19 -19
  49. package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +20 -20
  50. package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +27 -23
  51. package/plugins/huaweicloud-core/skills/huawei-iac/SKILL.md +93 -0
  52. package/plugins/huaweicloud-core/skills/huawei-iac/references/architectures.md +83 -0
  53. package/plugins/huaweicloud-core/skills/huawei-iac/references/resource-catalog.md +107 -0
  54. package/plugins/huaweicloud-core/skills/huawei-iac/references/state-file.md +104 -0
  55. package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +30 -30
  56. package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +65 -39
  57. package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +9 -9
  58. package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +33 -25
  59. package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +46 -45
  60. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +19 -19
  61. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +12 -7
  62. package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +13 -12
  63. package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +4 -4
  64. package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +71 -61
  65. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +776 -44
  66. package/plugins/huaweicloud-core/skills/huawei-sandbox/references/framework-commands.md +84 -0
  67. package/plugins/huaweicloud-core/skills/huawei-sandbox/references/nginx-templates.md +100 -0
  68. package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +36 -29
  69. package/plugins/huaweicloud-core/skills/huawei-voucher/SKILL.md +57 -0
  70. package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +39 -39
  71. package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +22 -15
  72. package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +20 -19
  73. package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +15 -15
  74. package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +23 -20
  75. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +13 -13
  76. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +3 -3
  77. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +21 -2
  78. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +3 -3
  79. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +30 -4
  80. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +3 -3
  81. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +68 -54
  82. package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +3 -3
  83. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +6 -6
  84. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +15 -1
  85. package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +0 -1
  86. package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +3 -3
  87. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +36 -34
  88. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +3 -3
  89. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +142 -5
  90. package/plugins/huaweicloud-core/src/auth/credentials.mjs +217 -5
  91. package/plugins/huaweicloud-core/src/auth/reconcile.mjs +174 -0
  92. package/plugins/huaweicloud-core/src/auth/service.mjs +57 -2
  93. package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
  94. package/plugins/huaweicloud-core/src/detect-framework.mjs +366 -0
  95. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +203 -16
  96. package/plugins/huaweicloud-core/src/icon-library.mjs +176 -0
  97. package/plugins/huaweicloud-core/src/mcp-protocol.mjs +88 -0
  98. package/plugins/huaweicloud-core/src/mcp-server-remote.mjs +107 -0
  99. package/plugins/huaweicloud-core/src/mcp-server.mjs +154 -99
  100. package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +20 -102
  101. package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +11 -4
  102. package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +4 -1
  103. package/plugins/huaweicloud-core/src/safety-policy.mjs +123 -68
  104. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +48 -12
  105. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +25 -26
  106. package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
  107. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +943 -10
  108. package/plugins/huaweicloud-core/src/sandbox/uninstall-cleanup.mjs +50 -0
  109. package/plugins/huaweicloud-core/src/search-market.mjs +49 -14
  110. package/plugins/huaweicloud-core/src/setup-cli.mjs +3091 -498
  111. package/plugins/huaweicloud-core/src/telemetry/agent-detect.mjs +40 -0
  112. package/plugins/huaweicloud-core/src/telemetry/agent-registry.mjs +328 -0
  113. package/plugins/huaweicloud-core/src/telemetry/telemetry.mjs +378 -0
  114. package/plugins/huaweicloud-core/src/tools.mjs +1272 -146
  115. package/plugins/huaweicloud-core/src/update-check.mjs +383 -0
  116. package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +72 -38
  117. package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +10 -5
  118. package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +13 -16
  119. package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +12 -16
  120. package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +46 -31
  121. package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +268 -0
  122. package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
  123. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +40 -13
  124. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +0 -190
  125. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +0 -124
@@ -0,0 +1,176 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ import { getProxyDispatcher } from './proxy/proxy-agent.mjs';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const SNAPSHOT_PATH = join(__dirname, 'data', 'icons-manifest.v1.json');
9
+ const MANIFEST_URL = 'https://open.huaweicloud.com/openplatform/icons/manifest.v1.json';
10
+ const ICONS_PAGE_URL = 'https://open.huaweicloud.com/openplatform/icons.html';
11
+ const HTTP_TIMEOUT_MS = 10000;
12
+ const MAX_RESULTS = 5;
13
+
14
+ let cachedManifest = null;
15
+
16
+ export function clearIconCache() {
17
+ cachedManifest = null;
18
+ }
19
+
20
+ async function fetchLiveManifest() {
21
+ const controller = new AbortController();
22
+ const timer = setTimeout(() => controller.abort(), HTTP_TIMEOUT_MS);
23
+ try {
24
+ const dispatcher = await getProxyDispatcher(MANIFEST_URL);
25
+ const fetchOpts = {
26
+ headers: { 'User-Agent': 'huaweicloud-devkit/1.0' },
27
+ signal: controller.signal,
28
+ };
29
+ if (dispatcher) fetchOpts.dispatcher = dispatcher;
30
+ const resp = await fetch(MANIFEST_URL, fetchOpts);
31
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
32
+ const data = await resp.json();
33
+ if (!data || !Array.isArray(data.icons)) {
34
+ throw new Error('Manifest is missing the icons array.');
35
+ }
36
+ return data;
37
+ } finally {
38
+ clearTimeout(timer);
39
+ }
40
+ }
41
+
42
+ function loadSnapshot() {
43
+ const manifest = JSON.parse(readFileSync(SNAPSHOT_PATH, 'utf8'));
44
+ if (!manifest || !Array.isArray(manifest.icons)) {
45
+ throw new Error('Bundled icons manifest is corrupted.');
46
+ }
47
+ return manifest;
48
+ }
49
+
50
+ async function loadManifest() {
51
+ if (cachedManifest) return cachedManifest;
52
+ if (process.env.HUAWEICLOUD_ICONS_OFFLINE === '1') {
53
+ cachedManifest = { manifest: loadSnapshot(), source: 'snapshot' };
54
+ return cachedManifest;
55
+ }
56
+ try {
57
+ const live = await fetchLiveManifest();
58
+ cachedManifest = { manifest: live, source: 'live' };
59
+ } catch {
60
+ cachedManifest = { manifest: loadSnapshot(), source: 'snapshot' };
61
+ }
62
+ return cachedManifest;
63
+ }
64
+
65
+ function normalizeToken(token) {
66
+ return token.toLowerCase();
67
+ }
68
+
69
+ function scoreIcon(icon, tokens, index) {
70
+ let total = 0;
71
+ const matched = [];
72
+ for (const token of tokens) {
73
+ let best = 0;
74
+ const id = (icon.id || '').toLowerCase();
75
+ const name = (icon.name || '').toLowerCase();
76
+ if (id === token || (index.aliases[token] || []).includes(id)) {
77
+ best = 10;
78
+ } else if (name === token) {
79
+ best = 9;
80
+ } else if (name.startsWith(token)) {
81
+ best = 8;
82
+ } else if (name.includes(token)) {
83
+ best = 7;
84
+ } else if (id.includes(token)) {
85
+ best = 5;
86
+ } else if ((index.aliases[id] || []).some((a) => a.includes(token))) {
87
+ best = 6;
88
+ } else if ((index.tags[id] || []).some((t) => t.includes(token))) {
89
+ best = 4;
90
+ } else if ((index.categories[id] || '').includes(token)) {
91
+ best = 3;
92
+ } else if ((index.descriptions[id] || '').includes(token)) {
93
+ best = 2;
94
+ }
95
+ if (best > 0) {
96
+ total += best;
97
+ matched.push(token);
98
+ }
99
+ }
100
+ return [total, matched];
101
+ }
102
+
103
+ export async function getServiceIcon(service = '', category = '') {
104
+ const query = String(service || '').trim();
105
+ const catFilter = String(category || '')
106
+ .trim()
107
+ .toLowerCase();
108
+ if (!query && !catFilter) {
109
+ return {
110
+ ok: false,
111
+ error:
112
+ 'service or category is required. Examples: ecs, obs, modelarts, 对象存储, 虚拟私有云, or a category such as 计算 / 存储 / 人工智能.',
113
+ iconsPageUrl: ICONS_PAGE_URL,
114
+ };
115
+ }
116
+ const { manifest, source } = await loadManifest();
117
+ const tokens = query
118
+ .replace(/[,;]/g, ' ')
119
+ .split(/\s+/)
120
+ .filter(Boolean)
121
+ .map((t) => normalizeToken(t));
122
+
123
+ const index = { aliases: {}, tags: {}, categories: {}, descriptions: {} };
124
+ for (const icon of manifest.icons) {
125
+ const id = (icon.id || '').toLowerCase();
126
+ index.aliases[id] = (icon.aliases || []).map((a) => String(a).toLowerCase());
127
+ index.tags[id] = (icon.tags || []).map((t) => String(t).toLowerCase());
128
+ index.categories[id] = [
129
+ String(icon.category || '').toLowerCase(),
130
+ String(icon.subcategory || '').toLowerCase(),
131
+ ].join(' ');
132
+ index.descriptions[id] = String(icon.description || '').toLowerCase();
133
+ }
134
+
135
+ const results = [];
136
+ for (const icon of manifest.icons) {
137
+ const categoryMatches = !catFilter || String(icon.category || '').toLowerCase() === catFilter;
138
+ if (!categoryMatches) continue;
139
+ const [score, matched] = tokens.length ? scoreIcon(icon, tokens, index) : [0, []];
140
+ if (tokens.length && score === 0) continue;
141
+ results.push({
142
+ id: icon.id,
143
+ name: icon.name,
144
+ category: icon.category,
145
+ subcategory: icon.subcategory || undefined,
146
+ description: icon.description || undefined,
147
+ aliases: icon.aliases,
148
+ product_url: icon.product_url,
149
+ logo: {
150
+ source_url: icon.logo?.source_url,
151
+ local_path: icon.logo?.local_path,
152
+ },
153
+ architecture: icon.architecture
154
+ ? { status: icon.architecture.status, local_path: icon.architecture.local_path }
155
+ : undefined,
156
+ score,
157
+ matched,
158
+ });
159
+ }
160
+ results.sort((a, b) => b.score - a.score);
161
+
162
+ return {
163
+ ok: true,
164
+ query,
165
+ category: catFilter || undefined,
166
+ source,
167
+ manifestGeneratedAt: manifest.generated_at,
168
+ count: results.length,
169
+ iconsPageUrl: ICONS_PAGE_URL,
170
+ note:
171
+ source === 'live'
172
+ ? 'Live manifest from open.huaweicloud.com.'
173
+ : 'Live manifest unreachable; using bundled snapshot. Upgrade the package to refresh the bundled snapshot.',
174
+ results: results.slice(0, MAX_RESULTS),
175
+ };
176
+ }
@@ -0,0 +1,88 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve, dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ import { TOOL_DEFINITIONS, callTool } from './tools.mjs';
6
+ import { peekCachedUpdateInfo, applyUpdateHint } from './update-check.mjs';
7
+ import { initTelemetry } from './telemetry/telemetry.mjs';
8
+ import { detectAgent } from './telemetry/agent-detect.mjs';
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ const pluginRoot = resolve(__dirname, '..');
12
+ const packageRoot = resolve(pluginRoot, '..', '..');
13
+ let pkgVersion = '0.0.0';
14
+ for (const base of [pluginRoot, packageRoot]) {
15
+ try {
16
+ const version = JSON.parse(readFileSync(join(base, 'package.json'), 'utf8')).version;
17
+ if (version) {
18
+ pkgVersion = version;
19
+ break;
20
+ }
21
+ } catch {}
22
+ }
23
+
24
+ // 会话内首个非 check/upgrade 工具调用附加 _updateInfo,只消费一次。
25
+ let hintConsumed = false;
26
+ function decorateResult(name, result) {
27
+ if (hintConsumed) return result;
28
+ try {
29
+ const hint = peekCachedUpdateInfo();
30
+ if (!hint) return result;
31
+ const decorated = applyUpdateHint(result, name, hint);
32
+ if (decorated !== result) hintConsumed = true;
33
+ return decorated;
34
+ } catch {
35
+ return result; // 兜底装饰失败绝不影响工具调用
36
+ }
37
+ }
38
+
39
+ export async function dispatch(method, params) {
40
+ if (method === 'initialize') {
41
+ const ci = params.clientInfo || {};
42
+
43
+ try {
44
+ const { hdkitGenerateUserHash } = await import('./sandbox/hdkitservice-api.mjs');
45
+ await Promise.race([
46
+ hdkitGenerateUserHash(),
47
+ new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 3000)),
48
+ ]);
49
+ } catch {}
50
+
51
+ const agent = detectAgent(ci);
52
+ initTelemetry({ harness: agent.harness, version: agent.version });
53
+ return {
54
+ protocolVersion: params.protocolVersion || '2024-11-05',
55
+ capabilities: {
56
+ tools: {},
57
+ },
58
+ serverInfo: {
59
+ name: 'huaweicloud-devkit',
60
+ version: pkgVersion,
61
+ },
62
+ };
63
+ }
64
+
65
+ if (method === 'tools/list') {
66
+ return { tools: TOOL_DEFINITIONS };
67
+ }
68
+
69
+ if (method === 'tools/call') {
70
+ const result = await callTool(params.name, params.arguments || {});
71
+ const decorated = decorateResult(params.name, result);
72
+ return {
73
+ content: [
74
+ {
75
+ type: 'text',
76
+ text: JSON.stringify(decorated, null, 2),
77
+ },
78
+ ],
79
+ isError: false,
80
+ };
81
+ }
82
+
83
+ if (method === 'resources/list') {
84
+ return { resources: [] };
85
+ }
86
+
87
+ throw new Error(`Unsupported method: ${method}`);
88
+ }
@@ -0,0 +1,107 @@
1
+ import { createServer } from 'node:http';
2
+ import { resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { format } from 'node:util';
5
+
6
+ import { dispatch } from './mcp-protocol.mjs';
7
+
8
+ export const DEFAULT_PORT = 9528;
9
+ export const DEFAULT_HOST = '127.0.0.1';
10
+
11
+ export async function startRemoteServer({ port = DEFAULT_PORT, host = DEFAULT_HOST } = {}) {
12
+ const server = createServer(async (req, res) => {
13
+ res.setHeader('Access-Control-Allow-Origin', '*');
14
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
15
+ res.setHeader(
16
+ 'Access-Control-Allow-Headers',
17
+ 'Content-Type, MCP-Protocol-Version, Mcp-Session-Id, Accept, Authorization',
18
+ );
19
+ res.setHeader('Access-Control-Expose-Headers', 'MCP-Protocol-Version, Mcp-Session-Id');
20
+
21
+ if (req.method === 'OPTIONS') {
22
+ res.writeHead(204);
23
+ res.end();
24
+ return;
25
+ }
26
+
27
+ if (req.method !== 'POST') {
28
+ res.writeHead(405, { Allow: 'POST, OPTIONS' });
29
+ res.end();
30
+ return;
31
+ }
32
+
33
+ let message;
34
+ try {
35
+ const chunks = [];
36
+ for await (const chunk of req) chunks.push(chunk);
37
+ message = JSON.parse(Buffer.concat(chunks).toString('utf8'));
38
+ } catch {
39
+ res.writeHead(400, { 'Content-Type': 'application/json' });
40
+ res.end(JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } }));
41
+ return;
42
+ }
43
+
44
+ if (!Object.hasOwn(message, 'id')) {
45
+ // 通知类消息(含 notifications/initialized)无需响应体,HTTP 层直接 202。
46
+ res.writeHead(202);
47
+ res.end();
48
+ return;
49
+ }
50
+
51
+ let response;
52
+ try {
53
+ const result = await dispatch(message.method, message.params || {});
54
+ response = { jsonrpc: '2.0', id: message.id, result };
55
+ if (message.method === 'initialize') {
56
+ res.setHeader('MCP-Protocol-Version', result.protocolVersion || '2024-11-05');
57
+ }
58
+ } catch (error) {
59
+ response = {
60
+ jsonrpc: '2.0',
61
+ id: message.id,
62
+ error: { code: -32603, message: error.message },
63
+ };
64
+ }
65
+
66
+ writeMCPResponse(res, response, req.headers.accept || '');
67
+ });
68
+
69
+ await new Promise((resolvePromise, rejectPromise) => {
70
+ server.once('error', rejectPromise);
71
+ server.listen(port, host, () => resolvePromise());
72
+ });
73
+
74
+ const address = server.address();
75
+ process.stdout.write(format('huaweicloud-devkit MCP server (remote) listening on %s:%s\n', host, address.port));
76
+
77
+ return {
78
+ server,
79
+ port: address.port,
80
+ close: () => new Promise((resolvePromise) => server.close(resolvePromise)),
81
+ };
82
+ }
83
+
84
+ function writeMCPResponse(res, response, accept) {
85
+ const json = JSON.stringify(response);
86
+ if (accept.includes('application/json')) {
87
+ res.writeHead(200, { 'Content-Type': 'application/json' });
88
+ res.end(json);
89
+ return;
90
+ }
91
+ // 客户端只接受 SSE 时的兜底:单帧 event 后关闭流。
92
+ res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' });
93
+ res.write(`event: message\ndata: ${json}\n\n`);
94
+ res.end();
95
+ }
96
+
97
+ if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
98
+ const portIdx = process.argv.indexOf('--port');
99
+ const port = portIdx > -1 ? Number(process.argv[portIdx + 1]) : DEFAULT_PORT;
100
+ const hostIdx = process.argv.indexOf('--host');
101
+ const host = hostIdx > -1 && process.argv[hostIdx + 1] ? process.argv[hostIdx + 1] : DEFAULT_HOST;
102
+ startRemoteServer({ port, host }).catch((error) => {
103
+ process.stderr.write(`Failed to start MCP remote server: ${error.message}\n`);
104
+ // eslint-disable-next-line n/no-process-exit -- fatal startup error in standalone CLI mode
105
+ process.exit(1);
106
+ });
107
+ }
@@ -2,8 +2,48 @@
2
2
  import { stdin, stdout } from 'node:process';
3
3
  import { rmSync, existsSync } from 'node:fs';
4
4
  import { resolve, dirname } from 'node:path';
5
+ import { platform } from 'node:os';
5
6
  import { fileURLToPath } from 'node:url';
6
- import { TOOL_DEFINITIONS, callTool } from './tools.mjs';
7
+
8
+ import { dispatch } from './mcp-protocol.mjs';
9
+ import { DEFAULT_PORT, DEFAULT_HOST } from './mcp-server-remote.mjs';
10
+ import { getCachedUpdateInfo, readInstalledVersion } from './update-check.mjs';
11
+ import { detectAgent } from './telemetry/agent-detect.mjs';
12
+
13
+ const transportIdx = process.argv.indexOf('--transport');
14
+ const transport = transportIdx > -1 && process.argv[transportIdx + 1] ? process.argv[transportIdx + 1] : 'stdio';
15
+ const portIdx = process.argv.indexOf('--port');
16
+ const remotePort = portIdx > -1 ? Number(process.argv[portIdx + 1]) : DEFAULT_PORT;
17
+ const hostIdx = process.argv.indexOf('--host');
18
+ const remoteHost = hostIdx > -1 && process.argv[hostIdx + 1] ? process.argv[hostIdx + 1] : DEFAULT_HOST;
19
+
20
+ const projectDirIdx = process.argv.indexOf('--codearts-project-dir');
21
+ if (projectDirIdx > -1 && process.argv[projectDirIdx + 1]) {
22
+ process.env.CODEARTS_PROJECT_DIR = process.argv[projectDirIdx + 1];
23
+ }
24
+
25
+ const endpointIdx = process.argv.indexOf('--hdkitservice-endpoint');
26
+ if (endpointIdx > -1 && process.argv[endpointIdx + 1]) {
27
+ process.env.HDKITSERVICE_ENDPOINT = process.argv[endpointIdx + 1];
28
+ }
29
+
30
+ const telemetryEndpointIdx = process.argv.indexOf('--telemetry-endpoint');
31
+ if (telemetryEndpointIdx > -1 && process.argv[telemetryEndpointIdx + 1]) {
32
+ process.env.HUAWEICLOUD_DEVKIT_TELEMETRY_ENDPOINT = process.argv[telemetryEndpointIdx + 1];
33
+ }
34
+
35
+ try {
36
+ const { readProxyConfig } = await import('./proxy/proxy-config.mjs');
37
+ const proxyConfig = readProxyConfig();
38
+ if (proxyConfig) {
39
+ if (proxyConfig.https_proxy || proxyConfig.HTTPS_PROXY) {
40
+ process.env.HTTPS_PROXY = process.env.HTTPS_PROXY || proxyConfig.https_proxy || proxyConfig.HTTPS_PROXY;
41
+ }
42
+ if (proxyConfig.http_proxy || proxyConfig.HTTP_PROXY) {
43
+ process.env.HTTP_PROXY = process.env.HTTP_PROXY || proxyConfig.http_proxy || proxyConfig.HTTP_PROXY;
44
+ }
45
+ }
46
+ } catch {}
7
47
 
8
48
  // The MCP server is now loaded by a live agent session. Clear the install marker
9
49
  // in this plugin dir so `doctor` no longer reports "restart needed".
@@ -13,117 +53,132 @@ try {
13
53
  if (existsSync(marker)) rmSync(marker, { force: true });
14
54
  } catch {}
15
55
 
16
- let buffer = Buffer.alloc(0);
17
- let useContentLengthFraming = true;
18
-
19
- stdin.on('data', (chunk) => {
20
- buffer = Buffer.concat([buffer, chunk]);
21
- readFrames();
22
- });
23
-
24
- function readFrames() {
25
- while (true) {
26
- const headerEnd = buffer.indexOf('\r\n\r\n');
27
- if (headerEnd !== -1) {
28
- useContentLengthFraming = true;
29
- const consumed = parseContentLengthFrame(headerEnd);
30
- if (!consumed) return;
31
- continue;
32
- }
56
+ if (transport === 'remote') {
57
+ const { startRemoteServer } = await import('./mcp-server-remote.mjs');
58
+ startRemoteServer({ port: remotePort, host: remoteHost }).catch((error) => {
59
+ process.stderr.write(`Failed to start MCP remote server: ${error.message}\n`);
60
+ // eslint-disable-next-line n/no-process-exit -- fatal startup error; no active session to keep alive
61
+ process.exit(1);
62
+ });
63
+ } else {
64
+ runStdioServer();
65
+ }
33
66
 
34
- const lf = buffer.indexOf('\n');
35
- if (lf !== -1) {
36
- useContentLengthFraming = false;
37
- const line = buffer.subarray(0, lf).toString('utf8').trim();
38
- buffer = buffer.subarray(lf + 1);
39
- if (line) void handleMessage(JSON.parse(line));
40
- continue;
41
- }
67
+ function runStdioServer() {
68
+ const updatePrewarm = () => {
69
+ getCachedUpdateInfo(readInstalledVersion() || '0.0.0').catch(() => {});
70
+ };
42
71
 
43
- return;
44
- }
45
- }
72
+ let buffer = Buffer.alloc(0);
73
+ let useContentLengthFraming = true;
46
74
 
47
- function parseContentLengthFrame(headerEnd) {
48
- const header = buffer.subarray(0, headerEnd).toString('utf8');
49
- const match = header.match(/Content-Length:\s*(\d+)/i);
50
- if (!match) {
51
- buffer = Buffer.alloc(0);
52
- return true;
53
- }
54
- const length = Number(match[1]);
55
- const bodyStart = headerEnd + 4;
56
- const bodyEnd = bodyStart + length;
57
- if (buffer.length < bodyEnd) return false;
58
- const body = buffer.subarray(bodyStart, bodyEnd).toString('utf8');
59
- buffer = buffer.subarray(bodyEnd);
60
- void handleMessage(JSON.parse(body));
61
- return true;
62
- }
75
+ // Keep the event loop alive after stdin is closed (Windows Hermes workaround).
76
+ // Node.js exits when no active handles remain; the stdin 'data' listener is
77
+ // the only handle. On Windows, Hermes may close the stdin pipe after the
78
+ // initial handshake, causing the process to exit silently (exit 0).
79
+ //
80
+ // For Hermes on Windows: start a keepalive timer on stdin close, and only exit
81
+ // when stdout also closes.
82
+ // For all other agents (OfficeAce, WorkBuddy, etc.): stdin close is the
83
+ // shutdown signal — exit cleanly so the host does not see CLOSE_TIMEOUT.
84
+ const { harness } = detectAgent();
85
+ const NEEDS_KEEPALIVE = harness === 'hermes' && platform() === 'win32';
86
+
87
+ // 版本升级检测预热:异步、非阻塞;失败静默(离线/超时不影响会话)。
88
+ process.nextTick(updatePrewarm);
63
89
 
64
- async function handleMessage(message) {
65
- if (!Object.hasOwn(message, 'id')) {
66
- if (message.method === 'notifications/initialized') return;
67
- return;
90
+ let keepAlive = null;
91
+ function onStdinClose() {
92
+ if (keepAlive) return;
93
+ if (NEEDS_KEEPALIVE) {
94
+ keepAlive = setInterval(() => {}, 60000);
95
+ } else {
96
+ // eslint-disable-next-line n/no-process-exit -- stdin close is the shutdown signal; exit now without waiting for stdout
97
+ process.exit(0);
98
+ }
68
99
  }
69
- try {
70
- const result = await dispatch(message.method, message.params || {});
71
- writeMessage({ jsonrpc: '2.0', id: message.id, result });
72
- } catch (error) {
73
- writeMessage({
74
- jsonrpc: '2.0',
75
- id: message.id,
76
- error: {
77
- code: -32603,
78
- message: error.message,
79
- },
80
- });
100
+ function onStdoutClose() {
101
+ if (keepAlive) {
102
+ clearInterval(keepAlive);
103
+ keepAlive = null;
104
+ }
105
+ process.exitCode = 0;
81
106
  }
82
- }
107
+ stdin.on('close', onStdinClose);
108
+ stdin.on('end', onStdinClose);
109
+ stdout.on('close', onStdoutClose);
83
110
 
84
- async function dispatch(method, params) {
85
- if (method === 'initialize') {
86
- return {
87
- protocolVersion: params.protocolVersion || '2024-11-05',
88
- capabilities: {
89
- tools: {},
90
- },
91
- serverInfo: {
92
- name: 'huaweicloud-devkit',
93
- version: '0.1.0',
94
- },
95
- };
96
- }
111
+ stdin.on('data', (chunk) => {
112
+ buffer = Buffer.concat([buffer, chunk]);
113
+ readFrames();
114
+ });
97
115
 
98
- if (method === 'tools/list') {
99
- return { tools: TOOL_DEFINITIONS };
100
- }
116
+ function readFrames() {
117
+ while (true) {
118
+ const headerEnd = buffer.indexOf('\r\n\r\n');
119
+ if (headerEnd !== -1) {
120
+ useContentLengthFraming = true;
121
+ const consumed = parseContentLengthFrame(headerEnd);
122
+ if (!consumed) return;
123
+ continue;
124
+ }
101
125
 
102
- if (method === 'tools/call') {
103
- const result = await callTool(params.name, params.arguments || {});
104
- return {
105
- content: [
106
- {
107
- type: 'text',
108
- text: JSON.stringify(result, null, 2),
109
- },
110
- ],
111
- isError: false,
112
- };
126
+ const lf = buffer.indexOf('\n');
127
+ if (lf !== -1) {
128
+ useContentLengthFraming = false;
129
+ const line = buffer.subarray(0, lf).toString('utf8').trim();
130
+ buffer = buffer.subarray(lf + 1);
131
+ if (line) void handleMessage(JSON.parse(line));
132
+ continue;
133
+ }
134
+
135
+ return;
136
+ }
113
137
  }
114
138
 
115
- if (method === 'resources/list') {
116
- return { resources: [] };
139
+ function parseContentLengthFrame(headerEnd) {
140
+ const header = buffer.subarray(0, headerEnd).toString('utf8');
141
+ const match = header.match(/Content-Length:\s*(\d+)/i);
142
+ if (!match) {
143
+ buffer = Buffer.alloc(0);
144
+ return true;
145
+ }
146
+ const length = Number(match[1]);
147
+ const bodyStart = headerEnd + 4;
148
+ const bodyEnd = bodyStart + length;
149
+ if (buffer.length < bodyEnd) return false;
150
+ const body = buffer.subarray(bodyStart, bodyEnd).toString('utf8');
151
+ buffer = buffer.subarray(bodyEnd);
152
+ void handleMessage(JSON.parse(body));
153
+ return true;
117
154
  }
118
155
 
119
- throw new Error(`Unsupported method: ${method}`);
120
- }
156
+ async function handleMessage(message) {
157
+ if (!Object.hasOwn(message, 'id')) {
158
+ if (message.method === 'notifications/initialized') return;
159
+ return;
160
+ }
161
+ try {
162
+ const result = await dispatch(message.method, message.params || {});
163
+ writeMessage({ jsonrpc: '2.0', id: message.id, result });
164
+ } catch (error) {
165
+ writeMessage({
166
+ jsonrpc: '2.0',
167
+ id: message.id,
168
+ error: {
169
+ code: -32603,
170
+ message: error.message,
171
+ },
172
+ });
173
+ }
174
+ }
121
175
 
122
- function writeMessage(message) {
123
- const json = JSON.stringify(message);
124
- if (useContentLengthFraming) {
125
- stdout.write(`Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`);
126
- } else {
127
- stdout.write(json + '\n');
176
+ function writeMessage(message) {
177
+ const json = JSON.stringify(message);
178
+ if (useContentLengthFraming) {
179
+ stdout.write(`Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`);
180
+ } else {
181
+ stdout.write(json + '\n');
182
+ }
128
183
  }
129
184
  }