@icyfenix-dmla/cli 2026.5.3-821 → 2026.5.4-1216

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icyfenix-dmla/cli",
3
- "version": "2026.5.3-821",
3
+ "version": "2026.5.4-1216",
4
4
  "description": "DMLA 沙箱服务命令行工具",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/scripts/build.js CHANGED
@@ -49,11 +49,11 @@ function copyDir(src, dest, filter = null) {
49
49
  return true
50
50
  }
51
51
 
52
- // 复制服务器代码(只复制 .js 文件)
52
+ // 复制服务器代码(复制 .js 和 .py 文件)
53
53
  console.log('\n📋 复制服务器代码...')
54
54
  console.log(` 源目录: ${localServerSrc}`)
55
55
  console.log(` 目标目录: ${cliServerDest}`)
56
- copyDir(localServerSrc, cliServerDest, (name) => name.endsWith('.js'))
56
+ copyDir(localServerSrc, cliServerDest, (name) => name.endsWith('.js') || name.endsWith('.py'))
57
57
 
58
58
  // 复制共享模块(复制所有 .py 文件和 __init__.py)
59
59
  console.log('\n📋 复制共享模块...')
@@ -1,10 +1,10 @@
1
1
  # shared 模块包初始化
2
2
  # 包含统计学习系列文档中可复用的类定义
3
3
 
4
- from .linear import *
5
- from .cnn import *
6
4
  from .bayesian import *
7
- from .tree import *
5
+ from .cnn import *
6
+ from .linear import *
7
+ from .neural import *
8
8
  from .svm import *
9
+ from .tree import *
9
10
  from .unsupervised import *
10
- from .neural import *
@@ -1,6 +1,6 @@
1
- # Bayesian 模块
2
- from .simple_bayesian_network import SimpleBayesianNetwork
1
+ # BAYESIAN 模块
2
+ from .bayesian_network import SimpleBayesianNetwork
3
3
  from .gaussian_mixture_model import GaussianMixtureModel
4
4
  from .multinomial_naive_bayes import MultinomialNaiveBayes
5
5
 
6
- __all__ = ['SimpleBayesianNetwork', 'GaussianMixtureModel', 'MultinomialNaiveBayes']
6
+ __all__ = ['SimpleBayesianNetwork', 'GaussianMixtureModel', 'MultinomialNaiveBayes']
@@ -1,5 +1,8 @@
1
1
  # CNN 模块
2
- from .alex_net import AlexNet
2
+ from .alexnet import AlexNet
3
+ from .minimal_preprocess_cache import MinimalPreprocessCache
4
+ from .realtime_dataset import RealtimeAugmentDataset
5
+ from .realtime_dataset import RealtimeValDataset
3
6
  from .tiny_imagenet_dataset import TinyImageNetDataset
4
7
 
