huaweicloud-devkit 1.0.2-next.8 → 1.0.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 (38) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/LICENSE +201 -201
  3. package/README.md +71 -52
  4. package/README.zh-CN.md +71 -50
  5. package/bin/setup.cjs +0 -0
  6. package/package.json +9 -5
  7. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +2 -2
  8. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +2 -2
  9. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +2 -2
  10. package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +30 -0
  11. package/plugins/huaweicloud-core/.mcp.json +1 -2
  12. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +2 -3
  13. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +159 -136
  14. package/plugins/huaweicloud-core/openclaw.plugin.json +20 -0
  15. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +1 -1
  16. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +67 -20
  17. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +11 -0
  18. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +16 -0
  19. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +27 -24
  20. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +12 -0
  21. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +79 -11
  22. package/plugins/huaweicloud-core/src/auth/credentials.mjs +65 -0
  23. package/plugins/huaweicloud-core/src/mcp-server.mjs +31 -4
  24. package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +6 -1
  25. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +7 -2
  26. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +14 -4
  27. package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
  28. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +432 -10
  29. package/plugins/huaweicloud-core/src/setup-cli.mjs +581 -87
  30. package/plugins/huaweicloud-core/src/tools.mjs +225 -33
  31. package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +18 -4
  32. package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +9 -0
  33. package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +9 -0
  34. package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +9 -0
  35. package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +9 -0
  36. package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +267 -0
  37. package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
  38. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +9 -5
