huaweicloud-devkit 1.0.2-next.9 → 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 (36) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/README.md +85 -51
  3. package/README.zh-CN.md +85 -49
  4. package/package.json +11 -5
  5. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +4 -17
  6. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +4 -17
  7. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +4 -17
  8. package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +30 -0
  9. package/plugins/huaweicloud-core/.mcp.json +1 -2
  10. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +4 -18
  11. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +32 -9
  12. package/plugins/huaweicloud-core/openclaw.plugin.json +20 -0
  13. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +1 -1
  14. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +67 -20
  15. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +11 -0
  16. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +16 -0
  17. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +27 -24
  18. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +12 -0
  19. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +103 -2
  20. package/plugins/huaweicloud-core/src/auth/credentials.mjs +65 -0
  21. package/plugins/huaweicloud-core/src/mcp-server.mjs +31 -4
  22. package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +6 -1
  23. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +7 -2
  24. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +14 -4
  25. package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
  26. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +432 -10
  27. package/plugins/huaweicloud-core/src/setup-cli.mjs +823 -22
  28. package/plugins/huaweicloud-core/src/tools.mjs +239 -27
  29. package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +18 -4
  30. package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +9 -0
  31. package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +9 -0
  32. package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +9 -0
  33. package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +9 -0
  34. package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +267 -0
  35. package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
  36. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +9 -5
@@ -5,17 +5,26 @@ import { readFileSync, readdirSync, existsSync, writeFileSync } from 'node:fs';
5
5
  import { join, dirname } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { homedir } from 'node:os';
8
+ import { spawnSync } from 'node:child_process';
8
9
  import { searchMarketplace } from './search-market.mjs';
9
10
  import { getServiceIcon } from './icon-library.mjs';
10
11
  import {
11
12
  execWithSession,
13
+ execOneShot,
12
14
  closeSession,
13
15
  uploadFileWithSession,
14
- DEFAULT_WORKSPACE_ID,
16
+ uploadProjectWithSession,
17
+ currentWorkspaceId,
18
+ setWorkspaceId,
15
19
  } from './sandbox/session-manager.mjs';
16
20
  import { hdkitCheckUser, hdkitSignAgreement, hdkitConnect, hdkitCredentials } from './sandbox/hdkitservice-api.mjs';
17
21
  import { getAuthStatus, syncAuth } from './auth/service.mjs';
18
- import { readGlobalCredentials, writeObsConfig as writeObsConfigFile } from './auth/credentials.mjs';
22
+ import {
23
+ readGlobalCredentials,
24
+ writeObsConfig as writeObsConfigFile,
25
+ setRuntimeCredentials,
26
+ clearRuntimeCredentials,
27
+ } from './auth/credentials.mjs';
19
28
 
20
29
  const __dirname = dirname(fileURLToPath(import.meta.url));
21
30
  const SKILLS_ROOT_DEV = join(__dirname, '..', 'skills');
@@ -35,6 +44,52 @@ function dshSkillsDir() {
35
44
  const home = process.env.DSH_HOME || join(homedir(), '.dsh');
36
45
  return join(home, 'skills');
37
46
  }
