dsh-eval-guard 0.6.1 → 0.7.0

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.
@@ -0,0 +1,358 @@
1
+ # Neural-Pendulum:从零神经网络基础设施与双驱动三阶摆控制
2
+
3
+ ## 任务目标
4
+
5
+ 从零实现一套纯 C++17、仅 CPU、fp32 的神经网络基础设施,并用它训练一个神经网络控制器,控制预编译的双驱动三节摆。
6
+
7
+ 目标:
8
+
9
+ - 从三杆全部向下的固定初始状态起摆
10
+ - 保持三根杆全部向上至少 t_tgt = 10.0 秒
11
+ - 同时保证神经网络基础设施全部通过公开测试
12
+
13
+ 允许使用 PyTorch 做开发阶段的自查和数值对照。最终提交源码中不得出现 torch,评测环境不提供 Python/torch。
14
+
15
+ ## 环境
16
+
17
+ 工作区提供预编译仿真器:
18
+
19
+ ~~~
20
+ tools/sim
21
+ ~~~
22
+
23
+ 不提供仿真器源码和物理参数。
24
+
25
+ ### 初始状态
26
+
27
+ 训练时每次 reset:
28
+
29
+ - 三根杆接近全向下
30
+ - 角度以全向下为中心,只加小范围随机噪声
31
+ - 不添加固定角度偏差
32
+ - 带小范围随机初始角速度
33
+
34
+ 评测时:
35
+
36
+ - 使用固定初始状态
37
+ - 三个固定角度互不相同
38
+ - 三个固定角度的绝对值也互不相同
39
+ - 初始角速度为 0
40
+ - 不加任何随机扰动
41
+
42
+ ### 驱动结构
43
+
44
+ ~~~text
45
+ joint 1:驱动
46
+ joint 2:被动
47
+ joint 3:驱动
48
+ ~~~
49
+
50
+ 第一关节和第三关节各有一个归一化力矩输入。
51
+
52
+ ### 观测
53
+
54
+ 控制器每一步只获得三个关节角度:
55
+
56
+ ~~~text
57
+ a_i = wrap_to_pi(theta_i - pi) i = 1,2,3
58
+ obs = [a1, a2, a3] (rad)
59
+ ~~~
60
+
61
+ 即 3 维观测,单位 rad,不除以 pi。
62
+
63
+ - theta_i 是第 i 根杆的绝对角度
64
+ - a_i 是相对竖直向上的角度误差
65
+ - 不提供角速度
66
+ - 不提供第三杆末端位置
67
+ - 不提供杆长、质量、惯量等物理参数
68
+
69
+ 控制器的内部结构和训练方法由你自行设计。
70
+
71
+ ### 动作
72
+
73
+ 控制器输出两个归一化力矩:
74
+
75
+ ~~~text
76
+ [u1, u3] ∈ [-1, 1]^2
77
+ ~~~
78
+
79
+ 分别作用于第一关节和第三关节。第二关节始终无驱动。
80
+
81
+ 非有限值、超范围或格式错误会判为控制器失败。
82
+
83
+ ### 控制与仿真
84
+
85
+ ~~~
86
+ 控制频率 = 100 Hz
87
+ 控制周期 = 0.01 s
88
+ 最大仿真时间 = 30.0 s
89
+ t_tgt = 10.0 s
90
+ 全向上容差 = 每根杆与竖直方向夹角 <= 0.05 rad
91
+ ~~~
92
+
93
+ 评测会计算:
94
+
95
+ ~~~
96
+ t_can = 三根杆同时处于全向上容差内的最长连续时间
97
+ ~~~
98
+
99
+ 当:
100
+
101
+ ~~~
102
+ t_can >= t_tgt
103
+ 且基础设施测试全部通过
104
+ ~~~
105
+
106
+ 任务完成,计时停止。
107
+
108
+ ## 训练用仿真接口
109
+
110
+ tools/sim 使用 JSONL 协议。
111
+
112
+ 输入:
113
+
114
+ ~~~json
115
+ {"cmd":"reset"}
116
+ {"cmd":"step","torque":[0.5,-0.5]}
117
+ ~~~
118
+
119
+ 输出:
120
+
121
+ ~~~json
122
+ {"obs":[3 floats]}
123
+ ~~~
124
+
125
+ 每个 episode 最多 3000 步(30.0 s)。
126
+
127
+ 达到最大步数后再发送 step:
128
+
129
+ ~~~json
130
+ {"error":"episode_finished"}
131
+ ~~~
132
+
133
+ 此时不返回新观测,也不会继续推进仿真。
134
+ 必须 reset 后才能开始新 episode;reset 前的 step 都返回该错误。
135
+
136
+ 可以反复 reset / step 进行数据采集、模型训练和自测。
137
+
138
+ 环境只返回状态,不提供 reward、loss、cost 或 done 信号。
139
+ 训练信号由你自行构造。
140
+
141
+ ## 交付内容
142
+
143
+ 只提交源码,不提交编译产物。
144
+
145
+ 源码根目录必须支持:
146
+
147
+ ~~~
148
+ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
149
+ cmake --build build -j4
150
+ ~~~
151
+
152
+ 必须产出两个入口:
153
+
154
+ ~~~
155
+ build/nn_selftest
156
+ build/control
157
+ ~~~
158
+
159
+ ### 1. 基础设施入口
160
+
161
+ build/nn_selftest 必须运行公开基础设施测试并返回 0 表示通过。
162
+
163
+ 必须包含的基础设施:
164
+
165
+ - fp32 tensor,形状检查与广播
166
+ - elementwise add/sub/mul/div
167
+ - matmul / linear
168
+ - relu / softmax
169
+ - layernorm
170
+ - conv2d,支持 stride/padding/dilation/groups
171
+ - MSE
172
+ - matmul/linear/relu/layernorm/conv2d/MSE 反向
173
+ - SGD momentum
174
+ - AdamW
175
+ - 模型保存/加载
176
+ - 线程池
177
+
178
+ 占位符或空实现会被判为基础设施错误。
179
+
180
+ 公开测试文件在工作区中,评测时使用评测侧的干净副本。
181
+
182
+ ### 2. 控制器入口
183
+
184
+ build/control 是一个常驻进程,通过 stdin/stdout 使用 JSONL 通信。
185
+
186
+ 输入:
187
+
188
+ ~~~json
189
+ {"type":"reset"}
190
+ {"type":"step","step":0,"state":[3 floats]}
191
+ ~~~
192
+
193
+ 输出:
194
+
195
+ ~~~json
196
+ {"type":"ready"}
197
+ {"type":"action","torque":[0.5,-0.5]}
198
+ ~~~
199
+
200
+ 规则:
201
+
202
+ - 每次评测启动一个新进程
203
+ - 先收到 reset,必须回复 ready
204
+ - 每个 step 必须在 100 ms 内回复
205
+ - 控制器可以保存内部历史状态
206
+ - 崩溃、超时、非法输出会判为控制器失败
207
+
208
+ ## 提交与测试
209
+
210
+ 使用宿主工具:
211
+
212
+ ~~~text
213
+ eval_submit
214
+ eval_test
215
+ ~~~
216
+
217
+ ### eval_submit
218
+
219
+ 把当前工作区源码快照提交到宿主侧不可读目录。每次提交独立保留,不覆盖旧提交。提交本身不改变分数。
220
+
221
+ ### eval_test
222
+
223
+ 由宿主对最新提交快照进行构建和隐藏仿真评估,返回:
224
+
225
+ ~~~text
226
+ stage
227
+ t_can
228
+ completed
229
+ error
230
+ ~~~
231
+
232
+ 只有 eval_test 会更新分数。第一次 eval_test 之前必须先 eval_submit,否则 stage = no_submission。
233
+
234
+ 阶段枚举:
235
+
236
+ ~~~text
237
+ no_submission
238
+ compile
239
+ infra
240
+ controller
241
+ simulation
242
+ scoring
243
+ completed
244
+ busy
245
+ ~~~
246
+
247
+ 最终排行分数不会返回给你,只用于赛后排名。
248
+
249
+ ## 完成条件
250
+
251
+ 当测试返回:
252
+
253
+ ~~~
254
+ stage = completed
255
+ t_can >= 10.0
256
+ ~~~
257
+
258
+ 即任务完成。
259
+
260
+ 完成后:
261
+
262
+ - 立即停止修改
263
+ - 立即总结你的实现
264
+ - 之后的提交和测试请求都会被拒绝
265
+
266
+ ## 约束
267
+
268
+ - 只允许 C++17 + CMake + C++ 标准库
269
+ - 仅 CPU,fp32
270
+ - 提交源码中不得出现 torch
271
+ - 评测环境无 Python/torch
272
+ - 无网络
273
+ - 不得修改仿真器或公开测试
274
+ - 不得提交编译产物
275
+ - 赛后人工审核:控制器必须是神经网络,且必须实际调用你实现的神经网络基础设施;手写控制律、查表、固定轨迹或外部依赖会被判无效
276
+
277
+ ## 优化目标
278
+
279
+ 从接近全向下的初始状态起摆,使三根杆全部进入竖直向上容差,并尽可能长时间保持。
280
+
281
+ 这是一个控制目标,不是固定损失函数。环境不提供 reward/loss;你需要自己决定训练信号、损失、奖励或优化方式。
282
+
283
+ ## 评分规则
284
+
285
+ 任务从会话开始时刻计时。
286
+
287
+ ### 测试请求与分数
288
+
289
+ 提交源码本身不改变分数。
290
+
291
+ 只有申请测试会更新当前分数:
292
+
293
+ ~~~
294
+ 无效测试 / 编译失败 / 基础设施失败 / 控制器失败 / 仿真失败 / 无提交:
295
+ score(t) = -1
296
+
297
+ 有效测试:
298
+ score(t) = (min(t_can, t_tgt) - t_tgt) / t_tgt
299
+ ~~~
300
+
301
+ 其中:
302
+
303
+ ~~~
304
+ t_tgt = 10.0 s
305
+ score(t) 的范围是 [-1, 0]
306
+ t_can = 0 时 score = -1
307
+ t_can >= t_tgt 时 score = 0
308
+ ~~~
309
+
310
+ 分数从测试请求时刻开始生效,直到下一次测试请求。
311
+
312
+ 第一次测试请求之前,score = -1。
313
+
314
+ ### 最终分数
315
+
316
+ 最终分数是时间积分,不除以总时长:
317
+
318
+ ~~~
319
+ final = integral_0^T_done score(t) dt
320
+ ~~~
321
+
322
+ T_done 是第一次返回 completed 的测试请求时间。
323
+
324
+ 因此:
325
+
326
+ - 越早达到完成条件,负分区间越短,最终分数越高
327
+ - 拖得越久,负分区间越长
328
+ - 完成后停止积分
329
+ - 未完成不参与最终排名
330
+
331
+ ### 完成时
332
+
333
+ 当测试返回 completed 时:
334
+
335
+ - 当前 score 固定为 0
336
+ - 时间积分立即停止
337
+ - 之后的提交和测试全部拒绝
338
+ - 不会再有新的分数变化
339
+
340
+ ### 测试结果与最终分数
341
+
342
+ 测试结果会返回:
343
+
344
+ - stage
345
+ - t_can
346
+ - completed
347
+ - error
348
+
349
+ 最终排行分数不会返回给你,只用于赛后排名。
350
+
351
+ ## 建议工作顺序
352
+
353
+ 1. 阅读公开测试和 tools/sim 协议
354
+ 2. 先实现最小可用的 tensor/算子/反向/优化器
355
+ 3. 跑通 nn_selftest
356
+ 4. 用 tools/sim 采集数据并训练控制器
357
+ 5. 反复提交和测试,优先把 t_can 提升到 10 秒
358
+ 6. 完成后立即总结
@@ -0,0 +1,126 @@
1
+ #include <algorithm>
2
+ #include <array>
3
+ #include <cmath>
4
+ #include <cstdio>
5
+ #include <cstring>
6
+ #include <iostream>
7
+ #include <random>
8
+ #include <sstream>
9
+ #include <string>
10
+ #include <vector>
11
+
12
+ namespace {
13
+ constexpr int N = 3;
14
+ constexpr double DT = 0.01;
15
+ constexpr int SUB = 4;
16
+ constexpr int MAX_STEPS = 3000;
17
+ constexpr double PI = 3.14159265358979323846;
18
+ constexpr double GRAV = 9.81;
19
+ constexpr double DAMP = 0.01;
20
+ constexpr double TAU_MAX = 18.0;
21
+ const std::array<double, N> MASS{{1.0, 1.0, 1.0}};
22
+ const std::array<double, N> LEN{{0.5, 0.5, 0.5}};
23
+ const std::array<double, N> R{{0.25, 0.25, 0.25}};
24
+ const std::array<double, N> INERTIA{{MASS[0]*LEN[0]*LEN[0]/12.0, MASS[1]*LEN[1]*LEN[1]/12.0, MASS[2]*LEN[2]*LEN[2]/12.0}};
25
+ const std::array<double, N> GCOEF{{MASS[0]*R[0] + LEN[0]*(MASS[1]+MASS[2]),
26
+ MASS[1]*R[1] + LEN[1]*MASS[2],
27
+ MASS[2]*R[2]}};
28
+
29
+ struct Vec { std::array<double,N> v{}; };
30
+ struct State { std::array<double,N> theta{}; std::array<double,N> omega{}; };
31
+
32
+ double wrap(double x) {
33
+ while (x <= -PI) x += 2*PI;
34
+ while (x > PI) x -= 2*PI;
35
+ return x;
36
+ }
37
+
38
+ static std::array<std::array<double,N>,N> mass_matrix(const std::array<double,N>& q) {
39
+ std::array<std::array<double,N>,N> M{};
40
+ for (int i=0;i<N;i++) for (int j=0;j<N;j++) {
41
+ if (i==j) {
42
+ double tail=0; for (int k=i+1;k<N;k++) tail += MASS[k];
43
+ M[i][j] = INERTIA[i] + MASS[i]*R[i]*R[i] + LEN[i]*LEN[i]*tail;
44
+ } else {
45
+ int a=std::min(i,j), b=std::max(i,j);
46
+ M[i][j] = LEN[a] * GCOEF[b] * std::cos(q[i]-q[j]);
47
+ }
48
+ }
49
+ return M;
50
+ }
51
+ static void solve3(const std::array<std::array<double,N>,N>& A, const std::array<double,N>& b, std::array<double,N>& x) {
52
+ double a[3][4];
53
+ for(int i=0;i<3;i++){ for(int j=0;j<3;j++) a[i][j]=A[i][j]; a[i][3]=b[i]; }
54
+ for(int c=0;c<3;c++){
55
+ int p=c; for(int r=c+1;r<3;r++) if(std::fabs(a[r][c])>std::fabs(a[p][c])) p=r;
56
+ if(p!=c) for(int j=0;j<4;j++) std::swap(a[c][j],a[p][j]);
57
+ double d=a[c][c]; if(std::fabs(d)<1e-15) d=(d<0?-1e-15:1e-15);
58
+ for(int j=c;j<4;j++) a[c][j]/=d;
59
+ for(int r=0;r<3;r++) if(r!=c){ double f=a[r][c]; for(int j=c;j<4;j++) a[r][j]-=f*a[c][j]; }
60
+ }
61
+ for(int i=0;i<3;i++) x[i]=a[i][3];
62
+ }
63
+ static void deriv(const State& s, const std::array<double,N>& tau, State& out) {
64
+ auto M = mass_matrix(s.theta);
65
+ std::array<double,N> C{}, G{}, Q{}, rhs{};
66
+ for(int i=0;i<N;i++){
67
+ double ci=0;
68
+ for(int j=0;j<N;j++) if(j!=i) ci += M[i][j] * std::sin(s.theta[i]-s.theta[j]) * s.omega[j]*s.omega[j];
69
+ C[i]=ci;
70
+ G[i]=GRAV*std::sin(s.theta[i])*GCOEF[i];
71
+ }
72
+ // joint damping: b * D^T D omega, D lower bidiagonal with -1 previous
73
+ for(int i=0;i<N;i++){
74
+ double e=s.omega[i] - (i>0?s.omega[i-1]:0.0);
75
+ Q[i] -= DAMP*e;
76
+ }
77
+ for(int i=0;i<N;i++) rhs[i]=tau[i]-C[i]-G[i]+Q[i];
78
+ solve3(M, rhs, out.omega);
79
+ out.theta = s.omega;
80
+ }
81
+ } // namespace
82
+
83
+ int main(int argc, char** argv) {
84
+ bool eval=false; int seed=0;
85
+ for(int i=1;i<argc;i++){ std::string a=argv[i]; if(a=="--eval") eval=true; else if(a=="--seed" && i+1<argc) seed=std::atoi(argv[++i]); }
86
+ std::mt19937 rng(seed);
87
+ std::uniform_real_distribution<double> u(-1.0,1.0);
88
+ State s;
89
+ int steps=0; bool ready=false;
90
+ auto reset=[&](){ steps=0; ready=true; if(eval){ s.theta={0.013,-0.008,0.021}; s.omega={0,0,0}; } else { s.theta={u(rng)*0.05,u(rng)*0.05,u(rng)*0.05}; s.omega={u(rng)*0.02,u(rng)*0.02,u(rng)*0.02}; } };
91
+ // zero state
92
+ s.theta={0,0,0}; s.omega={0,0,0};
93
+ std::string line;
94
+ while(std::getline(std::cin,line)){
95
+ if(line.empty()) continue;
96
+ if(line.find("reset")!=std::string::npos){ reset(); std::cout << "{\"obs\":[" << wrap(s.theta[0]-PI) << "," << wrap(s.theta[1]-PI) << "," << wrap(s.theta[2]-PI) << "]}\n" << std::flush; continue; }
97
+ if(line.find("step")!=std::string::npos){
98
+ if(!ready || steps>=MAX_STEPS){ std::cout << "{\"error\":\"episode_finished\"}\n" << std::flush; continue; }
99
+ // parse torque array
100
+ double u1=0,u3=0; auto p=line.find('['); if(p!=std::string::npos){ char* end=nullptr; u1=std::strtod(line.c_str()+p+1,&end); if(end){ const char* q=end; while(*q && (*q==','||*q==' ')) q++; u3=std::strtod(q,&end); } }
101
+ u1=std::max(-1.0,std::min(1.0,u1)); u3=std::max(-1.0,std::min(1.0,u3));
102
+ std::array<double,N> tau{{u1*TAU_MAX,0.0,u3*TAU_MAX}};
103
+ // one control step with 4 RK4 substeps
104
+ double h=DT/SUB;
105
+ for(int sub=0;sub<SUB;sub++){
106
+ State k1=s,k2=s,k3=s,k4=s; State d{};
107
+ deriv(k1,tau,d); for(int i=0;i<N;i++){ k2.theta[i]=s.theta[i]+0.5*h*d.theta[i]; k2.omega[i]=s.omega[i]+0.5*h*d.omega[i]; }
108
+ deriv(k2,tau,d); for(int i=0;i<N;i++){ k3.theta[i]=s.theta[i]+0.5*h*d.theta[i]; k3.omega[i]=s.omega[i]+0.5*h*d.omega[i]; }
109
+ deriv(k3,tau,d); for(int i=0;i<N;i++){ k4.theta[i]=s.theta[i]+h*d.theta[i]; k4.omega[i]=s.omega[i]+h*d.omega[i]; }
110
+ deriv(k4,tau,d);
111
+ // need derivatives at all; use helper recompute arrays
112
+ State ds1{},ds2{},ds3{},ds4{};
113
+ deriv(k1,tau,ds1); deriv(k2,tau,ds2); deriv(k3,tau,ds3); deriv(k4,tau,ds4);
114
+ for(int i=0;i<N;i++){
115
+ s.theta[i] += h/6.0*(ds1.theta[i]+2*ds2.theta[i]+2*ds3.theta[i]+ds4.theta[i]);
116
+ s.omega[i] += h/6.0*(ds1.omega[i]+2*ds2.omega[i]+2*ds3.omega[i]+ds4.omega[i]);
117
+ }
118
+ }
119
+ steps++;
120
+ std::cout << "{\"obs\":[" << wrap(s.theta[0]-PI) << "," << wrap(s.theta[1]-PI) << "," << wrap(s.theta[2]-PI) << "]}\n" << std::flush;
121
+ continue;
122
+ }
123
+ std::cout << "{\"error\":\"bad_command\"}\n" << std::flush;
124
+ }
125
+ return 0;
126
+ }
Binary file
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env python3
2
+ """Plugin wrapper for the standalone Neural-Pendulum evaluator."""
3
+ from __future__ import annotations
4
+ import os
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ HERE = Path(__file__).resolve().parent
9
+ STANDALONE = HERE.parent / 'neural-pendulum-eval' / 'run_eval.py'
10
+
11
+ if not STANDALONE.is_file():
12
+ print('neural-pendulum-eval/run_eval.py missing; copy the standalone evaluator folder first', file=sys.stderr)
13
+ raise SystemExit(1)
14
+
15
+ os.execv(sys.executable, [sys.executable, '-X', 'utf8', str(STANDALONE), *sys.argv[1:]])
@@ -94,7 +94,9 @@ window.__ModuleLoader__.load({
94
94
  pipProxyUrl: "",
95
95
  cifarProxyUrl: "",
96
96
  cifarMirror: "toronto",
97
- customCifarUrl: ""
97
+ customCifarUrl: "",
98
+ parallelEval: false,
99
+ taskId: ""
98
100
  };
99
101
  const box = {
100
102
  border: "1px solid var(--vscode-panel-border, #3c3c3c)",
@@ -159,7 +161,9 @@ window.__ModuleLoader__.load({
159
161
  pipProxyUrl: typeof value?.pipProxyUrl === "string" ? value.pipProxyUrl : "",
160
162
  cifarProxyUrl: typeof value?.cifarProxyUrl === "string" ? value.cifarProxyUrl : "",
161
163
  cifarMirror: typeof value?.cifarMirror === "string" && value.cifarMirror !== "" ? value.cifarMirror : "toronto",
162
- customCifarUrl: typeof value?.customCifarUrl === "string" ? value.customCifarUrl : ""
164
+ customCifarUrl: typeof value?.customCifarUrl === "string" ? value.customCifarUrl : "",
165
+ parallelEval: value?.parallelEval === true,
166
+ taskId: typeof value?.taskId === "string" ? value.taskId : ""
163
167
  };
164
168
  }
165
169
  /** The settings card. */
@@ -237,8 +241,15 @@ window.__ModuleLoader__.load({
237
241
  return () => clearInterval(timer);
238
242
  }, []);
239
243
  (0, react.useEffect)(() => {
240
- if (state !== null && state.tasks.length > 0 && (taskId === "" || !state.tasks.some((task) => task.id === taskId))) setTaskId(state.tasks[0].id);
241
- }, [state, taskId]);
244
+ if (state !== null && state.tasks.length > 0 && (taskId === "" || !state.tasks.some((task) => task.id === taskId))) {
245
+ if (draft.taskId !== "" && state.tasks.some((task) => task.id === draft.taskId)) setTaskId(draft.taskId);
246
+ else setTaskId(state.tasks[0].id);
247
+ }
248
+ }, [
249
+ state,
250
+ taskId,
251
+ draft.taskId
252
+ ]);
242
253
  const ready = snapshot.status === "ready" && snapshot.writable;
