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 +16 -10
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +284 -4
- package/dist/server/index.js.map +1 -1
- package/dist/tracker.d.ts +54 -0
- package/dist/tracker.d.ts.map +1 -0
- package/dist/tracker.js +394 -0
- package/dist/tracker.js.map +1 -0
- package/dist/ui/dashboard.html +855 -89
- package/package.json +1 -1
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
|
-
##
|
|
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;
|
|
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"}
|
package/dist/server/index.js
CHANGED
|
@@ -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
|
|
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) {
|