5
- __all__ = ['AlexNet', 'TinyImageNetDataset']
8
+ __all__ = ['AlexNet', 'MinimalPreprocessCache', 'RealtimeAugmentDataset', 'RealtimeValDataset', 'TinyImageNetDataset']
@@ -0,0 +1,153 @@
1
+ # MinimalPreprocessCache 类定义
2
+ # 从文档自动提取生成
3
+
4
+ import os
5
+ from PIL import Image
6
+
7
+ class MinimalPreprocessCache:
8
+ """
9
+ 最小缓存策略:执行 64*64 -> 224*224 Resize,保存为 JPEG 格式
10
+ 缓存大小:约 600MB,训练时约 4GB 内存
11
+ """
12
+ def __init__(self, data_dir, cache_dir):
13
+ self.data_dir = data_dir
14
+ self.cache_dir = cache_dir
15
+ self.train_cache = os.path.join(cache_dir, 'train')
16
+ self.val_cache = os.path.join(cache_dir, 'val')
17
+ self.manifest_path = os.path.join(cache_dir, 'manifest.json')
18
+
19
+ def preprocess_image(self, img_path, save_path):
20
+ """单张图片预处理:Resize(224) → JPEG"""
21
+ img = Image.open(img_path).convert('RGB')
22
+ img = img.resize((224, 224), Image.BILINEAR)
23
+ img.save(save_path, 'JPEG', quality=95)
24
+
25
+ def check_cache_exists(self):
26
+ """检查缓存是否已完整存在"""
27
+ return os.path.exists(self.manifest_path)
28
+
29
+ def get_cache_stats(self):
30
+ """获取缓存统计信息"""
31
+ if os.path.exists(self.manifest_path):
32
+ with open(self.manifest_path, 'r') as f:
33
+ manifest = json.load(f)
34
+ return manifest.get('train_count', 0), manifest.get('val_count', 0)
35
+ return 0, 0
36
+
37
+ def _preprocess_train_set(self, progress):
38
+ """预处理训练集"""
39
+ train_dir = os.path.join(self.data_dir, 'train')
40
+ classes = sorted(os.listdir(train_dir))
41
+
42
+ os.makedirs(self.train_cache, exist_ok=True)
43
+ total_count = 0
44
+
45
+ for cls_idx, cls in enumerate(classes):
46
+ cls_cache_dir = os.path.join(self.train_cache, cls)
47
+
48
+ # 中断恢复:检查已存在的类别目录
49
+ if os.path.exists(cls_cache_dir):
50
+ existing_files = [f for f in os.listdir(cls_cache_dir) if f.endswith('.JPEG')]
51
+ if len(existing_files) >= 500:
52
+ total_count += len(existing_files)
53
+ progress.update(cls_idx + 1, message=f"跳过已缓存类别 {cls_idx+1}/200: {cls}")
54
+ continue
55
+
56
+ os.makedirs(cls_cache_dir, exist_ok=True)
57
+
58
+ images_dir = os.path.join(train_dir, cls, 'images')
59
+ if not os.path.exists(images_dir):
60
+ continue
61
+
62
+ count = 0
63
+ for img_name in os.listdir(images_dir):
64
+ if img_name.endswith('.JPEG'):
65
+ img_path = os.path.join(images_dir, img_name)
66
+ save_path = os.path.join(cls_cache_dir, img_name)
67
+
68
+ try:
69
+ self.preprocess_image(img_path, save_path)
70
+ count += 1
71
+ total_count += 1
72
+ except Exception as e:
73
+ print(f"Warning: Failed to process {img_path}: {e}")
74
+
75
+ progress.update(cls_idx + 1, message=f"预处理类别 {cls_idx+1}/200: {cls} ({count} 张)")
76
+
77
+ return total_count
78
+
79
+ def _preprocess_val_set(self, progress):
80
+ """预处理验证集"""
81
+ val_dir = os.path.join(self.data_dir, 'val')
82
+ val_images_dir = os.path.join(val_dir, 'images')
83
+ val_annotations = os.path.join(val_dir, 'val_annotations.txt')
84
+
85
+ # 读取类别映射
86
+ wnids_path = os.path.join(self.data_dir, 'wnids.txt')
87
+ with open(wnids_path, 'r') as f:
88
+ wnids = [line.strip() for line in f.readlines()]
89
+ class_to_idx = {wnid: idx for idx, wnid in enumerate(wnids)}
90
+
91
+ # 读取标注文件
92
+ with open(val_annotations, 'r') as f:
93
+ val_lines = f.readlines()
94
+ total_val = len(val_lines)
95
+
96
+ os.makedirs(self.val_cache, exist_ok=True)
97
+
98
+ # 中断恢复
99
+ existing_files = [f for f in os.listdir(self.val_cache) if f.endswith('.JPEG')]
100
+ start_idx = len(existing_files)
101
+ if start_idx >= total_val:
102
+ progress.update(total_val, message=f"验证集已缓存: {total_val} 张")
103
+ return total_val, []
104
+
105
+ labels = []
106
+ progress.reset(total_steps=total_val, description="预处理验证集")
107
+
108
+ for line_idx in range(start_idx, total_val):
109
+ parts = val_lines[line_idx].strip().split('\t')
110
+ if len(parts) >= 2:
111
+ img_name = parts[0]
112
+ img_path = os.path.join(val_images_dir, img_name)
113
+ save_path = os.path.join(self.val_cache, f'val_{line_idx}.JPEG')
114
+ if os.path.exists(img_path):
115
+ try:
116
+ self.preprocess_image(img_path, save_path)
117
+ labels.append(class_to_idx.get(parts[1], 0))
118
+ except Exception as e:
119
+ print(f"处理图片出现异常 {img_path}: {e}")
120
+
121
+ if (line_idx + 1) % 100 == 0 or line_idx == total_val - 1:
122
+ progress.update(line_idx + 1, message=f"预处理验证集 {line_idx+1}/{total_val}")
123
+
124
+ return total_val, labels
125
+
126
+ def run(self, progress):
127
+ """执行预处理(支持断点续传)"""
128
+ start_time = time.time()
129
+ os.makedirs(self.cache_dir, exist_ok=True)
130
+ train_count = self._preprocess_train_set(progress)
131
+ val_count, val_labels = self._preprocess_val_set(progress)
132
+
133
+ # 保存清单文件
134
+ manifest = {
135
+ 'train_count': train_count,
136
+ 'val_count': val_count,
137
+ 'val_labels': val_labels if val_labels else self._load_existing_val_labels()
138
+ }
139
+ with open(self.manifest_path, 'w') as f:
140
+ json.dump(manifest, f)
141
+
142
+ elapsed = time.time() - start_time
143
+ progress.complete(message=f"预处理完成: 训练集 {train_count} 张, 验证集 {val_count} 张, 耗时 {elapsed:.1f}s")
144
+
145
+ return train_count, val_count
146
+
147
+ def _load_existing_val_labels(self):
148
+ """加载已有的验证集标签"""
149
+ if os.path.exists(self.manifest_path):
150
+ with open(self.manifest_path, 'r') as f:
151
+ manifest = json.load(f)
152
+ return manifest.get('val_labels', [])
153
+ return []
@@ -0,0 +1,178 @@
1
+ # 实时数据增强 Dataset 类
2
+ # 从缓存读取 JPEG,实时执行数据增强
3
+
4
+ import os
5
+ import json
6
+ from PIL import Image
7
+ import torch
8
+ from torch.utils.data import Dataset
9
+ from torchvision import transforms
10
+
11
+
12
+ class RealtimeAugmentDataset(Dataset):
13
+ """
14
+ 实时执行数据增强的 Dataset
15
+
16
+ 流程:
17
+ 1. 从缓存读取 JPEG(224×224)
18
+ 2. CPU 执行 ToTensor + RandomFlip + RandomCrop + ColorJitter
19
+ 3. Normalize 参数提供,可在 GPU 执行
20
+
21
+ 特点:
22
+ - 内存占用低:只缓存当前 batch
23
+ - 数据增强随机性:每次 epoch 看到不同版本
24
+ - 多线程友好:配合 DataLoader num_workers
25
+ """
26
+
27
+ # ImageNet Normalize 参数
28
+ MEAN = [0.485, 0.456, 0.406]
29
+ STD = [0.229, 0.224, 0.225]
30
+
31
+ def __init__(self, cache_dir, augment=True, normalize_on_gpu=False):
32
+ """
33
+ Args:
34
+ cache_dir: 缓存目录路径
35
+ augment: 是否执行数据增强(训练集 True,验证集 False)
36
+ normalize_on_gpu: 是否将 Normalize 移到 GPU 执行
37
+ """
38
+ self.cache_dir = cache_dir
39
+ self.augment = augment
40
+ self.normalize_on_gpu = normalize_on_gpu
41
+
42
+ # 加载清单文件
43
+ manifest_path = os.path.join(cache_dir, 'manifest.json')
44
+ if os.path.exists(manifest_path):
45
+ with open(manifest_path, 'r') as f:
46
+ self.manifest = json.load(f)
47
+ else:
48
+ self.manifest = {'val_labels': []}
49
+
50
+ # 加载图片路径和标签
51
+ self.image_paths = []
52
+ self.labels = []
53
+
54
+ train_cache = os.path.join(cache_dir, 'train')
55
+ if os.path.exists(train_cache):
56
+ # 加载类别映射
57
+ wnids_path = '/data/datasets/tiny-imagenet-200/wnids.txt'
58
+ if os.path.exists(wnids_path):
59
+ with open(wnids_path, 'r') as f:
60
+ wnids = [line.strip() for line in f.readlines()]
61
+ class_to_idx = {wnid: idx for idx, wnid in enumerate(wnids)}
62
+ else:
63
+ class_to_idx = {}
64
+
65
+ classes = sorted(os.listdir(train_cache))
66
+ for cls_idx, cls in enumerate(classes):
67
+ cls_dir = os.path.join(train_cache, cls)
68
+ if os.path.isdir(cls_dir):
69
+ for img_name in os.listdir(cls_dir):
70
+ if img_name.endswith('.JPEG'):
71
+ self.image_paths.append(os.path.join(cls_dir, img_name))
72
+ self.labels.append(class_to_idx.get(cls, cls_idx))
73
+
74
+ # CPU 数据增强(训练集)
75
+ if augment:
76
+ self.transform = transforms.Compose([
77
+ transforms.ToTensor(),
78
+ transforms.RandomHorizontalFlip(p=0.5),
79
+ transforms.RandomCrop(224, padding=4),
80
+ transforms.ColorJitter(brightness=0.2, contrast=0.2),
81
+ ])
82
+ else:
83
+ # 验证集预处理(无增强)
84
+ self.transform = transforms.Compose([
85
+ transforms.ToTensor(),
86
+ ])
87
+
88
+ # Normalize(可在 CPU 或 GPU 执行)
89
+ if not normalize_on_gpu:
90
+ self.transform = transforms.Compose([
91
+ self.transform,
92
+ transforms.Normalize(mean=self.MEAN, std=self.STD)
93
+ ])
94
+
95
+ def __len__(self):
96
+ return len(self.image_paths)
97
+
98
+ def __getitem__(self, idx):
99
+ """
100
+ 获取单张图片
101
+
102
+ Returns:
103
+ image: tensor [3, 224, 224]
104
+ label: int
105
+ """
106
+ img_path = self.image_paths[idx]
107
+ label = self.labels[idx]
108
+
109
+ # 从缓存读取 JPEG
110
+ image = Image.open(img_path).convert('RGB')
111
+
112
+ # 执行变换
113
+ image = self.transform(image)
114
+
115
+ return image, label
116
+
117
+ def get_normalize_params(self):
118
+ """获取 Normalize 参数(供 GPU 执行时使用)"""
119
+ return torch.tensor(self.MEAN).view(3, 1, 1), torch.tensor(self.STD).view(3, 1, 1)
120
+
121
+
122
+ class RealtimeValDataset(Dataset):
123
+ """
124
+ 验证集 Dataset(从缓存读取)
125
+
126
+ 扁平化结构:val/val_<idx>.JPEG
127
+ """
128
+
129
+ MEAN = [0.485, 0.456, 0.406]
130
+ STD = [0.229, 0.224, 0.225]
131
+
132
+ def __init__(self, cache_dir, normalize_on_gpu=False):
133
+ self.cache_dir = cache_dir
134
+ self.val_cache = os.path.join(cache_dir, 'val')
135
+ self.normalize_on_gpu = normalize_on_gpu
136
+
137
+ # 加载清单文件获取标签
138
+ manifest_path = os.path.join(cache_dir, 'manifest.json')
139
+ if os.path.exists(manifest_path):
140
+ with open(manifest_path, 'r') as f:
141
+ manifest = json.load(f)
142
+ self.labels = manifest.get('val_labels', [])
143
+ else:
144
+ self.labels = []
145
+
146
+ # 构建图片路径列表
147
+ self.image_paths = []
148
+ if os.path.exists(self.val_cache):
149
+ for i in range(len(self.labels)):
150
+ img_path = os.path.join(self.val_cache, f'val_{i}.JPEG')
151
+ if os.path.exists(img_path):
152
+ self.image_paths.append(img_path)
153
+
154
+ # 验证集变换(无增强)
155
+ self.transform = transforms.Compose([
156
+ transforms.ToTensor(),
157
+ ])
158
+
159
+ if not normalize_on_gpu:
160
+ self.transform = transforms.Compose([
161
+ self.transform,
162
+ transforms.Normalize(mean=self.MEAN, std=self.STD)
163
+ ])
164
+
165
+ def __len__(self):
166
+ return len(self.image_paths)
167
+
168
+ def __getitem__(self, idx):
169
+ img_path = self.image_paths[idx]
170
+ label = self.labels[idx]
171
+
172
+ image = Image.open(img_path).convert('RGB')
173
+ image = self.transform(image)
174
+
175
+ return image, label
176
+
177
+ def get_normalize_params(self):
178
+ return torch.tensor(self.MEAN).view(3, 1, 1), torch.tensor(self.STD).view(3, 1, 1)
@@ -1,6 +1,8 @@
1
- # Linear 模块
1
+ # LINEAR 模块
2
+ from .lasso_regression import LassoRegression
2
3
  from .logistic_regression import LogisticRegression