243
254
  const save = async (patch) => {
244
255
  if (!ready || snapshot.revision === void 0) return;
@@ -274,6 +285,7 @@ window.__ModuleLoader__.load({
274
285
  };
275
286
  const selectTask = (next) => {
276
287
  setTaskId(next);
288
+ save({ taskId: next });
277
289
  try {
278
290
  localStorage.setItem("evalGuard.taskId", next);
279
291
  } catch {}
@@ -332,6 +344,7 @@ window.__ModuleLoader__.load({
332
344
  };
333
345
  const install = async () => {
334
346
  if (workspaceId === "" || taskId === "") return;
347
+ await save({ taskId });
335
348
  setError("");
336
349
  setNotice("");
337
350
  setJobNotice("");
@@ -368,6 +381,7 @@ window.__ModuleLoader__.load({
368
381
  };
369
382
  const evaluate = async () => {
370
383
  if (workspaceId === "" || taskId === "") return;
384
+ await save({ taskId });
371
385
  setError("");
372
386
  setNotice("");
373
387
  setJobNotice("");
@@ -814,6 +828,23 @@ window.__ModuleLoader__.load({
814
828
  " ",
815
829
  t("settings.timeLimit")
816
830
  ] }),
831
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
832
+ style: { marginLeft: 16 },
833
+ children: [
834
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
835
+ type: "checkbox",
836
+ checked: draft.parallelEval,
837
+ disabled: !ready,
838
+ onChange: (event) => void save({ parallelEval: event.target.checked })
839
+ }),
840
+ " ",
841
+ t("settings.parallelEval")
842
+ ]
843
+ }),
844
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
845
+ style: note,
846
+ children: t("settings.parallelEvalHint")
847
+ }),
817
848
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
818
849
  type: "number",