47
+ function readOfficeaceRegistryInstallDir() {
48
+ if (process.platform !== 'win32') return null;
49
+ try {
50
+ const r = spawnSync('reg', ['query', 'HKCU\\SOFTWARE\\OfficeAce\\OfficeAce', '/v', 'InstallDir'], {
51
+ encoding: 'utf8',
52
+ windowsHide: true,
53
+ timeout: 5000,
54
+ });
55
+ if (r.status === 0) {
56
+ const m = r.stdout.match(/InstallDir\s+REG_SZ\s+(.+)/);
57
+ if (m) return m[1].trim();
58
+ }
59
+ } catch {}
60
+ return null;
61
+ }
62
+
63
+ function officeaceSkillsRoot() {
64
+ const configRoot = process.env.OFFICE_CLAW_CONFIG_ROOT;
65
+ if (configRoot && existsSync(join(configRoot, 'capabilities.json'))) return join(configRoot, 'skills');
66
+ const regDir = readOfficeaceRegistryInstallDir();
67
+ if (regDir) {
68
+ const dir = join(regDir, '.office-claw', 'skills');
69
+ if (existsSync(dir)) return dir;
70
+ }
71
+ if (process.platform === 'win32') {
72
+ const bases = [process.env.ProgramFiles, 'C:\\Program Files', 'D:\\Program Files'];
73
+ if (process.env.LOCALAPPDATA) bases.push(join(process.env.LOCALAPPDATA, 'Programs'));
74
+ for (const base of bases) {
75
+ if (!base) continue;
76
+ const dir = join(base, 'OfficeAce', '.office-claw', 'skills');
77
+ if (existsSync(dir)) return dir;
78
+ }
79
+ }
80
+ return null;
81
+ }
82
+
83
+ function hermesSkillsDir() {
84
+ if (process.env.HERMES_HOME) return join(process.env.HERMES_HOME, 'skills');
85
+ // Hermes on Windows stores under LOCALAPPDATA, not ~/.hermes
86
+ if (process.platform === 'win32' && process.env.LOCALAPPDATA) {
87
+ return join(process.env.LOCALAPPDATA, 'hermes', 'skills');
88
+ }
89
+ const home = homedir();
90
+ return join(home, '.hermes', 'skills');
91
+ }
92
+
38
93
  export function listSkillDirs(root) {
39
94
  if (!existsSync(root)) return [];
40
95
  try {
@@ -55,8 +110,15 @@ export function findSkillsRoot(candidates) {
55
110
 
56
111
  function resolveSkillsRoot() {
57
112
  return (
58
- findSkillsRoot([SKILLS_ROOT_DEV, dshSkillsDir(), codeartsSkillsDir(), opencodeSkillsDir(), workbuddySkillsDir()]) ||
59
- SKILLS_ROOT_DEV
113
+ findSkillsRoot([
114
+ SKILLS_ROOT_DEV,
115
+ dshSkillsDir(),
116
+ codeartsSkillsDir(),
117
+ opencodeSkillsDir(),
118
+ workbuddySkillsDir(),
119
+ officeaceSkillsRoot(),
120
+ hermesSkillsDir(),
121
+ ]) || SKILLS_ROOT_DEV
60
122
  );
61
123
  }
62
124
  const SKILLS_ROOT = resolveSkillsRoot();
@@ -363,7 +425,7 @@ export const TOOL_DEFINITIONS = [
363
425
  target: {
364
426
  type: 'string',
365
427
  description:
366
- 'Agent target to check: opencode, codex, codex-desktop, codearts, workbuddy, dsh, or all (default).',
428
+ 'Agent target to check: opencode, codex, codex-desktop, codearts, workbuddy, dsh, officeace, hermes, openclaw, or all (default).',
367
429
  },
368
430
  },
369
431
  },
@@ -378,23 +440,60 @@ export const TOOL_DEFINITIONS = [
378
440
  target: {
379
441
  type: 'string',
380
442
  description:
381
- 'Agent target to report after sync: opencode, codex, codex-desktop, codearts, workbuddy, dsh, or all (default).',
443
+ 'Agent target to report after sync: opencode, codex, codex-desktop, codearts, workbuddy, dsh, officeace, hermes, or all (default).',
382
444
  },
383
445
  },
384
446
  },
385
447
  },