@@ -0,0 +1,267 @@
1
+ /*
2
+ * This file contains code derived from hwlink.
3
+ *
4
+ * Source:
5
+ * https://gitcode.com/huawei-developers/hwlink
6
+ *
7
+ * Licensed under the ISC License.
8
+ */
9
+
10
+ import { createServer } from 'node:net';
11
+ import {
12
+ OpCode,
13
+ ReserveSource,
14
+ FIXED_HEADER_LEN,
15
+ MAX_SEND_CHUNK_SIZE,
16
+ createPacket,
17
+ isOpTunnelSuccess,
18
+ isOpTcpTunnelData,
19
+ isOpFailed,
20
+ isSubStreamPing,
21
+ nextIdentifier,
22
+ } from './hwlink-packet.js';
23
+
24
+ const MAX_TUNNEL_PAYLOAD_SIZE = MAX_SEND_CHUNK_SIZE - FIXED_HEADER_LEN;
25
+
26
+ class HwlinkTunnelChannel {
27
+ constructor({ localPort = 0, remotePort, onReady, onClose, onError }) {
28
+ this.localPort = localPort;
29
+ this.remotePort = remotePort;
30
+ this.identifier = nextIdentifier();
31
+ this.mux = null;
32
+ this.closed = false;
33
+ this.opened = false;
34
+
35
+ this.subConnections = new Map();
36
+ this.localServer = null;
37
+ this.nextSubId = 1;
38
+
39
+ this.onReadyCb = onReady || null;
40
+ this.onCloseCb = onClose || null;
41
+ this.onErrorCb = onError || null;
42
+
43
+ this._readyResolve = null;
44
+ this._readyReject = null;
45
+ this.ready = new Promise((resolve, reject) => {
46
+ this._readyResolve = resolve;
47
+ this._readyReject = reject;
48
+ });
49
+ }
50
+
51
+ attach(mux) {
52
+ this.mux = mux;
53
+ mux.register(this);
54
+ }
55
+
56
+ onopen() {
57
+ if (this.closed || this.opened) return;
58
+ this.opened = true;
59
+ this.startLocalServer();
60
+ }
61
+
62
+ onmessage(packet) {
63
+ if (this.closed) return;
64
+
65
+ if (isOpFailed(packet.operation)) {
66
+ const subConn = this.subConnections.get(packet.identifier);
67
+ if (subConn && subConn.socket && !subConn.socket.destroyed) {
68
+ try {
69
+ subConn.socket.end();
70
+ } catch {}
71
+ }
72
+ this.subConnections.delete(packet.identifier);
73
+ this._unregisterSubId(packet.identifier);
74
+ return;
75
+ }
76
+
77
+ if (isOpTunnelSuccess(packet.operation)) {
78
+ const subConn = this.subConnections.get(packet.identifier);
79
+ if (subConn && !subConn.ready) {
80
+ subConn.ready = true;
81
+ if (subConn.readyResolve) subConn.readyResolve();
82
+ }
83
+ if (packet.identifier === this.identifier && this._readyResolve) {
84
+ this._readyResolve();
85
+ this._readyResolve = null;
86
+ this._readyReject = null;
87
+ }
88
+ return;
89
+ }
90
+
91
+ if (isOpTcpTunnelData(packet.operation)) {
92
+ const subConn = this.subConnections.get(packet.identifier);
93
+ if (subConn && subConn.socket && !subConn.socket.destroyed) {
94
+ subConn.socket.write(packet.data);
95
+ }
96
+ return;
97
+ }
98
+
99
+ if (isSubStreamPing(packet.operation)) {
100
+ this.sendRaw(
101
+ createPacket({
102
+ operation: OpCode.OpTcpTunnelData | OpCode.OpSubStreamPong,
103
+ reserve: ReserveSource.Web,
104
+ identifier: packet.identifier,
105
+ source: this.mux ? this.mux.source : 0,
106
+ payload: new Uint8Array(0),
107
+ }),
108
+ );
109
+ return;
110
+ }
111
+
112
+ if (packet.operation === OpCode.OpDisconnect) {
113
+ const subConn = this.subConnections.get(packet.identifier);
114
+ if (subConn && subConn.socket && !subConn.socket.destroyed) {
115
+ subConn.socket.end();
116
+ }
117
+ this.subConnections.delete(packet.identifier);
118
+ this._unregisterSubId(packet.identifier);
119
+ return;
120
+ }
121
+ }
122
+
123
+ onerror(error) {
124
+ if (this.onErrorCb) this.onErrorCb(error);
125
+ if (this._readyReject) {
126
+ this._readyReject(error);
127
+ this._readyResolve = null;
128
+ this._readyReject = null;
129
+ }
130
+ }
131
+
132
+ onclose() {
133
+ this.close();
134
+ }
135
+
136
+ startLocalServer() {
137
+ this.localServer = createServer((socket) => {
138
+ this.onIncomingConnection(socket);
139
+ });
140
+
141
+ this.localServer.listen(this.localPort, '127.0.0.1', () => {
142
+ this.localPort = this.localServer.address().port;
143
+ if (this.onReadyCb) this.onReadyCb();
144
+ if (this._readyResolve) {
145
+ this._readyResolve();
146
+ this._readyResolve = null;
147
+ this._readyReject = null;
148
+ }
149
+ });
150
+
151
+ this.localServer.on('error', (err) => {
152
+ if (this.onErrorCb) this.onErrorCb(err);
153
+ if (this._readyReject) {
154
+ this._readyReject(err);
155
+ this._readyResolve = null;
156
+ this._readyReject = null;
157
+ }
158
+ });
159
+ }
160
+
161
+ _registerSubId(subId) {
162
+ if (this.mux && !this.mux.channels.has(subId)) {
163
+ this.mux.channels.set(subId, this);
164
+ this.mux.queue.register({ identifier: subId });
165
+ }
166
+ }
167
+
168
+ _unregisterSubId(subId) {
169
+ if (this.mux) {
170
+ this.mux.channels.delete(subId);
171
+ this.mux.queue.unregister({ identifier: subId });
172
+ }
173
+ }
174
+
175
+ onIncomingConnection(socket) {
176
+ const subId = this.nextSubId;
177
+ this.nextSubId = ((this.nextSubId + 1) & 0xffffffff) >>> 0;
178
+
179
+ let readyResolve;
180
+ const readyPromise = new Promise((resolve) => {
181
+ readyResolve = resolve;
182
+ });
183
+ const subConn = {
184
+ identifier: subId,
185
+ socket,
186
+ ready: false,
187
+ readyResolve,
188
+ readyPromise,
189
+ };
190
+
191
+ this.subConnections.set(subId, subConn);
192
+ this._registerSubId(subId);
193
+
194
+ this.sendRaw(
195
+ createPacket({
196
+ operation: OpCode.OpCreateTcpTunnel,
197
+ reserve: ReserveSource.Web,
198
+ srcPort: this.localPort,
199
+ dstPort: this.remotePort,
200
+ identifier: subId,
201
+ source: this.mux ? this.mux.source : 0,
202
+ payload: new Uint8Array(0),
203
+ }),
204
+ );
205
+
206
+ socket.on('data', (data) => {
207
+ if (this.closed || socket.destroyed) return;
208
+ for (let offset = 0; offset < data.length; offset += MAX_TUNNEL_PAYLOAD_SIZE) {
209
+ const payload = data.subarray(offset, offset + MAX_TUNNEL_PAYLOAD_SIZE);
210
+ this.sendRaw(
211
+ createPacket({
212
+ operation: OpCode.OpTcpTunnelData,
213
+ reserve: ReserveSource.Web,
214
+ srcPort: this.localPort,
215
+ dstPort: this.remotePort,
216
+ identifier: subId,
217
+ source: this.mux ? this.mux.source : 0,
218
+ payload,
219
+ }),
220
+ );
221
+ }
222
+ });
223
+
224
+ socket.on('close', () => {
225
+ this._unregisterSubId(subId);
226
+ this.subConnections.delete(subId);
227
+ });
228
+
229
+ socket.on('error', () => {
230
+ this._unregisterSubId(subId);
231
+ this.subConnections.delete(subId);
232
+ });
233
+ }
234
+
235
+ destroySubConnection(subConn, error) {
236
+ if (subConn.socket && !subConn.socket.destroyed) {
237
+ if (error) subConn.socket.destroy(error);
238
+ else subConn.socket.destroy();
239
+ }
240
+ this._unregisterSubId(subConn.identifier);
241
+ this.subConnections.delete(subConn.identifier);
242
+ }
243
+
244
+ sendRaw(data) {
245
+ if (this.mux) this.mux.sendFairly(this, data);
246
+ }
247
+
248
+ close() {
249
+ if (this.closed) return;
250
+ this.closed = true;
251
+
252
+ for (const subConn of this.subConnections.values()) {
253
+ this.destroySubConnection(subConn);
254
+ }
255
+ this.subConnections.clear();
256
+
257
+ if (this.localServer) {
258
+ this.localServer.close();
259
+ this.localServer = null;
260
+ }
261
+
262
+ if (this.mux) this.mux.unregister(this);
263
+ if (this.onCloseCb) this.onCloseCb();
264
+ }
265
+ }
266
+
267
+ export { HwlinkTunnelChannel };
@@ -3,10 +3,12 @@ import * as hwlinkPacket from './hwlink-packet.js';
3
3
  import * as wsExec from './ws-exec-client.js';
