viberadar 0.3.222 → 0.3.224

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 CHANGED
@@ -56,16 +56,22 @@ npx jest --coverage
56
56
  npx playwright test
57
57
  ```
58
58
 
59
- ## Logging standard
60
-
61
- Repository includes an observability baseline for structured logs:
62
-
63
- - Standard document: `docs/observability/logging-standard.md`
64
- - Error code dictionary: `config/logging-error-codes.json`
65
- - CI/lint command: `npm run lint:logs`
66
- - Lint self-check tests: `npm run test:lint-logs`
67
-
68
- ## Tech
59
+ ## Logging standard
60
+
61
+ Repository includes an observability baseline for structured logs:
62
+
63
+ - Standard document: `docs/observability/logging-standard.md`
64
+ - Error code dictionary: `config/logging-error-codes.json`
65
+ - CI/lint command: `npm run lint:logs`
66
+ - Lint self-check tests: `npm run test:lint-logs`
67
+
68
+ ## Task tracker standard
69
+
70
+ Task Tracker imports project tasks from `viberadar.tasks.json`.
71
+ Authoring rules for human reviewers and Codex skills are documented in
72
+ `docs/task-tracker/task-authoring-standard.md`.
73
+
74
+ ## Tech
69
75
 
70
76
  - CLI: TypeScript + Node.js
71
77
  - Dashboard: vanilla HTML/JS (zero dependencies in browser)
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAO7B,OAAO,EAAE,UAAU,EAA4H,MAAM,YAAY,CAAC;AAOlK,UAAU,aAAa;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;CACrB;AA0vED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAk4G1G"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAO7B,OAAO,EAAE,UAAU,EAA4H,MAAM,YAAY,CAAC;AAiBlK,UAAU,aAAa;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;CACrB;AA0vED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAypH1G"}
@@ -49,6 +49,7 @@ const docx_1 = require("../docx");
49
49
  const config_1 = require("../probe/config");
50
50
  const runner_1 = require("../probe/runner");
51
51
  const notify_1 = require("../probe/notify");
52
+ const tracker_1 = require("../tracker");
52
53
  const DASHBOARD_HTML = fs.readFileSync(path.join(__dirname, '../ui/dashboard.html'), 'utf-8');
53
54
  // ─── Agent CLI commands ───────────────────────────────────────────────────────
54
55
  const WIN = process.platform === 'win32';
@@ -2335,7 +2336,132 @@ function startServer({ data: initialData, port, projectRoot }) {
2335
2336
  }
2336
2337
  return { passes, fails };
2337
2338
  }
2338
- function normalizeK6Summary(raw, exitCode) {
2339
+ function parseK6ThresholdMs(thresholds, percentile) {
2340
+ if (!thresholds || typeof thresholds !== 'object')
2341
+ return undefined;
2342
+ for (const expression of Object.keys(thresholds)) {
2343
+ const escaped = percentile.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2344
+ const match = expression.match(new RegExp(`p\\(${escaped}\\)\\s*<\\s*(\\d+(?:\\.\\d+)?)`));
2345
+ if (match) {
2346
+ const value = Number(match[1]);
2347
+ if (Number.isFinite(value))
2348
+ return value;
2349
+ }
2350
+ }
2351
+ return undefined;
2352
+ }
2353
+ function normalizeK6EndpointTag(tags) {
2354
+ if (!tags || typeof tags !== 'object')
2355
+ return null;
2356
+ const record = tags;
2357
+ const candidates = [record.endpoint, record.name, record.url];
2358
+ for (const candidate of candidates) {
2359
+ if (typeof candidate !== 'string')
2360
+ continue;
2361
+ const value = candidate.trim();
2362
+ if (!value)
2363
+ continue;
2364
+ return value.replace(/^https?:\/\/[^/]+/i, '').slice(0, 180);
2365
+ }
2366
+ return null;
2367
+ }
2368
+ function percentile(values, p) {
2369
+ if (values.length === 0)
2370
+ return undefined;
2371
+ const sorted = values.slice().sort((a, b) => a - b);
2372
+ const rank = (sorted.length - 1) * p;
2373
+ const lower = Math.floor(rank);
2374
+ const upper = Math.ceil(rank);
2375
+ if (lower === upper)
2376
+ return sorted[lower];
2377
+ const weight = rank - lower;
2378
+ return sorted[lower] * (1 - weight) + sorted[upper] * weight;
2379
+ }
2380
+ function parseK6EndpointMetrics(metricsPath) {
2381
+ if (!fs.existsSync(metricsPath))
2382
+ return [];
2383
+ const acc = new Map();
2384
+ const getBucket = (endpoint) => {
2385
+ let bucket = acc.get(endpoint);
2386
+ if (!bucket) {
2387
+ bucket = { requests: 0, failures: 0, durations: [], durationSum: 0 };
2388
+ acc.set(endpoint, bucket);
2389
+ }
2390
+ return bucket;
2391
+ };
2392
+ const fd = fs.openSync(metricsPath, 'r');
2393
+ const buffer = Buffer.alloc(1024 * 1024);
2394
+ let remainder = '';
2395
+ try {
2396
+ while (true) {
2397
+ const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, null);
2398
+ if (bytesRead <= 0)
2399
+ break;
2400
+ const text = remainder + buffer.subarray(0, bytesRead).toString('utf-8');
2401
+ const lines = text.split(/\r?\n/);
2402
+ remainder = lines.pop() || '';
2403
+ for (const line of lines)
2404
+ processK6MetricLine(line);
2405
+ }
2406
+ if (remainder.trim())
2407
+ processK6MetricLine(remainder);
2408
+ }
2409
+ finally {
2410
+ fs.closeSync(fd);
2411
+ }
2412
+ function processK6MetricLine(line) {
2413
+ if (!line.trim())
2414
+ return;
2415
+ let point;
2416
+ try {
2417
+ point = JSON.parse(line);
2418
+ }
2419
+ catch {
2420
+ return;
2421
+ }
2422
+ if (point?.type !== 'Point')
2423
+ return;
2424
+ const data = point.data;
2425
+ if (!data || typeof data.value !== 'number')
2426
+ return;
2427
+ const endpoint = normalizeK6EndpointTag(data.tags);
2428
+ if (!endpoint)
2429
+ return;
2430
+ const bucket = getBucket(endpoint);
2431
+ if (point.metric === 'http_reqs') {
2432
+ bucket.requests += data.value;
2433
+ }
2434
+ else if (point.metric === 'http_req_failed') {
2435
+ if (data.value > 0)
2436
+ bucket.failures += data.value;
2437
+ }
2438
+ else if (point.metric === 'http_req_duration') {
2439
+ bucket.durations.push(data.value);
2440
+ bucket.durationSum += data.value;
2441
+ }
2442
+ }
2443
+ return Array.from(acc.entries())
2444
+ .map(([endpoint, bucket]) => {
2445
+ const requests = Math.round(bucket.requests || bucket.durations.length);
2446
+ const failures = Math.round(bucket.failures);
2447
+ const avgDuration = bucket.durations.length > 0 ? bucket.durationSum / bucket.durations.length : undefined;
2448
+ return {
2449
+ endpoint,
2450
+ requests,
2451
+ failures,
2452
+ errorPct: requests > 0 ? (failures / requests) * 100 : undefined,
2453
+ avgDuration,
2454
+ p90Duration: percentile(bucket.durations, 0.90),
2455
+ p95Duration: percentile(bucket.durations, 0.95),
2456
+ p99Duration: percentile(bucket.durations, 0.99),
2457
+ maxDuration: bucket.durations.length ? bucket.durations.reduce((max, value) => Math.max(max, value), 0) : undefined,
2458
+ };
2459
+ })
2460
+ .filter((endpoint) => endpoint.requests > 0 || endpoint.avgDuration != null)
2461
+ .sort((a, b) => (b.p95Duration || 0) - (a.p95Duration || 0) || b.requests - a.requests)
2462
+ .slice(0, 100);
2463
+ }
2464
+ function normalizeK6Summary(raw, exitCode, metricsPath) {
2339
2465
  const metrics = raw?.metrics || {};
2340
2466
  const duration = metrics.http_req_duration?.values || {};
2341
2467
  const reqs = metrics.http_reqs?.values || {};
@@ -2351,6 +2477,9 @@ function startServer({ data: initialData, port, projectRoot }) {
2351
2477
  thresholdsPassed++;
2352
2478
  }
2353
2479
  }
2480
+ const endpoints = metricsPath ? parseK6EndpointMetrics(metricsPath) : [];
2481
+ const p95ThresholdMs = parseK6ThresholdMs(metrics.http_req_duration?.thresholds, '95');
2482
+ const p99ThresholdMs = parseK6ThresholdMs(metrics.http_req_duration?.thresholds, '99');
2354
2483
  return {
2355
2484
  totalRequests: typeof reqs.count === 'number' ? reqs.count : undefined,
2356
2485
  rps: typeof reqs.rate === 'number' ? reqs.rate : undefined,
@@ -2364,14 +2493,17 @@ function startServer({ data: initialData, port, projectRoot }) {
2364
2493
  checksFailed: checks.fails,
2365
2494
  thresholdsPassed,
2366
2495
  thresholdsFailed,
2496
+ p95ThresholdMs,
2497
+ p99ThresholdMs,
2498
+ endpoints,
2367
2499
  exitCode,
2368
2500
  };
2369
2501
  }
2370
- function readK6Summary(summaryPath, exitCode) {
2502
+ function readK6Summary(summaryPath, exitCode, metricsPath) {
2371
2503
  try {
2372
2504
  if (!fs.existsSync(summaryPath))
2373
2505
  return { exitCode };
2374
- return normalizeK6Summary(JSON.parse(fs.readFileSync(summaryPath, 'utf-8')), exitCode);
2506
+ return normalizeK6Summary(JSON.parse(fs.readFileSync(summaryPath, 'utf-8')), exitCode, metricsPath);
2375
2507
  }
2376
2508
  catch {
2377
2509
  return { exitCode };
@@ -2437,6 +2569,44 @@ function startServer({ data: initialData, port, projectRoot }) {
2437
2569
  catch { }
2438
2570
  }
2439
2571
  loadLastRunIntoState();
2572
+ function sendJson(res, statusCode, payload) {
2573
+ res.writeHead(statusCode, jsonH);
2574
+ res.end(JSON.stringify(payload));
2575
+ }
2576
+ function readJsonBody(req, maxBytes = 1024 * 1024) {
2577
+ return new Promise((resolve, reject) => {
2578
+ let body = '';
2579
+ req.on('data', (chunk) => {
2580
+ body += chunk.toString('utf-8');
2581
+ if (Buffer.byteLength(body, 'utf-8') > maxBytes) {
2582
+ reject(new Error('Request body is too large'));
2583
+ req.destroy();
2584
+ }
2585
+ });
2586
+ req.on('end', () => {
2587
+ if (!body.trim()) {
2588
+ resolve({});
2589
+ return;
2590
+ }
2591
+ try {
2592
+ resolve(JSON.parse(body));
2593
+ }
2594
+ catch (err) {
2595
+ reject(new tracker_1.TrackerValidationError([{ field: 'body', message: err.message || 'invalid JSON' }]));
2596
+ }
2597
+ });
2598
+ req.on('error', reject);
2599
+ });
2600
+ }
2601
+ function sendTrackerError(res, err) {
2602
+ if (err instanceof tracker_1.TrackerValidationError) {
2603
+ const notFound = err.issues.some((issue) => issue.field === 'id' && issue.message.includes('не найдена'));
2604
+ sendJson(res, notFound ? 404 : 400, { ok: false, error: err.message, issues: err.issues });
2605
+ return;
2606
+ }
2607
+ const message = err instanceof Error ? err.message : String(err);
2608
+ sendJson(res, 500, { ok: false, error: message });
2609
+ }
2440
2610
  // ── SSE clients ────────────────────────────────────────────────────────────
2441
2611
  const sseClients = new Set();
2442
2612
  function broadcast(event, payload = {}) {
@@ -2718,10 +2888,27 @@ function startServer({ data: initialData, port, projectRoot }) {
2718
2888
  /** Actually spawn the agent process for a queue item */
2719
2889
  async function executeAgentItem(item) {
2720
2890
  const { runId, task, featureKey, filePath, selectedFilePaths, title, agent, savedErrors, savedFailedFiles, savedTestType, autoFixAttempt = 0, autoFixSourceTask, } = item;
2891
+ const trackerTaskId = typeof item.meta?.taskId === 'string' ? item.meta.taskId : null;
2721
2892
  const normalizeRelPath = (p) => p.replace(/\\/g, '/');
2722
2893
  const emitOutput = (line, isError = false, isDim = false) => {
2723
2894
  broadcast('agent-output', { runId, line, isError, isDim });
2724
2895
  };
2896
+ const updateTrackedTaskFromRun = (phase) => {
2897
+ if (!trackerTaskId)
2898
+ return;
2899
+ try {
2900
+ const patch = phase === 'completed'
2901
+ ? { status: 'done', lastRunId: runId }
2902
+ : phase === 'failed'
2903
+ ? { status: 'review', lastRunId: runId }
2904
+ : { lastRunId: runId };
2905
+ (0, tracker_1.updateTrackerTask)(projectRoot, trackerTaskId, patch);
2906
+ broadcast('tasks-updated', { filePath: tracker_1.TRACKER_FILE_NAME, taskId: trackerTaskId, runId, phase });
2907
+ }
2908
+ catch (err) {
2909
+ emitOutput(`⚠️ Не удалось обновить задачу трекера ${trackerTaskId}: ${err.message || err}`, true, true);
2910
+ }
2911
+ };
2725
2912
  const targetSourcePaths = (() => {
2726
2913
  if (task === 'write-tests-file' && filePath) {
2727
2914
  return [normalizeRelPath(filePath)];
@@ -2744,6 +2931,7 @@ function startServer({ data: initialData, port, projectRoot }) {
2744
2931
  });
2745
2932
  function failBeforeStart(message) {
2746
2933
  setRunPhase(runId, 'failed', { error: message, targetSourcePaths });
2934
+ updateTrackedTaskFromRun('failed');
2747
2935
  broadcast('agent-error', { runId, message });
2748
2936
  agentRunning = false;
2749
2937
  processNextInQueue();
@@ -3441,12 +3629,14 @@ function startServer({ data: initialData, port, projectRoot }) {
3441
3629
  validationStats: finalValidationStats,
3442
3630
  error: finalError,
3443
3631
  });
3632
+ updateTrackedTaskFromRun(finalPhase);
3444
3633
  processNextInQueue(true);
3445
3634
  }
3446
3635
  else if (code === 255) {
3447
3636
  process.stdout.write(` ❌ Agent auth error (exit code 255)\n`);
3448
3637
  const message = `${agent === 'claude' ? 'Claude Code' : 'Codex'} не авторизован. Нажми 🔑 Перелогиниться в меню агента.`;
3449
3638
  setRunPhase(runId, 'failed', { targetSourcePaths, error: message });
3639
+ updateTrackedTaskFromRun('failed');
3450
3640
  broadcast('agent-error', {
3451
3641
  runId,
3452
3642
  message,
@@ -3459,6 +3649,7 @@ function startServer({ data: initialData, port, projectRoot }) {
3459
3649
  process.stdout.write(` ❌ Agent failed (exit code ${code})\n`);
3460
3650
  const message = `Агент завершился с кодом ${code}`;
3461
3651
  setRunPhase(runId, 'failed', { targetSourcePaths, error: message });
3652
+ updateTrackedTaskFromRun('failed');
3462
3653
  broadcast('agent-error', { runId, message });
3463
3654
  if (queueBlockSignal === 403 || queueBlockSignal === 429) {
3464
3655
  stopQueuedTasks(`пойман ${queueBlockSignal} от ${agent === 'claude' ? 'Claude Code' : 'Codex'}`);
@@ -3480,6 +3671,7 @@ function startServer({ data: initialData, port, projectRoot }) {
3480
3671
  : `Не удалось запустить ${agent}: ${err.message}`;
3481
3672
  process.stdout.write(' ❌ Agent spawn error: ' + err.message + '\n');
3482
3673
  setRunPhase(runId, 'failed', { targetSourcePaths, error: msg });
3674
+ updateTrackedTaskFromRun('failed');
3483
3675
  broadcast('agent-error', { runId, message: msg, notInstalled: isNotFound, agent });
3484
3676
  processNextInQueue(true);
3485
3677
  });
@@ -3639,6 +3831,9 @@ function startServer({ data: initialData, port, projectRoot }) {
3639
3831
  }
3640
3832
  title = `${agentLabel} — пайплайны для "${feat.label}"`;
3641
3833
  }
3834
+ else if (task === 'custom-prompt') {
3835
+ title = `${agentLabel} — ${meta?.title || 'задача трекера'}`;
3836
+ }
3642
3837
  else if (task === 'classify-orphan-tests') {
3643
3838
  const orphanCount = getOrphanTests(currentData.modules).noFeature.length;
3644
3839
  if (orphanCount === 0) {
@@ -3730,6 +3925,14 @@ function startServer({ data: initialData, port, projectRoot }) {
3730
3925
  .on('add', f => scheduleRescan(path.join(projectRoot, f)))
3731
3926
  .on('change', f => scheduleRescan(path.join(projectRoot, f)))
3732
3927
  .on('unlink', f => scheduleRescan(path.join(projectRoot, f)));
3928
+ chokidar_1.default.watch([tracker_1.TRACKER_FILE_NAME], {
3929
+ cwd: projectRoot,
3930
+ ignoreInitial: true,
3931
+ persistent: true,
3932
+ })
3933
+ .on('add', () => broadcast('tasks-updated', { filePath: tracker_1.TRACKER_FILE_NAME }))
3934
+ .on('change', () => broadcast('tasks-updated', { filePath: tracker_1.TRACKER_FILE_NAME }))
3935
+ .on('unlink', () => broadcast('tasks-updated', { filePath: tracker_1.TRACKER_FILE_NAME, deleted: true }));
3733
3936
  // ── HTTP server ────────────────────────────────────────────────────────────
3734
3937
  const server = http.createServer((req, res) => {
3735
3938
  const rawUrl = req.url ?? '/';
@@ -3906,6 +4109,83 @@ function startServer({ data: initialData, port, projectRoot }) {
3906
4109
  res.end(JSON.stringify(buildAgentStateSnapshot()));
3907
4110
  return;
3908
4111
  }
4112
+ if (url === '/api/tasks' && req.method === 'GET') {
4113
+ try {
4114
+ sendJson(res, 200, { ...(0, tracker_1.readTrackerFile)(projectRoot), filePath: tracker_1.TRACKER_FILE_NAME });
4115
+ }
4116
+ catch (err) {
4117
+ sendTrackerError(res, err);
4118
+ }
4119
+ return;
4120
+ }
4121
+ if (url === '/api/tasks' && req.method === 'POST') {
4122
+ readJsonBody(req).then((payload) => {
4123
+ const task = (0, tracker_1.createTrackerTask)(projectRoot, payload?.task ?? payload);
4124
+ broadcast('tasks-updated', { filePath: tracker_1.TRACKER_FILE_NAME, taskId: task.id });
4125
+ sendJson(res, 201, { ok: true, task, file: (0, tracker_1.readTrackerFile)(projectRoot) });
4126
+ }).catch((err) => sendTrackerError(res, err));
4127
+ return;
4128
+ }
4129
+ if (url === '/api/tasks/import' && req.method === 'POST') {
4130
+ readJsonBody(req, 2 * 1024 * 1024).then((payload) => {
4131
+ const result = (0, tracker_1.importTrackerTasks)(projectRoot, payload);
4132
+ broadcast('tasks-updated', { filePath: tracker_1.TRACKER_FILE_NAME, imported: result.imported });
4133
+ sendJson(res, 200, { ok: true, imported: result.imported, file: result.file });
4134
+ }).catch((err) => sendTrackerError(res, err));
4135
+ return;
4136
+ }
4137
+ const taskRunMatch = url.match(/^\/api\/tasks\/([^/]+)\/run$/);
4138
+ if (taskRunMatch && req.method === 'POST') {
4139
+ const taskId = decodeURIComponent(taskRunMatch[1]);
4140
+ try {
4141
+ const file = (0, tracker_1.readTrackerFile)(projectRoot);
4142
+ const task = file.tasks.find((item) => item.id === taskId);
4143
+ if (!task) {
4144
+ sendJson(res, 404, { ok: false, error: `Задача не найдена: ${taskId}` });
4145
+ return;
4146
+ }
4147
+ const prompt = (0, tracker_1.buildTrackerTaskPrompt)(task);
4148
+ const runId = runAgent('custom-prompt', undefined, undefined, undefined, {
4149
+ prompt,
4150
+ title: `задача "${task.title}"`,
4151
+ taskId: task.id,
4152
+ });
4153
+ if (!runId) {
4154
+ sendJson(res, 409, { ok: false, error: 'Не удалось поставить задачу в очередь агента' });
4155
+ return;
4156
+ }
4157
+ const updatedTask = (0, tracker_1.updateTrackerTask)(projectRoot, task.id, { status: 'in-progress', lastRunId: runId });
4158
+ broadcast('tasks-updated', { filePath: tracker_1.TRACKER_FILE_NAME, taskId: task.id, runId });
4159
+ sendJson(res, 200, { ok: true, runId, task: updatedTask });
4160
+ }
4161
+ catch (err) {
4162
+ sendTrackerError(res, err);
4163
+ }
4164
+ return;
4165
+ }
4166
+ const taskArchiveMatch = url.match(/^\/api\/tasks\/([^/]+)\/archive$/);
4167
+ if (taskArchiveMatch && req.method === 'POST') {
4168
+ const taskId = decodeURIComponent(taskArchiveMatch[1]);
4169
+ try {
4170
+ const task = (0, tracker_1.archiveTrackerTask)(projectRoot, taskId);
4171
+ broadcast('tasks-updated', { filePath: tracker_1.TRACKER_FILE_NAME, taskId });
4172
+ sendJson(res, 200, { ok: true, task });
4173
+ }
4174
+ catch (err) {
4175
+ sendTrackerError(res, err);
4176
+ }
4177
+ return;
4178
+ }
4179
+ const taskPatchMatch = url.match(/^\/api\/tasks\/([^/]+)$/);
4180
+ if (taskPatchMatch && req.method === 'PATCH') {
4181
+ const taskId = decodeURIComponent(taskPatchMatch[1]);
4182
+ readJsonBody(req).then((payload) => {
4183
+ const task = (0, tracker_1.updateTrackerTask)(projectRoot, taskId, payload);
4184
+ broadcast('tasks-updated', { filePath: tracker_1.TRACKER_FILE_NAME, taskId });
4185
+ sendJson(res, 200, { ok: true, task, file: (0, tracker_1.readTrackerFile)(projectRoot) });
4186
+ }).catch((err) => sendTrackerError(res, err));
4187
+ return;
4188
+ }
3909
4189
  const queueCancelMatch = url.match(/^\/api\/queue\/([^/]+)\/cancel$/);
3910
4190
  if (queueCancelMatch && req.method === 'POST') {
3911
4191
  const runId = decodeURIComponent(queueCancelMatch[1]);
@@ -5486,7 +5766,7 @@ a{color:var(--blue)}
5486
5766
  loadState.status = code === 0 ? 'done' : 'error';
5487
5767
  }
5488
5768
  loadState.endTime = Date.now();
5489
- loadState.summary = readK6Summary(summaryPath, code);
5769
+ loadState.summary = readK6Summary(summaryPath, code, jsonOutPath);
5490
5770
  if (loadState.summary?.totalRequests != null)
5491
5771
  loadState.totalRequests = loadState.summary.totalRequests;
5492
5772
  if (loadState.summary?.errorPct != null && loadState.summary.totalRequests != null) {