448
+ {
449
+ name: 'huaweicloud_auth_init',
450
+ description:
451
+ 'Set or clear runtime Huawei Cloud credentials (AK/SK) for this MCP session. Runtime credentials take highest priority over environment variables and config files for all subsequent API calls. Use when switching accounts within the same Agent session — call with AK/SK to switch, or with clear=true to fall back to env/file credentials.',
452
+ inputSchema: {
453
+ type: 'object',
454
+ properties: {
455
+ ak: { type: 'string', description: 'Huawei Cloud Access Key (required unless clear=true)' },
456
+ sk: { type: 'string', description: 'Huawei Cloud Secret Key (required unless clear=true)' },
457
+ region: { type: 'string', description: 'Default region (optional)' },
458
+ clear: { type: 'boolean', description: 'Set to true to clear runtime credentials and revert to env/file' },
459
+ },
460
+ },
461
+ },
386
462
  {
387
463
  name: 'huaweicloud_sandbox_exec_with_session',
388
464
  description:
389
- 'Execute a command on a workspace terminal with session reuse (state persists across calls). Shell state (cd, env vars, aliases) carries over between calls.',
465
+ 'Execute a command on a workspace terminal with session reuse (state persists across calls). Shell state (cd, env vars, aliases) carries over between calls. Use for interactive work and command sequences that need shared state. NOT for long-running commands (>30s) — prefer exec_one_shot for those.',
390
466
  inputSchema: {
391
467
  type: 'object',
392
468
  required: ['command'],
393
469
  properties: {
394
470
  command: { type: 'string', description: 'The shell command to execute on the remote workspace' },
395
- workspace_id: { type: 'string', description: 'The workspace ID' },
471
+ workspace_id: {
472
+ type: 'string',
473
+ description:
474
+ 'Workspace ID from huaweicloud_sandbox_connect return value. Required - must be passed explicitly when HW_WORKSPACE_ID is not set.',
475
+ },
396
476
  username: { type: 'string', description: 'Login username for the remote terminal (default: root)' },
397
- timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 30000)' },
477
+ timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 120000)' },
478
+ },
479
+ },
480
+ },
481
+ {
482
+ name: 'huaweicloud_sandbox_exec_one_shot',
483
+ description:
484
+ 'Execute a command on a workspace terminal with a fresh connection per call (no session state carries over). Each invocation opens a new WebSocket connection, executes one command, then disconnects. Use for long-running build/deploy/install commands (>30s) that do not need shell state persistence between calls. More stable than session-based execution for heavy workloads.',
485
+ inputSchema: {
486
+ type: 'object',
487
+ required: ['command'],
488
+ properties: {
489
+ command: { type: 'string', description: 'The shell command to execute on the remote workspace' },
490
+ workspace_id: {
491
+ type: 'string',
492
+ description:
493
+ 'Workspace ID from huaweicloud_sandbox_connect return value. Required - must be passed explicitly when HW_WORKSPACE_ID is not set.',
494
+ },
495
+ username: { type: 'string', description: 'Login username for the remote terminal (default: root)' },
496
+ timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 120000)' },
398
497
  },
399
498
  },
400
499
  },
@@ -404,7 +503,11 @@ export const TOOL_DEFINITIONS = [
404
503
  inputSchema: {
405
504
  type: 'object',
406
505
  properties: {
407
- workspace_id: { type: 'string', description: 'The workspace ID' },
506
+ workspace_id: {
507
+ type: 'string',
508
+ description:
509
+ 'Workspace ID from huaweicloud_sandbox_connect return value. Required - must be passed explicitly when HW_WORKSPACE_ID is not set.',
510
+ },
408
511
  username: { type: 'string', description: 'Login username (default: root)' },
409
512
  },
410
513
  },
@@ -419,9 +522,46 @@ export const TOOL_DEFINITIONS = [
419
522
  properties: {
420
523
  local_path: { type: 'string', description: 'Absolute path to the local file to upload.' },
421
524
  remote_path: { type: 'string', description: 'Target path in the sandbox, e.g. /workspace/<repo>/index.html.' },
422
- workspace_id: { type: 'string', description: 'The workspace ID' },
525
+ workspace_id: {
526
+ type: 'string',
527
+ description:
528
+ 'Workspace ID from huaweicloud_sandbox_connect return value. Required - must be passed explicitly when HW_WORKSPACE_ID is not set.',
529
+ },
423
530
  username: { type: 'string', description: 'Login username (default: root)' },
424
- timeout_ms: { type: 'number', description: 'Per-command execution timeout in milliseconds (default: 30000)' },
531
+ timeout_ms: { type: 'number', description: 'Per-command execution timeout in milliseconds (default: 120000)' },
532
+ },
533
+ },
534
+ },
535
+ {
536
+ name: 'huaweicloud_sandbox_upload_project',
537
+ description:
538
+ 'Package a local project directory and upload it to a sandbox workspace via HTTP tunnel. Falls back to base64 chunking if tunnel fails. Creates a tar.gz archive, uploads it, and extracts it on the sandbox by default.',
539
+ inputSchema: {
540
+ type: 'object',
541
+ required: ['local_dir'],
542
+ properties: {
543
+ local_dir: { type: 'string', description: 'Local project directory to upload.' },
544
+ remote_dir: {
545
+ type: 'string',
546
+ description:
547
+ 'Remote parent directory where project will be extracted (default: /workspace). Final layout: <remote_dir>/<dirname>/',
548
+ },
549
+ workspace_id: {
550
+ type: 'string',
551
+ description:
552
+ 'Workspace ID from huaweicloud_sandbox_connect return value. Required - must be passed explicitly when HW_WORKSPACE_ID is not set.',
553
+ },
554
+ username: { type: 'string', description: 'Login username (default: root)' },
555
+ exclude: {
556
+ type: 'array',
557
+ items: { type: 'string' },
558
+ description: 'Patterns to exclude from archive (default: .git, node_modules, __pycache__, .venv)',
559
+ },
560
+ extract: {
561
+ type: 'boolean',
562
+ description: 'Extract tar.gz on sandbox after upload (default: true)',
563
+ },
564
+ timeout_ms: { type: 'number', description: 'Execution timeout in milliseconds (default: 300000)' },
425
565
  },
426
566
  },
427
567
  },