4
4
  import { HwlinkWebSocketMultiplexer } from './hwlink-multiplexer.js';
5
5
  import { HwlinkTerminalChannel } from './hwlink-terminal-channel.js';
6
+ import { HwlinkTunnelChannel } from './hwlink-tunnel-channel.mjs';
6
7
 
7
8
  export * from './ws-exec-client.js';
8
9
  export * from './hwlink-exec-client.js';
9
10
  export { HwlinkTerminalChannel } from './hwlink-terminal-channel.js';
11
+ export { HwlinkTunnelChannel } from './hwlink-tunnel-channel.mjs';
10
12
  export { HwlinkWebSocketMultiplexer } from './hwlink-multiplexer.js';
11
13
  export { hwlinkPacket };
12
14
 
@@ -14,6 +16,7 @@ export default {
14
16
  ...wsExec,
15
17
  ...hwlinkExec,
16
18
  HwlinkTerminalChannel,
19
+ HwlinkTunnelChannel,
17
20
  HwlinkWebSocketMultiplexer,
18
21
  hwlinkPacket,
19
22
  };
@@ -65,7 +65,6 @@ function buildMarkers(nonce = randomBytes(8).toString('hex')) {
65
65
  doneSuffix,
66
66
  readyMarker,
67
67
  doneMarker,
68
- donePattern: new RegExp(`${escapeRegExp(doneMarker)}(\\d+)`),
69
68
  };
70
69
  }
71
70
 
@@ -284,10 +283,15 @@ class WebSocketShellSession {
284
283
  const pending = this.pending;
285
284
  if (!pending) return;
286
285
 
287
- const doneMatch = pending.buffer.match(pending.markers.donePattern);
288
- if (!doneMatch || doneMatch.index === undefined) return;
286
+ const doneMarker = pending.markers.doneMarker;
287
+ const markerIndex = pending.buffer.lastIndexOf(doneMarker);
288
+ if (markerIndex === -1) return;
289
289
 
290
- const rawOutput = pending.buffer.slice(0, doneMatch.index);
290
+ const afterMarker = pending.buffer.slice(markerIndex + doneMarker.length);
291
+ const exitMatch = afterMarker.match(/^(\d+)/);
292
+ if (!exitMatch) return;
293
+
294
+ const rawOutput = pending.buffer.slice(0, markerIndex);
291
295
  const stdout = cleanCommandOutput(rawOutput, {
292
296
  inputEchoed: this.inputEchoed,
293
297
  command: pending.command,
@@ -298,7 +302,7 @@ class WebSocketShellSession {
298
302
  this.pending = null;
299
303
  pending.resolve({
300
304
  stdout,
301
- exitCode: Number(doneMatch[1]),
305
+ exitCode: Number(exitMatch[1]),
302
306
  url: this.url,
303
307
  command: pending.command,
304
308
  });