rl-rockcli 0.0.2 → 0.0.3
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/README.md +400 -0
- package/index.js +51 -21
- package/package.json +1 -1
- package/commands/log/core/constants.js +0 -237
- package/commands/log/core/display.js +0 -370
- package/commands/log/core/search.js +0 -330
- package/commands/log/core/tail.js +0 -216
- package/commands/log/core/utils.js +0 -424
- package/commands/log.js +0 -298
- package/commands/sandbox/core/log-bridge.js +0 -119
- package/commands/sandbox/core/replay/analyzer.js +0 -311
- package/commands/sandbox/core/replay/batch-orchestrator.js +0 -536
- package/commands/sandbox/core/replay/batch-task.js +0 -369
- package/commands/sandbox/core/replay/concurrent-display.js +0 -70
- package/commands/sandbox/core/replay/concurrent-orchestrator.js +0 -170
- package/commands/sandbox/core/replay/data-source.js +0 -86
- package/commands/sandbox/core/replay/display.js +0 -231
- package/commands/sandbox/core/replay/executor.js +0 -634
- package/commands/sandbox/core/replay/history-fetcher.js +0 -124
- package/commands/sandbox/core/replay/index.js +0 -338
- package/commands/sandbox/core/replay/loghouse-data-source.js +0 -177
- package/commands/sandbox/core/replay/pid-mapping.js +0 -26
- package/commands/sandbox/core/replay/request.js +0 -109
- package/commands/sandbox/core/replay/worker.js +0 -166
- package/commands/sandbox/core/session.js +0 -346
- package/commands/sandbox/log-bridge.js +0 -2
- package/commands/sandbox/ray.js +0 -2
- package/commands/sandbox/replay/analyzer.js +0 -311
- package/commands/sandbox/replay/batch-orchestrator.js +0 -536
- package/commands/sandbox/replay/batch-task.js +0 -369
- package/commands/sandbox/replay/concurrent-display.js +0 -70
- package/commands/sandbox/replay/concurrent-orchestrator.js +0 -170
- package/commands/sandbox/replay/display.js +0 -231
- package/commands/sandbox/replay/executor.js +0 -634
- package/commands/sandbox/replay/history-fetcher.js +0 -118
- package/commands/sandbox/replay/index.js +0 -338
- package/commands/sandbox/replay/pid-mapping.js +0 -26
- package/commands/sandbox/replay/request.js +0 -109
- package/commands/sandbox/replay/worker.js +0 -166
- package/commands/sandbox/replay.js +0 -2
- package/commands/sandbox/session.js +0 -2
- package/commands/sandbox-original.js +0 -1393
- package/commands/sandbox.js +0 -499
- package/help/help.json +0 -1071
- package/help/middleware.js +0 -71
- package/help/renderer.js +0 -800
- package/lib/plugin-context.js +0 -40
- package/sdks/sandbox/core/client.js +0 -845
- package/sdks/sandbox/core/config.js +0 -70
- package/sdks/sandbox/core/types.js +0 -74
- package/sdks/sandbox/httpLogger.js +0 -251
- package/sdks/sandbox/index.js +0 -9
- package/utils/asciiArt.js +0 -138
- package/utils/bun-compat.js +0 -59
- package/utils/ciPipelines.js +0 -138
- package/utils/cli.js +0 -17
- package/utils/command-router.js +0 -79
- package/utils/configManager.js +0 -503
- package/utils/dependency-resolver.js +0 -135
- package/utils/eagleeye_traceid.js +0 -151
- package/utils/envDetector.js +0 -78
- package/utils/execution_logger.js +0 -415
- package/utils/featureManager.js +0 -68
- package/utils/firstTimeTip.js +0 -44
- package/utils/hook-manager.js +0 -125
- package/utils/http-logger.js +0 -264
- package/utils/i18n.js +0 -139
- package/utils/image-progress.js +0 -159
- package/utils/logger.js +0 -154
- package/utils/plugin-loader.js +0 -124
- package/utils/plugin-manager.js +0 -348
- package/utils/ray_cli_wrapper.js +0 -746
- package/utils/sandbox-client.js +0 -419
- package/utils/terminal.js +0 -32
- package/utils/tips.js +0 -106
|
@@ -1,536 +0,0 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const fs = require('fs-extra');
|
|
3
|
-
const logger = require('../../../../utils/logger');
|
|
4
|
-
const { SandboxClient, SandboxConfig } = require('../../../../sdks/sandbox');
|
|
5
|
-
const { RequestAnalyzer } = require('./analyzer');
|
|
6
|
-
const { ReplayExecutor } = require('./executor');
|
|
7
|
-
const { SandboxTask, BatchReplayResult, TaskStatus, formatDuration } = require('./batch-task');
|
|
8
|
-
const { HistoryFetcher } = require('./history-fetcher');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 批量回放协调器
|
|
12
|
-
* 管理多个沙箱的批量回放流程
|
|
13
|
-
*/
|
|
14
|
-
class BatchReplayOrchestrator {
|
|
15
|
-
/**
|
|
16
|
-
* @param {Object} options - 配置选项
|
|
17
|
-
* @param {number} options.concurrency - 并发数
|
|
18
|
-
* @param {Object} options.baseConfig - 基础沙箱配置
|
|
19
|
-
* @param {string} options.mode - 回放模式
|
|
20
|
-
* @param {string} options.timing - 时间控制
|
|
21
|
-
* @param {Array} options.filters - 过滤器
|
|
22
|
-
* @param {boolean} options.autoFetchHistory - 是否自动获取历史
|
|
23
|
-
* @param {boolean} options.continueOnError - 错误时是否继续
|
|
24
|
-
* @param {boolean} options.quiet - 静默模式
|
|
25
|
-
* @param {boolean} options.dryRun - 预演模式
|
|
26
|
-
* @param {boolean} options.executeStop - 是否执行 stop 请求
|
|
27
|
-
* @param {string} options.reportFile - 报告文件路径
|
|
28
|
-
* @param {string} options.logFileBase - 日志文件基础路径
|
|
29
|
-
*/
|
|
30
|
-
constructor(options) {
|
|
31
|
-
this.concurrency = options.concurrency || 1;
|
|
32
|
-
this.baseConfig = options.baseConfig;
|
|
33
|
-
this.mode = options.mode || 'smart';
|
|
34
|
-
this.timing = options.timing || 'none';
|
|
35
|
-
this.interval = options.interval || 5;
|
|
36
|
-
this.filters = options.filters || [];
|
|
37
|
-
this.autoFetchHistory = options.autoFetchHistory !== false;
|
|
38
|
-
this.continueOnError = options.continueOnError !== false;
|
|
39
|
-
this.quiet = options.quiet || false;
|
|
40
|
-
this.dryRun = options.dryRun || false;
|
|
41
|
-
this.executeStop = options.executeStop || false;
|
|
42
|
-
this.reportFile = options.reportFile || 'batch-replay-report.json';
|
|
43
|
-
this.logFileBase = options.logFileBase || 'replay.log';
|
|
44
|
-
this.verboseLog = options.verboseLog || false;
|
|
45
|
-
|
|
46
|
-
this.historyFetcher = new HistoryFetcher();
|
|
47
|
-
this.tasks = [];
|
|
48
|
-
this.result = null;
|
|
49
|
-
this.progressIntervalId = null;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* 解析输入文件
|
|
54
|
-
* @param {string} filePath - 输入文件路径
|
|
55
|
-
* @returns {Object} - 解析后的输入数据
|
|
56
|
-
*/
|
|
57
|
-
parseInputFile(filePath) {
|
|
58
|
-
if (!fs.existsSync(filePath)) {
|
|
59
|
-
throw new Error(`Input file not found: ${filePath}`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
63
|
-
let data;
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
data = JSON.parse(content);
|
|
67
|
-
} catch (error) {
|
|
68
|
-
throw new Error(`Failed to parse JSON from ${filePath}: ${error.message}`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// 支持单个 history 文件格式 (有 sandboxId 和 requests)
|
|
72
|
-
if (data.sandboxId && data.requests && Array.isArray(data.requests)) {
|
|
73
|
-
// 转换为标准批量格式
|
|
74
|
-
return {
|
|
75
|
-
metadata: {
|
|
76
|
-
description: `Single sandbox replay: ${data.name || data.sandboxId}`,
|
|
77
|
-
sourceFile: filePath
|
|
78
|
-
},
|
|
79
|
-
defaults: {},
|
|
80
|
-
sandboxes: [data]
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// 验证标准批量格式
|
|
85
|
-
if (!data.sandboxes || !Array.isArray(data.sandboxes)) {
|
|
86
|
-
throw new Error('Invalid input format: "sandboxes" array is required, or provide a single sandbox history file with "sandboxId" and "requests"');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (data.sandboxes.length === 0) {
|
|
90
|
-
throw new Error('No sandboxes specified in input file');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// 验证每个沙箱配置
|
|
94
|
-
data.sandboxes.forEach((sandbox, index) => {
|
|
95
|
-
if (!sandbox.sandboxId) {
|
|
96
|
-
throw new Error(`Invalid sandbox at index ${index}: "sandboxId" is required`);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
return data;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* 从输入数据创建任务列表
|
|
105
|
-
* @param {Object} inputData - 解析后的输入数据
|
|
106
|
-
* @returns {SandboxTask[]}
|
|
107
|
-
*/
|
|
108
|
-
createTasks(inputData) {
|
|
109
|
-
const defaults = inputData.defaults || {};
|
|
110
|
-
const tasks = [];
|
|
111
|
-
|
|
112
|
-
inputData.sandboxes.forEach((sandboxConfig, index) => {
|
|
113
|
-
const task = new SandboxTask(index, sandboxConfig, defaults);
|
|
114
|
-
tasks.push(task);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
return tasks;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* 执行批量回放
|
|
122
|
-
* @param {string} inputFilePath - 输入文件路径
|
|
123
|
-
* @returns {Promise<BatchReplayResult>}
|
|
124
|
-
*/
|
|
125
|
-
async execute(inputFilePath) {
|
|
126
|
-
// 1. 解析输入文件
|
|
127
|
-
const inputData = this.parseInputFile(inputFilePath);
|
|
128
|
-
|
|
129
|
-
// 2. 初始化结果对象
|
|
130
|
-
this.result = new BatchReplayResult({
|
|
131
|
-
inputFile: inputFilePath,
|
|
132
|
-
inputMetadata: inputData.metadata || null,
|
|
133
|
-
concurrency: this.concurrency,
|
|
134
|
-
mode: this.mode,
|
|
135
|
-
timing: this.timing
|
|
136
|
-
});
|
|
137
|
-
this.result.start();
|
|
138
|
-
|
|
139
|
-
// 3. 创建任务列表
|
|
140
|
-
this.tasks = this.createTasks(inputData);
|
|
141
|
-
|
|
142
|
-
if (!this.quiet) {
|
|
143
|
-
this.displayBatchSummary();
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// 4. 预演模式 - 只显示输入文件摘要,不获取历史
|
|
147
|
-
if (this.dryRun) {
|
|
148
|
-
this.displayDryRunSummaryFromInput();
|
|
149
|
-
this.result.end();
|
|
150
|
-
return this.result;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// 5. 获取历史(如果需要)
|
|
154
|
-
if (this.autoFetchHistory) {
|
|
155
|
-
await this.fetchHistories();
|
|
156
|
-
} else {
|
|
157
|
-
// 标记所有预加载的任务为 ready,并显示进度
|
|
158
|
-
if (!this.quiet) {
|
|
159
|
-
console.log('\n📥 Phase 1/2: Validating Pre-loaded Histories');
|
|
160
|
-
console.log('-'.repeat(40));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
let completedCount = 0;
|
|
164
|
-
this.tasks.forEach(task => {
|
|
165
|
-
completedCount++;
|
|
166
|
-
if (task.hasRequests()) {
|
|
167
|
-
task.status = TaskStatus.READY;
|
|
168
|
-
if (!this.quiet) {
|
|
169
|
-
console.log(` [${completedCount}/${this.tasks.length}] ✅ ${task.name} (${task.requests.length} requests)`);
|
|
170
|
-
}
|
|
171
|
-
} else {
|
|
172
|
-
task.status = TaskStatus.FAILED;
|
|
173
|
-
task.error = 'No requests provided and auto-fetch-history is disabled';
|
|
174
|
-
task.errorPhase = 'parse';
|
|
175
|
-
if (!this.quiet) {
|
|
176
|
-
console.log(` [${completedCount}/${this.tasks.length}] ❌ ${task.name} (no requests)`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// 6. 过滤出可执行的任务
|
|
183
|
-
const validTasks = this.tasks.filter(t => t.status === TaskStatus.READY);
|
|
184
|
-
|
|
185
|
-
if (validTasks.length === 0) {
|
|
186
|
-
if (!this.quiet) {
|
|
187
|
-
logger.warn('No valid tasks to execute after history fetch.');
|
|
188
|
-
}
|
|
189
|
-
this.result.end();
|
|
190
|
-
this.result.generateFromTasks(this.tasks);
|
|
191
|
-
await this.saveReport();
|
|
192
|
-
return this.result;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// 7. 执行回放
|
|
196
|
-
await this.executeReplays(validTasks);
|
|
197
|
-
|
|
198
|
-
// 8. 生成结果
|
|
199
|
-
this.result.end();
|
|
200
|
-
this.result.generateFromTasks(this.tasks);
|
|
201
|
-
|
|
202
|
-
// 9. 保存报告
|
|
203
|
-
await this.saveReport();
|
|
204
|
-
|
|
205
|
-
// 10. 显示摘要
|
|
206
|
-
if (!this.quiet) {
|
|
207
|
-
this.displayFinalSummary();
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return this.result;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* 显示批量回放摘要
|
|
215
|
-
*/
|
|
216
|
-
displayBatchSummary() {
|
|
217
|
-
console.log('\n' + '═'.repeat(70));
|
|
218
|
-
console.log('📋 Batch Replay Mode');
|
|
219
|
-
console.log('═'.repeat(70));
|
|
220
|
-
console.log(` Total Sandboxes: ${this.tasks.length}`);
|
|
221
|
-
console.log(` Concurrency: ${this.concurrency}`);
|
|
222
|
-
console.log(` Mode: ${this.mode}`);
|
|
223
|
-
console.log(` Timing: ${this.timing}`);
|
|
224
|
-
console.log(` Auto Fetch History: ${this.autoFetchHistory}`);
|
|
225
|
-
console.log('═'.repeat(70) + '\n');
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* 获取所有沙箱的历史
|
|
230
|
-
*/
|
|
231
|
-
async fetchHistories() {
|
|
232
|
-
if (!this.quiet) {
|
|
233
|
-
console.log('\n📥 Phase 1/2: Fetching Histories');
|
|
234
|
-
console.log('-'.repeat(40));
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
await this.historyFetcher.fetchBatch(
|
|
238
|
-
this.tasks,
|
|
239
|
-
this.concurrency,
|
|
240
|
-
{
|
|
241
|
-
quiet: this.quiet,
|
|
242
|
-
onProgress: (completed, total, task) => {
|
|
243
|
-
if (!this.quiet) {
|
|
244
|
-
const status = task.status === TaskStatus.READY ? '✅' : '❌';
|
|
245
|
-
const requestCount = task.requests ? task.requests.length : 0;
|
|
246
|
-
console.log(` [${completed}/${total}] ${status} ${task.name} (${requestCount} requests)`);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* 执行回放任务
|
|
255
|
-
* @param {SandboxTask[]} tasks - 待执行的任务
|
|
256
|
-
*/
|
|
257
|
-
async executeReplays(tasks) {
|
|
258
|
-
if (!this.quiet) {
|
|
259
|
-
console.log('\n🔄 Phase 2/2: Executing Replays');
|
|
260
|
-
console.log('-'.repeat(40));
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// 创建任务队列
|
|
264
|
-
const queue = [...tasks];
|
|
265
|
-
let completed = 0;
|
|
266
|
-
|
|
267
|
-
// 启动进度监控
|
|
268
|
-
if (!this.quiet) {
|
|
269
|
-
this.startProgressMonitor(tasks);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Worker 函数
|
|
273
|
-
const worker = async (workerId) => {
|
|
274
|
-
while (queue.length > 0) {
|
|
275
|
-
const task = queue.shift();
|
|
276
|
-
if (!task) break;
|
|
277
|
-
|
|
278
|
-
await this.executeReplayTask(task, workerId);
|
|
279
|
-
completed++;
|
|
280
|
-
|
|
281
|
-
if (!this.quiet) {
|
|
282
|
-
const status = task.status === TaskStatus.COMPLETED ? '✅' :
|
|
283
|
-
task.status === TaskStatus.FAILED ? '❌' : '⏸️';
|
|
284
|
-
const successCount = task.replayResult ? task.replayResult.success : 0;
|
|
285
|
-
const failedCount = task.replayResult ? task.replayResult.failed : 0;
|
|
286
|
-
console.log(` [${completed}/${tasks.length}] ${status} ${task.name} (success: ${successCount}, failed: ${failedCount})`);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
// 启动并发 workers
|
|
292
|
-
const workers = [];
|
|
293
|
-
const actualConcurrency = Math.min(this.concurrency, tasks.length);
|
|
294
|
-
|
|
295
|
-
for (let i = 0; i < actualConcurrency; i++) {
|
|
296
|
-
workers.push(worker(i + 1));
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// 等待所有 workers 完成
|
|
300
|
-
await Promise.allSettled(workers);
|
|
301
|
-
|
|
302
|
-
// 停止进度监控
|
|
303
|
-
this.stopProgressMonitor();
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* 执行单个回放任务
|
|
308
|
-
* @param {SandboxTask} task - 任务
|
|
309
|
-
* @param {number} workerId - Worker ID
|
|
310
|
-
*/
|
|
311
|
-
async executeReplayTask(task, workerId) {
|
|
312
|
-
task.startReplay();
|
|
313
|
-
task.logFile = this.getTaskLogFileName(task.index);
|
|
314
|
-
|
|
315
|
-
try {
|
|
316
|
-
// 创建沙箱配置
|
|
317
|
-
const config = new SandboxConfig({
|
|
318
|
-
baseUrl: this.baseConfig.baseUrl,
|
|
319
|
-
xrlAuthorization: this.baseConfig.xrlAuthorization,
|
|
320
|
-
cluster: task.cluster || this.baseConfig.cluster,
|
|
321
|
-
userId: task.userId || this.baseConfig.userId,
|
|
322
|
-
experimentId: task.experimentId || this.baseConfig.experimentId,
|
|
323
|
-
extraHeaders: this.baseConfig.extraHeaders || {},
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
// 创建客户端
|
|
327
|
-
const client = new SandboxClient(config);
|
|
328
|
-
|
|
329
|
-
// 分析请求
|
|
330
|
-
const analyzer = new RequestAnalyzer({
|
|
331
|
-
mode: task.mode || this.mode,
|
|
332
|
-
filters: [...this.filters, ...task.filters],
|
|
333
|
-
executeStop: this.executeStop
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
const plan = analyzer.analyze(task.requests);
|
|
337
|
-
|
|
338
|
-
if (plan.executeCount === 0) {
|
|
339
|
-
task.completeReplay({
|
|
340
|
-
total: 0,
|
|
341
|
-
success: 0,
|
|
342
|
-
failed: 0,
|
|
343
|
-
skipped: plan.skippedCount
|
|
344
|
-
}, null, []);
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// 创建执行器
|
|
349
|
-
const executor = new ReplayExecutor(client, {
|
|
350
|
-
timing: task.timing || this.timing,
|
|
351
|
-
interval: this.interval,
|
|
352
|
-
quiet: true, // 批量模式下强制静默
|
|
353
|
-
logFile: task.logFile,
|
|
354
|
-
verboseLog: this.verboseLog,
|
|
355
|
-
plan: plan,
|
|
356
|
-
workerId: workerId
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
// 执行回放
|
|
360
|
-
const results = await executor.execute(plan);
|
|
361
|
-
|
|
362
|
-
// 完成任务
|
|
363
|
-
task.completeReplay(
|
|
364
|
-
results,
|
|
365
|
-
executor.currentSandboxId
|
|
366
|
-
);
|
|
367
|
-
|
|
368
|
-
} catch (error) {
|
|
369
|
-
task.failReplay(error);
|
|
370
|
-
|
|
371
|
-
if (!this.continueOnError) {
|
|
372
|
-
throw error;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* 获取任务专属的日志文件名
|
|
379
|
-
* @param {number} taskIndex - 任务索引
|
|
380
|
-
* @returns {string}
|
|
381
|
-
*/
|
|
382
|
-
getTaskLogFileName(taskIndex) {
|
|
383
|
-
const ext = path.extname(this.logFileBase);
|
|
384
|
-
const base = path.basename(this.logFileBase, ext);
|
|
385
|
-
const dir = path.dirname(this.logFileBase);
|
|
386
|
-
return path.join(dir, `${base}-sandbox-${taskIndex}${ext}`);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* 启动进度监控
|
|
391
|
-
* @param {SandboxTask[]} tasks - 任务列表
|
|
392
|
-
*/
|
|
393
|
-
startProgressMonitor(tasks) {
|
|
394
|
-
this.progressIntervalId = setInterval(() => {
|
|
395
|
-
const running = tasks.filter(t => t.status === TaskStatus.RUNNING);
|
|
396
|
-
const completed = tasks.filter(t => t.status === TaskStatus.COMPLETED || t.status === TaskStatus.FAILED);
|
|
397
|
-
|
|
398
|
-
console.log('\n--- Progress Update ---');
|
|
399
|
-
console.log(` Completed: ${completed.length}/${tasks.length}`);
|
|
400
|
-
running.forEach(task => {
|
|
401
|
-
console.log(` ⏳ ${task.name}... running`);
|
|
402
|
-
});
|
|
403
|
-
console.log('-'.repeat(23) + '\n');
|
|
404
|
-
}, 30000); // 每 30 秒更新一次
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* 停止进度监控
|
|
409
|
-
*/
|
|
410
|
-
stopProgressMonitor() {
|
|
411
|
-
if (this.progressIntervalId) {
|
|
412
|
-
clearInterval(this.progressIntervalId);
|
|
413
|
-
this.progressIntervalId = null;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* 显示预演摘要(从输入文件,不获取历史)
|
|
419
|
-
*/
|
|
420
|
-
displayDryRunSummaryFromInput() {
|
|
421
|
-
console.log('\n' + '═'.repeat(70));
|
|
422
|
-
console.log('🔍 DRY RUN - Preview Only (no history fetched, no requests executed)');
|
|
423
|
-
console.log('═'.repeat(70));
|
|
424
|
-
|
|
425
|
-
console.log(`\n📊 Configuration:`);
|
|
426
|
-
console.log(` Total Sandboxes: ${this.tasks.length}`);
|
|
427
|
-
console.log(` Concurrency: ${this.concurrency}`);
|
|
428
|
-
console.log(` Mode: ${this.mode}`);
|
|
429
|
-
console.log(` Timing: ${this.timing}`);
|
|
430
|
-
console.log(` Auto Fetch History: ${this.autoFetchHistory}`);
|
|
431
|
-
|
|
432
|
-
const preloadedCount = this.tasks.filter(t => t.hasRequests()).length;
|
|
433
|
-
console.log(`\n📋 Sandboxes:`);
|
|
434
|
-
console.log(` With Pre-loaded Requests: ${preloadedCount}`);
|
|
435
|
-
console.log(` Need History Fetch: ${this.tasks.length - preloadedCount}`);
|
|
436
|
-
|
|
437
|
-
console.log(`\n📝 Sandbox List:`);
|
|
438
|
-
this.tasks.forEach((task, index) => {
|
|
439
|
-
const hasRequests = task.hasRequests();
|
|
440
|
-
const requestInfo = hasRequests ? `${task.requests.length} requests (pre-loaded)` : 'will fetch history';
|
|
441
|
-
console.log(` ${index + 1}. ${task.name} (${task.sandboxId.substring(0, 8)}...) - ${requestInfo}`);
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
console.log('\n' + '═'.repeat(70));
|
|
445
|
-
console.log('Dry run completed - no history fetched, no requests executed');
|
|
446
|
-
console.log('═'.repeat(70) + '\n');
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* 显示预演摘要(获取历史后)
|
|
451
|
-
* @param {SandboxTask[]} validTasks - 有效任务
|
|
452
|
-
*/
|
|
453
|
-
displayDryRunSummary(validTasks) {
|
|
454
|
-
console.log('\n' + '═'.repeat(70));
|
|
455
|
-
console.log('🔍 DRY RUN - Preview Only (no requests will be executed)');
|
|
456
|
-
console.log('═'.repeat(70));
|
|
457
|
-
|
|
458
|
-
console.log(`\n📊 Summary:`);
|
|
459
|
-
console.log(` Total Sandboxes: ${this.tasks.length}`);
|
|
460
|
-
console.log(` Valid for Replay: ${validTasks.length}`);
|
|
461
|
-
console.log(` Failed History Fetch: ${this.tasks.filter(t => t.status === TaskStatus.FAILED).length}`);
|
|
462
|
-
|
|
463
|
-
console.log(`\n📋 Sandboxes to Replay:`);
|
|
464
|
-
validTasks.forEach((task, index) => {
|
|
465
|
-
const requestCount = task.requests ? task.requests.length : 0;
|
|
466
|
-
console.log(` ${index + 1}. ${task.name} (${task.sandboxId.substring(0, 8)}...) - ${requestCount} requests`);
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
console.log('\n' + '═'.repeat(70));
|
|
470
|
-
console.log('Dry run completed - no requests were executed');
|
|
471
|
-
console.log('═'.repeat(70) + '\n');
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
/**
|
|
475
|
-
* 显示最终摘要
|
|
476
|
-
*/
|
|
477
|
-
displayFinalSummary() {
|
|
478
|
-
const summary = this.result.summary;
|
|
479
|
-
|
|
480
|
-
console.log('\n' + '═'.repeat(70));
|
|
481
|
-
console.log('📊 Batch Replay Summary');
|
|
482
|
-
console.log('═'.repeat(70));
|
|
483
|
-
|
|
484
|
-
console.log(`\nTotal Sandboxes: ${summary.totalSandboxes}`);
|
|
485
|
-
console.log(`├── ✅ Successful: ${summary.successful}`);
|
|
486
|
-
console.log(`├── ❌ Failed: ${summary.failed}`);
|
|
487
|
-
console.log(`└── ⏭️ Skipped: ${summary.skipped}`);
|
|
488
|
-
|
|
489
|
-
console.log(`\nDuration: ${summary.totalDuration}`);
|
|
490
|
-
|
|
491
|
-
// 显示每个沙箱的结果
|
|
492
|
-
if (this.result.sandboxResults.length > 0) {
|
|
493
|
-
console.log('\nPer-Sandbox Results:');
|
|
494
|
-
console.log('┌' + '─'.repeat(8) + '┬' + '─'.repeat(24) + '┬' + '─'.repeat(10) + '┬' + '─'.repeat(10) + '┬' + '─'.repeat(12) + '┐');
|
|
495
|
-
console.log('│ Index │ Name │ Status │ Success │ Duration │');
|
|
496
|
-
console.log('├' + '─'.repeat(8) + '┼' + '─'.repeat(24) + '┼' + '─'.repeat(10) + '┼' + '─'.repeat(10) + '┼' + '─'.repeat(12) + '┤');
|
|
497
|
-
|
|
498
|
-
this.result.sandboxResults.forEach(result => {
|
|
499
|
-
const status = result.status === TaskStatus.COMPLETED ? '✅' :
|
|
500
|
-
result.status === TaskStatus.FAILED ? '❌' : '⏸️';
|
|
501
|
-
const success = result.replay ? result.replay.success.toString() : '-';
|
|
502
|
-
const duration = result.replay && result.replay.duration ?
|
|
503
|
-
formatDuration(result.replay.duration) : '-';
|
|
504
|
-
const name = result.name.length > 22 ? result.name.substring(0, 19) + '...' : result.name;
|
|
505
|
-
|
|
506
|
-
console.log(`│ ${result.index.toString().padEnd(6)} │ ${name.padEnd(22)} │ ${status.padEnd(8)} │ ${success.padEnd(8)} │ ${duration.padEnd(10)} │`);
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
console.log('└' + '─'.repeat(8) + '┴' + '─'.repeat(24) + '┴' + '─'.repeat(10) + '┴' + '─'.repeat(10) + '┴' + '─'.repeat(12) + '┘');
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// 显示失败的沙箱
|
|
513
|
-
if (this.result.failedSandboxes.length > 0) {
|
|
514
|
-
console.log('\nFailed Sandboxes:');
|
|
515
|
-
this.result.failedSandboxes.forEach((failed, index) => {
|
|
516
|
-
console.log(`├── [${failed.name}] ${failed.error} (phase: ${failed.phase})`);
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
console.log(`\n📝 Report saved to: ${this.reportFile}`);
|
|
521
|
-
console.log('═'.repeat(70) + '\n');
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* 保存报告
|
|
526
|
-
*/
|
|
527
|
-
async saveReport() {
|
|
528
|
-
try {
|
|
529
|
-
await fs.writeJson(this.reportFile, this.result.toJSON(), { spaces: 2 });
|
|
530
|
-
} catch (error) {
|
|
531
|
-
logger.warn(`Failed to save report to ${this.reportFile}: ${error.message}`);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
module.exports = { BatchReplayOrchestrator };
|