@@ -534,40 +674,112 @@ export async function callTool(name, args = {}) {
534
674
  return getAuthStatus(args.target || 'all');
535
675
  case 'huaweicloud_auth_sync':
536
676
  return syncAuth(args.target || 'all');
677
+ case 'huaweicloud_auth_init':
678
+ if (args.clear) {
679
+ clearRuntimeCredentials();
680
+ return { status: 'cleared', message: 'Runtime credentials cleared. Fallback to env/file.' };
681
+ }
682
+ if (!args.ak || !args.sk) {
683
+ throw new Error('ak and sk are required. Set clear=true to clear runtime credentials.');
684
+ }
685
+ setRuntimeCredentials(args.ak, args.sk, undefined, args.region);
686
+ return { status: 'ok', message: 'Runtime credentials set for this MCP session.' };
537
687
  case 'huaweicloud_sandbox_exec_with_session': {
538
- const sandboxWsId2 = args.workspace_id || DEFAULT_WORKSPACE_ID;
688
+ const sandboxWsId2 = args.workspace_id || currentWorkspaceId;
689
+ if (!sandboxWsId2) {
690
+ throw new Error(
691
+ 'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
692
+ 'or set HW_WORKSPACE_ID environment variable before starting the agent.',
693
+ );
694
+ }
539
695
  const sandboxUser2 = args.username || 'root';
540
- const sandboxTimeout2 = args.timeout_ms || 30000;
696
+ const sandboxTimeout2 = args.timeout_ms || 120000;
541
697
  const sandboxResult2 = await execWithSession(sandboxWsId2, args.command, sandboxUser2, sandboxTimeout2);
542
698
  return { stdout: sandboxResult2.stdout, exitCode: sandboxResult2.exitCode };
543
699
  }
544
- case 'huaweicloud_sandbox_close_session': {
545
- const sandboxWsId3 = args.workspace_id || DEFAULT_WORKSPACE_ID;
700
+ case 'huaweicloud_sandbox_exec_one_shot': {
701
+ const sandboxWsId3 = args.workspace_id || currentWorkspaceId;
702
+ if (!sandboxWsId3) {
703
+ throw new Error(
704
+ 'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
705
+ 'or set HW_WORKSPACE_ID environment variable before starting the agent.',
706
+ );
707
+ }
546
708
  const sandboxUser3 = args.username || 'root';
547
- const closed = await closeSession(sandboxWsId3, sandboxUser3);
709
+ const sandboxTimeout3 = args.timeout_ms || 120000;
710
+ const sandboxResult3 = await execOneShot(sandboxWsId3, args.command, sandboxUser3, sandboxTimeout3);
711
+ return { stdout: sandboxResult3.stdout, exitCode: sandboxResult3.exitCode };
712
+ }
713
+ case 'huaweicloud_sandbox_close_session': {
714
+ const sandboxWsId4 = args.workspace_id || currentWorkspaceId;
715
+ if (!sandboxWsId4) {
716
+ throw new Error(
717
+ 'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
718
+ 'or set HW_WORKSPACE_ID environment variable before starting the agent.',
719
+ );
720
+ }
721
+ const sandboxUser4 = args.username || 'root';
722
+ const closed = await closeSession(sandboxWsId4, sandboxUser4);
548
723
  return closed ? 'ok' : 'not_connected';
549
724
  }
550
725
  case 'huaweicloud_sandbox_upload_file': {
551
726
  if (!args.local_path || !args.remote_path) {
552
727
  throw new Error('local_path and remote_path are required.');
553
728
  }
554
- const sandboxWsId4 = args.workspace_id || DEFAULT_WORKSPACE_ID;
555
- const sandboxUser4 = args.username || 'root';
556
- const sandboxTimeout4 = args.timeout_ms || 30000;
729
+ const sandboxWsId5 = args.workspace_id || currentWorkspaceId;
730
+ if (!sandboxWsId5) {
731
+ throw new Error(
732
+ 'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
733
+ 'or set HW_WORKSPACE_ID environment variable before starting the agent.',
734
+ );
735
+ }
736
+ const sandboxUser5 = args.username || 'root';
737
+ const sandboxTimeout5 = args.timeout_ms || 120000;
557
738
  return await uploadFileWithSession(
558
- sandboxWsId4,
739
+ sandboxWsId5,
559
740
  args.local_path,
560
741
  args.remote_path,
561
- sandboxUser4,
562
- sandboxTimeout4,
742
+ sandboxUser5,
743
+ sandboxTimeout5,
744
+ );
745
+ }
746
+ case 'huaweicloud_sandbox_upload_project': {
747
+ if (!args.local_dir) {
748
+ throw new Error('local_dir is required.');
749
+ }
750
+ const sandboxWsId6 = args.workspace_id || currentWorkspaceId;
751
+ if (!sandboxWsId6) {
752
+ throw new Error(
753
+ 'workspace_id is required. No sandbox connected — call huaweicloud_sandbox_connect first, ' +
754
+ 'or set HW_WORKSPACE_ID environment variable before starting the agent.',
755
+ );
756
+ }
757
+ const sandboxUser6 = args.username || 'root';
758
+ const sandboxTimeout6 = args.timeout_ms || 120000;
759
+ return await uploadProjectWithSession(
760
+ sandboxWsId6,
761
+ args.local_dir,
762
+ args.remote_dir,
763
+ sandboxUser6,
764
+ sandboxTimeout6,
765
+ {
766
+ exclude: args.exclude,
767
+ extract: args.extract,
768
+ },
563
769
  );
564
770
  }
565
771
  case 'huaweicloud_sandbox_check_user':
566
772
  return await hdkitCheckUser();
567
773
  case 'huaweicloud_sandbox_sign_agreement':
568
774
  return await hdkitSignAgreement();
569
- case 'huaweicloud_sandbox_connect':
570
- return await hdkitConnect(args);
775
+ case 'huaweicloud_sandbox_connect': {
776
+ const connectResult = await hdkitConnect(args);
777
+ const devStageId = connectResult?.dev_stage_id || connectResult?.devStageId;
778
+ if (devStageId) {
779
+ setWorkspaceId(devStageId);
780
+ }
781
+ return connectResult;
782
+ }
571
783
  case 'huaweicloud_sandbox_credentials':
572
784
  return await hdkitCredentials(args.session_id, args.dev_stage_id, args.enable_sts !== false);
573
785
  default:
@@ -753,7 +965,7 @@ async function setupObsConfigFromHcloud(profile) {
753
965
  const SERVICE_EXAMPLES = {
754
966
  ECS: { list: 'ECS ListServersDetails', create: 'ECS CreateServers', show: 'IMS GlanceShowImage' },
755
967
  VPC: { list: 'VPC ListVpcs', create: 'VPC CreateVpc', show: 'VPC ShowVpc' },
756
- FunctionGraph: {
968
+ FUNCTIONGRAPH: {
757
969
  list: 'FunctionGraph ListFunctions',
758
970
  create: 'FunctionGraph CreateFunction',
759
971
  show: 'FunctionGraph ShowFunctionConfig',
@@ -762,7 +974,7 @@ const SERVICE_EXAMPLES = {
762
974
  OBS: { list: 'OBS ls', create: 'OBS mb obs://<bucket>', show: 'OBS stat obs://<bucket>/<key>' },
763
975
  RDS: { list: 'RDS ListInstances', create: 'RDS CreateInstance', show: 'RDS ShowInstance' },
764
976
  CES: { list: 'CES ListAlarms', create: 'CES CreateAlarm', show: 'CES ListMetrics' },
765
- GaussDB: { list: 'GaussDB ListInstances', create: 'GaussDB CreateInstance', show: 'GaussDB ShowInstance' },
977
+ GAUSSDB: { list: 'GaussDB ListInstances', create: 'GaussDB CreateInstance', show: 'GaussDB ShowInstance' },
766
978
  DDS: { list: 'DDS ListInstances', create: 'DDS CreateInstance', show: 'DDS ShowInstance' },
767
979
  DCS: { list: 'DCS ListInstances', create: 'DCS CreateInstance', show: 'DCS ShowInstance' },
768
980
  };
@@ -1,3 +1,12 @@
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
+
1
10
  import {
2
11
  DEFAULT_TIMEOUT_MS,
3
12
  WebSocketExecError,
@@ -383,10 +392,15 @@ class HwlinkTerminalExecSession {
383
392
  const pending = this.pending;
384
393
  if (!pending) return;
385
394
 
386
- const doneMatch = pending.buffer.match(pending.markers.donePattern);
387
- if (!doneMatch || doneMatch.index === undefined) return;
395
+ const doneMarker = pending.markers.doneMarker;
396
+ const markerIndex = pending.buffer.lastIndexOf(doneMarker);
397
+ if (markerIndex === -1) return;
398
+
399
+ const afterMarker = pending.buffer.slice(markerIndex + doneMarker.length);
400
+ const exitMatch = afterMarker.match(/^(\d+)/);
401
+ if (!exitMatch) return;
388
402
 
389
- const rawOutput = pending.buffer.slice(0, doneMatch.index);
403
+ const rawOutput = pending.buffer.slice(0, markerIndex);
390
404
  const stdout = cleanCommandOutput(rawOutput, {
391
405
  inputEchoed: this.inputEchoed,
392
406
  command: pending.command,
@@ -397,7 +411,7 @@ class HwlinkTerminalExecSession {
397
411
  this.pending = null;
398
412
  pending.resolve({
399
413
  stdout,
400
- exitCode: Number(doneMatch[1]),
414
+ exitCode: Number(exitMatch[1]),
401
415
  url: this.url,
402
416
  source: this.source,
403
417
  username: this.username,
@@ -1,3 +1,12 @@
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
+
1
10
  const DEFAULT_HIGH_WATERMARK = 8 * 1024 * 1024;
2
11
  const DEFAULT_LOW_WATERMARK = 2 * 1024 * 1024;
3
12
 
@@ -1,3 +1,12 @@
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
+
1
10
  import { Buffer } from 'node:buffer';
2
11
 
3
12
  import { FairQueue } from './hwlink-fair-queue.js';
@@ -1,3 +1,12 @@
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
+
1
10
  const FIXED_HEADER_LEN = 20;
2
11
  const MAX_SEND_CHUNK_SIZE = 8 * 1024;
3
12
 
@@ -1,3 +1,12 @@
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
+
1
10
  import {
2
11
  OpCode,
3
12
  ReserveSource,