819
850
  min: 1,
@@ -998,6 +1029,8 @@ window.__ModuleLoader__.load({
998
1029
  "settings.runningForce": "强行开启",
999
1030
  "settings.timeLimit": "开启最长时长",
1000
1031
  "settings.timeLimitHint": "从每条用户消息发送时开始计时;到点只终止当前轮次,并递归清理子代理与后台任务。",
1032
+ "settings.parallelEval": "多 agent 并行评测",
1033
+ "settings.parallelEvalHint": "启用后,安装任务时会在提示词中提醒 agent:运行环境可能因其他 agent 的行为而波动,需要自行适应。",
1001
1034
  "settings.minutes": "分钟",
1002
1035
  "settings.forbidden": "额外禁止路径",
1003
1036
  "settings.forbiddenHint": "每行一个绝对路径;# 开头为注释;! 开头表示在禁止范围内放开该子路径;按行顺序生效。默认空。",
@@ -1041,6 +1074,8 @@ window.__ModuleLoader__.load({
1041
1074
  "settings.recent": "最近事件",
1042
1075
  "settings.none": "暂无。",
1043
1076
  "settings.warningTitle": "评测守护警告",
1077
+ "settings.cifarProxy": "CIFAR 下载代理",
1078
+ "settings.cifarMirror": "CIFAR 下载镜像",
1044
1079
  "settings.close": "关闭"
1045
1080
  };
1046
1081
  const en = {
@@ -1053,6 +1088,8 @@ window.__ModuleLoader__.load({
1053
1088
  "settings.runningForce": "Force enable",
1054
1089
  "settings.timeLimit": "Enable time limit",
1055
1090
  "settings.timeLimitHint": "The clock starts at each user message; at the deadline only the current turn is cancelled, and subagents/jobs are cleaned up recursively.",
1091
+ "settings.parallelEval": "Parallel agent evaluation",
1092
+ "settings.parallelEvalHint": "When enabled, install prompts warn the agent that the runtime may fluctuate because other agents share the machine; it must adapt.",
1056
1093
  "settings.minutes": "minutes",
1057
1094
  "settings.forbidden": "Extra forbidden paths",
1058
1095
  "settings.forbiddenHint": "One absolute path per line; # comments; ! re-allows a subpath; later lines win. Empty by default.",