4
+ from .naive_bayes import MultinomialNaiveBayes
5
+ from .naive_bayes import GaussianNaiveBayes
3
6
  from .ridge_regression import RidgeRegression
4
- from .lasso_regression import LassoRegression
5
7
 
6
- __all__ = ['LogisticRegression', 'RidgeRegression', 'LassoRegression']
8
+ __all__ = ['LassoRegression', 'LogisticRegression', 'MultinomialNaiveBayes', 'GaussianNaiveBayes', 'RidgeRegression']
@@ -1,4 +1,4 @@
1
- # Neural 模块
1
+ # NEURAL 模块
2
2
  from .perceptron import Perceptron
3
3
 
4
- __all__ = ['Perceptron']
4
+ __all__ = ['Perceptron']
@@ -2,4 +2,4 @@
2
2
  from .kernel_svm import KernelSVM
3
3
  from .simple_svm import SimpleSVM
4
4
 
5
- __all__ = ['KernelSVM', 'SimpleSVM']
5
+ __all__ = ['KernelSVM', 'SimpleSVM']
@@ -1,6 +1,6 @@
1
- # Tree 模块
1
+ # TREE 模块
2
+ from .ada_boost import AdaBoost
2
3
  from .decision_tree_classifier import DecisionTreeClassifier
