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
@@ -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,20 +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
+
38
63
  function officeaceSkillsRoot() {
39
- if (process.env.OFFICEACE_HOME) return join(process.env.OFFICEACE_HOME, 'skills');
40
- const dotDir = join(homedir(), '.office-claw');
41
- const dotCapFile = join(dotDir, 'capabilities.json');
42
- if (existsSync(dotCapFile)) return join(dotDir, 'skills');
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
+ }
43
71
  if (process.platform === 'win32') {
44
- for (const base of [process.env.ProgramFiles, 'C:\\Program Files', 'D:\\Program Files']) {
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) {
45
75
  if (!base) continue;
46
- const dir = join(base, 'OfficeAce', '.office-claw');
47
- if (existsSync(join(dir, 'capabilities.json'))) return join(dir, 'skills');
76
+ const dir = join(base, 'OfficeAce', '.office-claw', 'skills');
77
+ if (existsSync(dir)) return dir;
48
78
  }
49
79
  }
50
- return join(dotDir, 'skills');
80
+ return null;
51
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
+
52
93
  export function listSkillDirs(root) {
53
94
  if (!existsSync(root)) return [];
54
95
  try {
@@ -76,6 +117,7 @@ function resolveSkillsRoot() {
76
117
  opencodeSkillsDir(),
77
118
  workbuddySkillsDir(),
78
119
  officeaceSkillsRoot(),
120
+ hermesSkillsDir(),
79
121
  ]) || SKILLS_ROOT_DEV
80
122
  );
81
123
  }
@@ -383,7 +425,7 @@ export const TOOL_DEFINITIONS = [
383
425
  target: {
384
426
  type: 'string',
385
427
  description:
386
- 'Agent target to check: opencode, codex, codex-desktop, codearts, workbuddy, dsh, officeace, or all (default).',
428
+ 'Agent target to check: opencode, codex, codex-desktop, codearts, workbuddy, dsh, officeace, hermes, openclaw, or all (default).',
387
429
  },
388
430
  },
389
431
  },
@@ -398,23 +440,60 @@ export const TOOL_DEFINITIONS = [
398
440
  target: {
399
441
  type: 'string',
400
442
  description:
401
- '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).',
402
444
  },
403
445
  },
404
446
  },
405
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
+ },
406
462
  {
407
463
  name: 'huaweicloud_sandbox_exec_with_session',
408
464
  description:
409
- '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.',
410
466
  inputSchema: {
411
467
  type: 'object',
412
468
  required: ['command'],
413
469
  properties: {
414
470
  command: { type: 'string', description: 'The shell command to execute on the remote workspace' },
415
- 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
+ },
416
476
  username: { type: 'string', description: 'Login username for the remote terminal (default: root)' },
417
- 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)' },
418
497
  },
419
498
  },
420
499
  },
@@ -424,7 +503,11 @@ export const TOOL_DEFINITIONS = [
424
503
  inputSchema: {
425
504
  type: 'object',
426
505
  properties: {
427
- 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
+ },
428
511
  username: { type: 'string', description: 'Login username (default: root)' },
429
512
  },
430
513
  },
@@ -439,9 +522,46 @@ export const TOOL_DEFINITIONS = [
439
522
  properties: {
440
523
  local_path: { type: 'string', description: 'Absolute path to the local file to upload.' },
441
524
  remote_path: { type: 'string', description: 'Target path in the sandbox, e.g. /workspace/<repo>/index.html.' },
442
- 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
+ },
443
530
  username: { type: 'string', description: 'Login username (default: root)' },
444
- 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)' },
445
565
  },
446
566
  },
447
567
  },
@@ -554,40 +674,112 @@ export async function callTool(name, args = {}) {
554
674
  return getAuthStatus(args.target || 'all');
555
675
  case 'huaweicloud_auth_sync':
556
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.' };
557
687
  case 'huaweicloud_sandbox_exec_with_session': {
558
- 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
+ }
559
695
  const sandboxUser2 = args.username || 'root';
560
- const sandboxTimeout2 = args.timeout_ms || 30000;
696
+ const sandboxTimeout2 = args.timeout_ms || 120000;
561
697
  const sandboxResult2 = await execWithSession(sandboxWsId2, args.command, sandboxUser2, sandboxTimeout2);
562
698
  return { stdout: sandboxResult2.stdout, exitCode: sandboxResult2.exitCode };
563
699
  }
564
- case 'huaweicloud_sandbox_close_session': {
565
- 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
+ }
566
708
  const sandboxUser3 = args.username || 'root';
567
- 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);
568
723
  return closed ? 'ok' : 'not_connected';
569
724
  }
570
725
  case 'huaweicloud_sandbox_upload_file': {
571
726
  if (!args.local_path || !args.remote_path) {
572
727
  throw new Error('local_path and remote_path are required.');
573
728
  }
574
- const sandboxWsId4 = args.workspace_id || DEFAULT_WORKSPACE_ID;
575
- const sandboxUser4 = args.username || 'root';
576
- 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;
577
738
  return await uploadFileWithSession(
578
- sandboxWsId4,
739
+ sandboxWsId5,
579
740
  args.local_path,
580
741
  args.remote_path,
581
- sandboxUser4,
582
- 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
+ },
583
769
  );
584
770
  }
585
771
  case 'huaweicloud_sandbox_check_user':
586
772
  return await hdkitCheckUser();
587
773
  case 'huaweicloud_sandbox_sign_agreement':
588
774
  return await hdkitSignAgreement();
589
- case 'huaweicloud_sandbox_connect':
590
- 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
+ }
591
783
  case 'huaweicloud_sandbox_credentials':
592
784
  return await hdkitCredentials(args.session_id, args.dev_stage_id, args.enable_sts !== false);
593
785
  default:
@@ -773,7 +965,7 @@ async function setupObsConfigFromHcloud(profile) {
773
965
  const SERVICE_EXAMPLES = {
774
966
  ECS: { list: 'ECS ListServersDetails', create: 'ECS CreateServers', show: 'IMS GlanceShowImage' },
775
967
  VPC: { list: 'VPC ListVpcs', create: 'VPC CreateVpc', show: 'VPC ShowVpc' },
776
- FunctionGraph: {
968
+ FUNCTIONGRAPH: {
777
969
  list: 'FunctionGraph ListFunctions',
778
970
  create: 'FunctionGraph CreateFunction',
779
971
  show: 'FunctionGraph ShowFunctionConfig',
@@ -782,7 +974,7 @@ const SERVICE_EXAMPLES = {
782
974
  OBS: { list: 'OBS ls', create: 'OBS mb obs://<bucket>', show: 'OBS stat obs://<bucket>/<key>' },
783
975
  RDS: { list: 'RDS ListInstances', create: 'RDS CreateInstance', show: 'RDS ShowInstance' },
784
976
  CES: { list: 'CES ListAlarms', create: 'CES CreateAlarm', show: 'CES ListMetrics' },
785
- GaussDB: { list: 'GaussDB ListInstances', create: 'GaussDB CreateInstance', show: 'GaussDB ShowInstance' },
977
+ GAUSSDB: { list: 'GaussDB ListInstances', create: 'GaussDB CreateInstance', show: 'GaussDB ShowInstance' },
786
978
  DDS: { list: 'DDS ListInstances', create: 'DDS CreateInstance', show: 'DDS ShowInstance' },
787
979
  DCS: { list: 'DCS ListInstances', create: 'DCS CreateInstance', show: 'DCS ShowInstance' },
788
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,