3
4
  from .random_forest_classifier import RandomForestClassifier
4
- from .ada_boost import AdaBoost
5
5
 
6
- __all__ = ['DecisionTreeClassifier', 'RandomForestClassifier', 'AdaBoost']
6
+ __all__ = ['AdaBoost', 'DecisionTreeClassifier', 'RandomForestClassifier']
@@ -1,5 +1,5 @@
1
- # Unsupervised 模块
1
+ # UNSUPERVISED 模块
2
2
  from .kmeans import KMeans
3
3
  from .pca import PCA
4
4
 
5
- __all__ = ['KMeans', 'PCA']
5
+ __all__ = ['KMeans', 'PCA']
@@ -326,6 +326,44 @@ function findServerPath() {
326
326
  return null
327
327
  }
328
328
 
329
+ /**
330
+ * 查找 kernel_runner.py 路径
331
+ * --dev 模式下需要挂载此文件
332
+ */
333
+ function findKernelRunnerPath() {
334
+ // 开发环境路径:packages/cli/src/commands -> ../../../local-server/src/kernel_runner.py
335
+ const devPath = path.resolve(__dirname, '../../../local-server/src/kernel_runner.py')
336
+ // npm 包路径:packages/cli/src/commands -> ../server/kernel_runner.py(构建后)
337
+ const npmPath = path.resolve(__dirname, '../server/kernel_runner.py')
338
+
339
+ if (fs.existsSync(devPath)) {
340
+ return devPath
341
+ }
342
+ if (fs.existsSync(npmPath)) {
343
+ return npmPath
344
+ }
345
+ return null
346
+ }
347
+
348
+ /**
349
+ * 查找 dmla_progress.py 路径
350
+ * --dev 模式下需要挂载此文件
351
+ */
352
+ function findProgressReporterPath() {
353
+ // 开发环境路径:packages/cli/src/commands -> ../../../local-server/src/dmla_progress.py
354
+ const devPath = path.resolve(__dirname, '../../../local-server/src/dmla_progress.py')
355
+ // npm 包路径:packages/cli/src/commands -> ../server/dmla_progress.py(构建后)
356
+ const npmPath = path.resolve(__dirname, '../server/dmla_progress.py')
357
+
358
+ if (fs.existsSync(devPath)) {
359
+ return devPath
360
+ }
361
+ if (fs.existsSync(npmPath)) {
362
+ return npmPath
363
+ }
364
+ return null
365
+ }
366
+
329
367
  /**
330
368
  * 查找共享模块目录
331
369
  * --dev 模式下需要挂载此目录
@@ -427,10 +465,23 @@ export async function startServerSync(port, useGpu = false, dev = false) {
427
465
 
428
466
  // 查找共享模块路径(--dev 模式需要)
429
467
  const sharedModulesPath = dev ? findSharedModulesPath() : null
468
+ // 查找 kernel_runner.py 路径(--dev 模式需要)
469
+ const kernelRunnerPath = dev ? findKernelRunnerPath() : null
470
+ // 查找 dmla_progress.py 路径(--dev 模式需要)
471
+ const progressReporterPath = dev ? findProgressReporterPath() : null
472
+
430
473
  if (dev && !sharedModulesPath) {
431
474
  console.log(chalk.yellow('⚠️ --dev 模式需要共享模块目录'))
432
475
  console.log(chalk.gray(' 未找到 shared_modules,将仅使用镜像内置模块'))
433
476
  }
477
+ if (dev && !kernelRunnerPath) {
478
+ console.log(chalk.yellow('⚠️ --dev 模式需要 kernel_runner.py'))
479
+ console.log(chalk.gray(' 未找到 kernel_runner.py,将仅使用镜像内置版本'))
480
+ }
481
+ if (dev && !progressReporterPath) {
482
+ console.log(chalk.yellow('⚠️ --dev 模式需要 dmla_progress.py'))
483
+ console.log(chalk.gray(' 未找到 dmla_progress.py,将仅使用镜像内置版本'))
484
+ }
434
485
 
435
486
  console.log(chalk.gray(` 镜像类型: ${imageResolution.message}`))
436
487
  console.log(chalk.gray(' 同步模式启动...'))
@@ -438,6 +489,12 @@ export async function startServerSync(port, useGpu = false, dev = false) {
438
489
  if (dev && sharedModulesPath) {
439
490
  console.log(chalk.gray(` 共享模块: ${sharedModulesPath}`))
440
491
  }
492
+ if (dev && kernelRunnerPath) {
493
+ console.log(chalk.gray(` 执行器: ${kernelRunnerPath}`))
494
+ }
495
+ if (dev && progressReporterPath) {
496
+ console.log(chalk.gray(` 进度报告: ${progressReporterPath}`))
497
+ }
441
498
  console.log()
442
499
 
443
500
  // 设置环境变量
@@ -452,6 +509,12 @@ export async function startServerSync(port, useGpu = false, dev = false) {
452
509
  if (sharedModulesPath) {
453
510
  process.env.SHARED_MODULES_PATH = sharedModulesPath
454
511
  }
512
+ if (kernelRunnerPath) {
513
+ process.env.KERNEL_RUNNER_PATH = kernelRunnerPath
514
+ }
515
+ if (progressReporterPath) {
516
+ process.env.PROGRESS_REPORTER_PATH = progressReporterPath
517
+ }
455
518
  }
456
519
 
457
520
  // 动态 import 服务器模块并直接运行
@@ -542,9 +605,20 @@ export async function startServer(port, useGpu = false, dev = false) {
542
605
 
543
606
  // 查找共享模块路径(--dev 模式需要)
544
607
  const sharedModulesPath = dev ? findSharedModulesPath() : null
608
+ // 查找 kernel_runner.py 路径(--dev 模式需要)
609
+ const kernelRunnerPath = dev ? findKernelRunnerPath() : null
610
+ // 查找 dmla_progress.py 路径(--dev 模式需要)
611
+ const progressReporterPath = dev ? findProgressReporterPath() : null
612
+
545
613
  if (dev && sharedModulesPath) {
546
614
  console.log(chalk.gray(` 共享模块: ${sharedModulesPath}`))
547
615
  }
616
+ if (dev && kernelRunnerPath) {
617
+ console.log(chalk.gray(` 执行器: ${kernelRunnerPath}`))
618
+ }
619
+ if (dev && progressReporterPath) {
620
+ console.log(chalk.gray(` 进度报告: ${progressReporterPath}`))
621
+ }
548
622
 
549
623
  // 日志文件路径
550
624
  const logDir = path.resolve(__dirname, '../../logs')
@@ -574,6 +648,12 @@ export async function startServer(port, useGpu = false, dev = false) {
574
648
  if (sharedModulesPath) {
575
649
  env.SHARED_MODULES_PATH = sharedModulesPath
576
650
  }
651
+ if (kernelRunnerPath) {
652
+ env.KERNEL_RUNNER_PATH = kernelRunnerPath
653
+ }
654
+ if (progressReporterPath) {
655
+ env.PROGRESS_REPORTER_PATH = progressReporterPath
656
+ }
577
657
  }
578
658
 
579
659
  // 写入启动日志