@jrpool/kilotest 38.0.0 → 39.0.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.
- package/.markdownlint-cli2.jsonc +12 -0
- package/.vscode/settings.json +3 -0
- package/CLAUDE.md +5 -0
- package/README.md +6 -4
- package/ai0BalanceForm/index.html +1 -1
- package/alerts.js +3 -1
- package/api/getReport.js +66 -0
- package/api/listDiagnoses.js +157 -0
- package/api/listIssues.js +225 -0
- package/api/listReports.js +87 -0
- package/api/listViolators.js +164 -0
- package/api/requestFeature.js +56 -0
- package/api/requestRetest.js +102 -0
- package/api/requestTest.js +106 -0
- package/api/routes.js +97 -0
- package/api/schemas.js +297 -0
- package/api/testSuite.js +294 -0
- package/api/util.js +149 -0
- package/diagnoses/index.js +2 -2
- package/docs/{AI-TOOL.md → AI-TOOLS.md} +54 -26
- package/docs/SERVICE.md +4 -1
- package/docs/decisions/001-api-design.md +59 -49
- package/eslint.config.mjs +13 -3
- package/index.html +1 -1
- package/index.js +201 -188
- package/issues/index.js +13 -21
- package/jsconfig.json +8 -0
- package/llms-full.txt +2 -2
- package/llms.txt +16 -11
- package/mcp.js +129 -65
- package/openapi.yaml +1234 -572
- package/package.json +18 -11
- package/reannotate/index.js +26 -15
- package/reannotateForm/index.js +15 -13
- package/redocly.yaml +4 -0
- package/reportHideForm/index.html +1 -1
- package/reportHideForm/index.js +24 -38
- package/reportIssue/index.js +3 -3
- package/reportIssues/index.js +128 -1
- package/reportUnhideForm/index.js +36 -44
- package/reportsExpungeForm/index.js +22 -16
- package/reportsPruneForm/index.js +22 -12
- package/reportsRewindForm/index.js +17 -9
- package/retestRec/index.js +8 -4
- package/retestRecForm/index.js +18 -6
- package/scripts/generate-openapi.js +83 -0
- package/targets/index.js +18 -13
- package/testOrder/index.js +11 -8
- package/tutorial/index.html +12 -12
- package/util.js +580 -539
- package/reportIssue/api.js +0 -183
- package/reportIssues/util.js +0 -138
- package/reportList/api.js +0 -83
- package/reportSummary/api.js +0 -181
- package/researchAgent.js +0 -206
- package/target/api.js +0 -126
- package/testRecForm/api.js +0 -55
package/index.js
CHANGED
|
@@ -10,9 +10,9 @@ require('dotenv').config({quiet: true});
|
|
|
10
10
|
// IMPORTS
|
|
11
11
|
const {
|
|
12
12
|
annotateReport,
|
|
13
|
+
createLock,
|
|
13
14
|
getJobNames,
|
|
14
15
|
getJSON,
|
|
15
|
-
getLogPath,
|
|
16
16
|
getObject,
|
|
17
17
|
getPOSTData,
|
|
18
18
|
getReport,
|
|
@@ -22,9 +22,9 @@ const {
|
|
|
22
22
|
isReportAvailable,
|
|
23
23
|
isTimeStamp,
|
|
24
24
|
isJobID,
|
|
25
|
-
isURL,
|
|
26
25
|
jobsPath,
|
|
27
|
-
|
|
26
|
+
makeReportsExtract,
|
|
27
|
+
recsLock,
|
|
28
28
|
reportsPath,
|
|
29
29
|
ruleIDs
|
|
30
30
|
} = require('./util');
|
|
@@ -71,7 +71,8 @@ const failedPath = path.join(jobsPath, 'failed');
|
|
|
71
71
|
const testaroAgent = process.env.TESTARO_AGENT;
|
|
72
72
|
const testaroAgentPW = process.env.TESTARO_AGENT_PW;
|
|
73
73
|
// Values that may require alerts.
|
|
74
|
-
const balancePath = path.join(__dirname, '
|
|
74
|
+
const balancePath = path.join(__dirname, 'ai0Balance.json');
|
|
75
|
+
const jobLock = createLock();
|
|
75
76
|
const WAVE_THRESHOLD = Number(process.env.WAVE_BALANCE_THRESHOLD);
|
|
76
77
|
const AI_SERVICE0_THRESHOLD = Number(process.env.AI_SERVICE0_BALANCE_THRESHOLD);
|
|
77
78
|
const AI_MODEL0_INPUT_PRICE = Number(process.env.AI_MODEL0_INPUT_PRICE);
|
|
@@ -83,7 +84,7 @@ const AI_MODEL0_OUTPUT_PRICE = Number(process.env.AI_MODEL0_OUTPUT_PRICE);
|
|
|
83
84
|
const serveError = async (error, response, isHumanUser = true) => {
|
|
84
85
|
const errorLines = Object.entries(error).map(pair => `${pair[0]}: ${pair[1]}`);
|
|
85
86
|
console.log(errorLines.join('\n') || 'ERROR');
|
|
86
|
-
if (!
|
|
87
|
+
if (!response.writableEnded) {
|
|
87
88
|
response.statusCode = 400;
|
|
88
89
|
// If the request is from a human user:
|
|
89
90
|
if (isHumanUser) {
|
|
@@ -152,6 +153,7 @@ const checkBalancesForAlerts = async report => {
|
|
|
152
153
|
if (typeof newBalance === 'number') {
|
|
153
154
|
// Update the recorded balance.
|
|
154
155
|
await fs.writeFile(balancePath, getJSON({balance: newBalance}));
|
|
156
|
+
console.log(`Estimated new AI Service 0 balance: $${newBalance.toFixed(2)}`);
|
|
155
157
|
// If it is nearing exhaustion:
|
|
156
158
|
if (newBalance < AI_SERVICE0_THRESHOLD) {
|
|
157
159
|
// Alert a manager.
|
|
@@ -186,6 +188,71 @@ const getAbuseError = (request, reason) => {
|
|
|
186
188
|
time: new Date().toISOString()
|
|
187
189
|
};
|
|
188
190
|
};
|
|
191
|
+
// Processes a job request from a Testaro agent.
|
|
192
|
+
const processJobRequest = async (request, response, agentID) => jobLock(async () => {
|
|
193
|
+
let clean = true;
|
|
194
|
+
const messageStart = `Testaro agent ${agentID} requested a job, `;
|
|
195
|
+
const jobNames = await getJobNames();
|
|
196
|
+
const claimedJobNames = jobNames.claimed;
|
|
197
|
+
// For each claimed job:
|
|
198
|
+
for (const jobName of claimedJobNames) {
|
|
199
|
+
const job = await getObject(path.join(jobsPath, 'claimed', jobName));
|
|
200
|
+
const {id, sources} = job;
|
|
201
|
+
const {agent} = sources;
|
|
202
|
+
// If its assignee is the agent:
|
|
203
|
+
if (agent === agentID) {
|
|
204
|
+
const messageEnd = `but has not completed job ${id}`;
|
|
205
|
+
// Report this.
|
|
206
|
+
await serveError({message: `${messageStart}${messageEnd}`}, response, false);
|
|
207
|
+
// Reclassify the job as failed.
|
|
208
|
+
await fs.rename(
|
|
209
|
+
path.join(claimedPath, jobName), path.join(failedPath, jobName)
|
|
210
|
+
);
|
|
211
|
+
clean = false;
|
|
212
|
+
// Stop checking claimed jobs.
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// If no aborted-job error was found for the agent:
|
|
217
|
+
if (clean) {
|
|
218
|
+
const queuedJobNames = jobNames.queue;
|
|
219
|
+
// If any jobs are queued:
|
|
220
|
+
if (queuedJobNames.length) {
|
|
221
|
+
const oldestJobName = queuedJobNames[0];
|
|
222
|
+
// Get the first one.
|
|
223
|
+
const firstJob = await getObject(path.join(queuePath, oldestJobName));
|
|
224
|
+
// Add the agent ID to the job.
|
|
225
|
+
firstJob.sources.agent = agentID;
|
|
226
|
+
console.log(
|
|
227
|
+
`Job ${firstJob.id} (${firstJob.target.what}) is being sent to the agent.`
|
|
228
|
+
);
|
|
229
|
+
// Assign the job to the agent.
|
|
230
|
+
response.writeHead(200, {
|
|
231
|
+
'content-type': 'application/json; charset=utf-8'
|
|
232
|
+
});
|
|
233
|
+
response.end(JSON.stringify(firstJob));
|
|
234
|
+
const messageEnd
|
|
235
|
+
= `and job ${firstJob.id} (${firstJob.target.what}) was assigned to the agent`;
|
|
236
|
+
console.log(`${messageStart}${messageEnd}`);
|
|
237
|
+
// Save the job in the claimed-jobs directory.
|
|
238
|
+
await fs.writeFile(
|
|
239
|
+
path.join(claimedPath, oldestJobName), getJSON(firstJob)
|
|
240
|
+
);
|
|
241
|
+
// Delete it from the queue.
|
|
242
|
+
await fs.unlink(path.join(queuePath, oldestJobName));
|
|
243
|
+
}
|
|
244
|
+
// Otherwise, i.e. if no jobs are queued:
|
|
245
|
+
else {
|
|
246
|
+
response.writeHead(200, {
|
|
247
|
+
'content-type': 'application/json; charset=utf-8'
|
|
248
|
+
});
|
|
249
|
+
// Send a no-jobs response to the agent.
|
|
250
|
+
response.end(JSON.stringify({}));
|
|
251
|
+
const messageEnd = 'but no job was in the queue';
|
|
252
|
+
console.log(`${messageStart}${messageEnd}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
});
|
|
189
256
|
// Handles a request.
|
|
190
257
|
const requestHandler = async (request, response) => {
|
|
191
258
|
// Sets response headers.
|
|
@@ -319,7 +386,7 @@ const requestHandler = async (request, response) => {
|
|
|
319
386
|
const topic = pageName.slice(0, -5);
|
|
320
387
|
// If the page can be generated:
|
|
321
388
|
if (answer[topic]) {
|
|
322
|
-
setHeaders('text/html',
|
|
389
|
+
setHeaders('text/html', pathname, 'ultra');
|
|
323
390
|
// Get the answer data.
|
|
324
391
|
const answerData = await answer[topic](pathTail, search);
|
|
325
392
|
// If they are valid:
|
|
@@ -341,37 +408,56 @@ const requestHandler = async (request, response) => {
|
|
|
341
408
|
}
|
|
342
409
|
// Otherwise, if it is for an API service:
|
|
343
410
|
else if (pageName === 'api') {
|
|
344
|
-
const [service,
|
|
345
|
-
// If the service
|
|
346
|
-
if (service === '
|
|
347
|
-
// Get the response
|
|
348
|
-
const
|
|
349
|
-
|
|
411
|
+
const [service, ...specs] = pathTail.split('/');
|
|
412
|
+
// If the service lists the available reports:
|
|
413
|
+
if (service === 'listReports') {
|
|
414
|
+
// Get the response body.
|
|
415
|
+
const responseBody = await require(path.join(__dirname, 'api', 'listReports'))
|
|
416
|
+
.response(specs);
|
|
417
|
+
// Send it.
|
|
350
418
|
setHeaders('application/json', null, 'ultra');
|
|
351
|
-
response.end(JSON.stringify(
|
|
419
|
+
response.end(JSON.stringify(responseBody));
|
|
352
420
|
}
|
|
353
|
-
// Otherwise, if the service
|
|
354
|
-
else if (service === '
|
|
355
|
-
// Get the response
|
|
356
|
-
const
|
|
421
|
+
// Otherwise, if the service lists the issues in a report:
|
|
422
|
+
else if (service === 'listIssues') {
|
|
423
|
+
// Get the response body.
|
|
424
|
+
const responseBody = await require(path.join(__dirname, 'api', 'listIssues'))
|
|
357
425
|
.response(specs);
|
|
358
|
-
// Send
|
|
426
|
+
// Send it.
|
|
359
427
|
setHeaders('application/json', null, 'high');
|
|
360
|
-
response.end(JSON.stringify(
|
|
428
|
+
response.end(JSON.stringify(responseBody));
|
|
361
429
|
}
|
|
362
|
-
// Otherwise, if the service
|
|
363
|
-
else if (service === '
|
|
364
|
-
// Get the response
|
|
365
|
-
const
|
|
430
|
+
// Otherwise, if the service lists the violators of an issue in a report:
|
|
431
|
+
else if (service === 'listViolators') {
|
|
432
|
+
// Get the response body.
|
|
433
|
+
const responseBody = await require(path.join(__dirname, 'api', 'listViolators'))
|
|
366
434
|
.response(specs);
|
|
367
|
-
// Send
|
|
435
|
+
// Send it.
|
|
368
436
|
setHeaders('application/json', null, 'high');
|
|
369
|
-
response.end(JSON.stringify(
|
|
437
|
+
response.end(JSON.stringify(responseBody));
|
|
438
|
+
}
|
|
439
|
+
// Otherwise, if the service lists the diagnoses of a violation of an issue in a report:
|
|
440
|
+
else if (service === 'listDiagnoses') {
|
|
441
|
+
// Get the response body.
|
|
442
|
+
const responseBody = await require(path.join(__dirname, 'api', 'listDiagnoses'))
|
|
443
|
+
.response(specs);
|
|
444
|
+
// Send it.
|
|
445
|
+
setHeaders('application/json', null, 'high');
|
|
446
|
+
response.end(JSON.stringify(responseBody));
|
|
447
|
+
}
|
|
448
|
+
// Otherwise, if the service serves a report:
|
|
449
|
+
else if (service === 'getReport') {
|
|
450
|
+
// Get the response body.
|
|
451
|
+
const responseBody = await require(path.join(__dirname, 'api', 'getReport'))
|
|
452
|
+
.response(specs);
|
|
453
|
+
// Send it.
|
|
454
|
+
setHeaders('application/json', null, 'low');
|
|
455
|
+
response.end(JSON.stringify(responseBody));
|
|
370
456
|
}
|
|
371
457
|
// Otherwise, i.e. if the service is invalid:
|
|
372
458
|
else {
|
|
373
459
|
// Report this.
|
|
374
|
-
await serveError({message: '
|
|
460
|
+
await serveError({message: 'Invalid service request'}, response, false);
|
|
375
461
|
}
|
|
376
462
|
}
|
|
377
463
|
// Otherwise, if it is for a tutorial image:
|
|
@@ -421,7 +507,7 @@ const requestHandler = async (request, response) => {
|
|
|
421
507
|
else {
|
|
422
508
|
// Report the error.
|
|
423
509
|
await serveError(
|
|
424
|
-
{message: `ERROR: Invalid GET request (${pathname}
|
|
510
|
+
{message: `ERROR: Invalid GET request (${pathname})`}, response, true
|
|
425
511
|
);
|
|
426
512
|
}
|
|
427
513
|
}
|
|
@@ -435,36 +521,8 @@ const requestHandler = async (request, response) => {
|
|
|
435
521
|
else {
|
|
436
522
|
// Get the data from the request body.
|
|
437
523
|
const postData = await getPOSTData(request);
|
|
438
|
-
// If the request is a
|
|
439
|
-
if (pageName === '
|
|
440
|
-
const {why} = postData;
|
|
441
|
-
const [timeStamp, jobID] = pathTail.split('/');
|
|
442
|
-
// If the request is valid:
|
|
443
|
-
if (isTimeStamp(timeStamp) && isJobID(jobID) && why) {
|
|
444
|
-
// Serve response headers.
|
|
445
|
-
setHeaders('text/html', `${pathname}${search}`, 'ultra');
|
|
446
|
-
// Get the answer data.
|
|
447
|
-
const answerData = await require(path.join(__dirname, 'retestRec', 'index'))
|
|
448
|
-
.answer(pathTail, why);
|
|
449
|
-
// If they are valid:
|
|
450
|
-
if (answerData.status === 'ok') {
|
|
451
|
-
// Serve the answer page.
|
|
452
|
-
response.end(answerData.answerPage);
|
|
453
|
-
}
|
|
454
|
-
// Otherwise, i.e. if they are invalid:
|
|
455
|
-
else {
|
|
456
|
-
// Report the error.
|
|
457
|
-
await serveError({message: answerData.message}, response, true);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
// Otherwise, i.e. if the request is invalid:
|
|
461
|
-
else {
|
|
462
|
-
// Report the error.
|
|
463
|
-
await serveError({message: 'ERROR: Invalid retest recommendation'}, response, true);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
// Otherwise, if it is a test recommendation:
|
|
467
|
-
else if (pageName === 'testRec.html') {
|
|
524
|
+
// If the request is a test recommendation:
|
|
525
|
+
if (pageName === 'testRec.html') {
|
|
468
526
|
const {what, url, why} = postData;
|
|
469
527
|
// If the request is valid:
|
|
470
528
|
if (what && url.startsWith('https://') && why) {
|
|
@@ -476,7 +534,7 @@ const requestHandler = async (request, response) => {
|
|
|
476
534
|
// Otherwise, i.e. if no report on the page is available:
|
|
477
535
|
else {
|
|
478
536
|
// Serve headers for a response.
|
|
479
|
-
setHeaders('text/html',
|
|
537
|
+
setHeaders('text/html', pathname, 'ultra');
|
|
480
538
|
// Get the answer data.
|
|
481
539
|
const answerData = await require(path.join(__dirname, 'testRec', 'index'))
|
|
482
540
|
.answer(what, url, why);
|
|
@@ -498,6 +556,34 @@ const requestHandler = async (request, response) => {
|
|
|
498
556
|
await serveError({message: 'ERROR: Invalid test recommendation'}, response, true);
|
|
499
557
|
}
|
|
500
558
|
}
|
|
559
|
+
// Otherwise, if it is a retest recommendation:
|
|
560
|
+
else if (pageName === 'retestRec.html') {
|
|
561
|
+
const {why} = postData;
|
|
562
|
+
const [timeStamp, jobID] = pathTail.split('/');
|
|
563
|
+
// If the request is valid:
|
|
564
|
+
if (isTimeStamp(timeStamp) && isJobID(jobID) && why) {
|
|
565
|
+
// Serve response headers.
|
|
566
|
+
setHeaders('text/html', pathname, 'ultra');
|
|
567
|
+
// Get the answer data.
|
|
568
|
+
const answerData = await require(path.join(__dirname, 'retestRec', 'index'))
|
|
569
|
+
.answer(pathTail, why);
|
|
570
|
+
// If they are valid:
|
|
571
|
+
if (answerData.status === 'ok') {
|
|
572
|
+
// Serve the answer page.
|
|
573
|
+
response.end(answerData.answerPage);
|
|
574
|
+
}
|
|
575
|
+
// Otherwise, i.e. if they are invalid:
|
|
576
|
+
else {
|
|
577
|
+
// Report the error.
|
|
578
|
+
await serveError({message: answerData.message}, response, true);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// Otherwise, i.e. if the request is invalid:
|
|
582
|
+
else {
|
|
583
|
+
// Report the error.
|
|
584
|
+
await serveError({message: 'ERROR: Invalid retest recommendation'}, response, true);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
501
587
|
// Otherwise, if it is an action on a test or retest recommendation:
|
|
502
588
|
else if (pageName === 'recAction.html') {
|
|
503
589
|
const {target, authCode} = postData;
|
|
@@ -509,8 +595,8 @@ const requestHandler = async (request, response) => {
|
|
|
509
595
|
// If the request is an approval:
|
|
510
596
|
if (what) {
|
|
511
597
|
// Set a location header for a response.
|
|
512
|
-
response.setHeader('content-location',
|
|
513
|
-
//
|
|
598
|
+
response.setHeader('content-location', pathname);
|
|
599
|
+
// Process the approval and get the answer data about the remaining recommendations.
|
|
514
600
|
const answerData = await require(path.join(__dirname, 'testOrder', 'index'))
|
|
515
601
|
.answer(url, what, authCode);
|
|
516
602
|
// If the answer data are valid:
|
|
@@ -526,12 +612,15 @@ const requestHandler = async (request, response) => {
|
|
|
526
612
|
}
|
|
527
613
|
// Otherwise, i.e. if it is a rejection:
|
|
528
614
|
else {
|
|
529
|
-
//
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
615
|
+
// Isolate this revision.
|
|
616
|
+
await recsLock(async () => {
|
|
617
|
+
// Get the recommendations.
|
|
618
|
+
const recs = await getRecs();
|
|
619
|
+
// Delete the rejected URL.
|
|
620
|
+
delete recs[url];
|
|
621
|
+
// Save the revised recommendations.
|
|
622
|
+
await fs.writeFile(path.join(jobsPath, 'recs.json'), getJSON(recs));
|
|
623
|
+
});
|
|
535
624
|
// Set a location header for a response.
|
|
536
625
|
response.setHeader('content-location', '/recActionForm.html');
|
|
537
626
|
// Get the answer data.
|
|
@@ -550,7 +639,7 @@ const requestHandler = async (request, response) => {
|
|
|
550
639
|
else if (pageName === 'reannotate.html') {
|
|
551
640
|
const {authCode} = postData;
|
|
552
641
|
// Set headers for a response.
|
|
553
|
-
setHeaders('text/html',
|
|
642
|
+
setHeaders('text/html', pathname, 'ultra');
|
|
554
643
|
// Get the answer data.
|
|
555
644
|
const answerData = await require(path.join(__dirname, 'reannotate', 'index'))
|
|
556
645
|
.answer(authCode);
|
|
@@ -569,7 +658,7 @@ const requestHandler = async (request, response) => {
|
|
|
569
658
|
else if (pageName === 'wcagRenew.html') {
|
|
570
659
|
const {authCode} = postData;
|
|
571
660
|
// Set headers for a response.
|
|
572
|
-
setHeaders('text/html',
|
|
661
|
+
setHeaders('text/html', pathname, 'low');
|
|
573
662
|
// Get the answer data.
|
|
574
663
|
const answerData = await require(path.join(__dirname, 'wcagRenew', 'index'))
|
|
575
664
|
.answer(authCode);
|
|
@@ -595,66 +684,8 @@ const requestHandler = async (request, response) => {
|
|
|
595
684
|
const service = segments[1];
|
|
596
685
|
// If the service is job assignment:
|
|
597
686
|
if (service === 'job') {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
const jobNames = await getJobNames();
|
|
601
|
-
const claimedJobNames = jobNames.claimed;
|
|
602
|
-
// For each claimed job:
|
|
603
|
-
for (const jobName of claimedJobNames) {
|
|
604
|
-
const job = await getObject(path.join(jobsPath, 'claimed', jobName));
|
|
605
|
-
const {id, sources} = job;
|
|
606
|
-
const {agent} = sources;
|
|
607
|
-
// If its assignee is the agent:
|
|
608
|
-
if (agent === agentID) {
|
|
609
|
-
const messageEnd = `but has not completed job ${id}`;
|
|
610
|
-
// Report this.
|
|
611
|
-
await serveError({message: `${messageStart}${messageEnd}`}, response, false);
|
|
612
|
-
// Reclassify the job as failed.
|
|
613
|
-
await fs.rename(
|
|
614
|
-
path.join(claimedPath, jobName), path.join(failedPath, jobName)
|
|
615
|
-
);
|
|
616
|
-
clean = false;
|
|
617
|
-
// Stop checking claimed jobs.
|
|
618
|
-
break;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
// If no aborted-job error was found for the agent:
|
|
622
|
-
if (clean) {
|
|
623
|
-
const queuedJobNames = jobNames.queue;
|
|
624
|
-
// If any jobs are queued:
|
|
625
|
-
if (queuedJobNames.length) {
|
|
626
|
-
const oldestJobName = queuedJobNames[0];
|
|
627
|
-
// Get the first one.
|
|
628
|
-
const firstJob = await getObject(path.join(queuePath, oldestJobName));
|
|
629
|
-
// Add the agent ID to the job.
|
|
630
|
-
firstJob.sources.agent = agentID;
|
|
631
|
-
console.log(
|
|
632
|
-
`Job ${firstJob.id} (${firstJob.target.what}) is being sent to the agent.`
|
|
633
|
-
);
|
|
634
|
-
// Assign the job to the agent.
|
|
635
|
-
response.writeHead(200, {
|
|
636
|
-
'content-type': 'application/json; charset=utf-8'
|
|
637
|
-
});
|
|
638
|
-
response.end(JSON.stringify(firstJob));
|
|
639
|
-
const messageEnd
|
|
640
|
-
= `and job ${firstJob.id} (${firstJob.target.what}) was assigned to the agent`;
|
|
641
|
-
console.log(`${messageStart}${messageEnd}`);
|
|
642
|
-
// Move the job from the queue to the claimed-jobs directory.
|
|
643
|
-
await fs.rename(
|
|
644
|
-
path.join(queuePath, oldestJobName), path.join(claimedPath, oldestJobName)
|
|
645
|
-
);
|
|
646
|
-
}
|
|
647
|
-
// Otherwise, i.e. if no jobs are queued:
|
|
648
|
-
else {
|
|
649
|
-
response.writeHead(200, {
|
|
650
|
-
'content-type': 'application/json; charset=utf-8'
|
|
651
|
-
});
|
|
652
|
-
// Send a no-jobs response to the agent.
|
|
653
|
-
response.end(JSON.stringify({}));
|
|
654
|
-
const messageEnd = 'but no job was in the queue';
|
|
655
|
-
console.log(`${messageStart}${messageEnd}`);
|
|
656
|
-
}
|
|
657
|
-
}
|
|
687
|
+
// Process the job request as a transaction.
|
|
688
|
+
await processJobRequest(request, response, agentID);
|
|
658
689
|
}
|
|
659
690
|
// Otherwise, if the service is report acquisition:
|
|
660
691
|
else if (service === 'report') {
|
|
@@ -664,91 +695,73 @@ const requestHandler = async (request, response) => {
|
|
|
664
695
|
const [timeStamp, jobID] = id?.split('-') ?? ['', ''];
|
|
665
696
|
// If the request is valid:
|
|
666
697
|
if (id && isTimeStamp(timeStamp) && isJobID(jobID) && what && url) {
|
|
667
|
-
// Acknowledge receipt.
|
|
668
|
-
response.setHeader('content-type', 'application/json; charset=utf-8');
|
|
669
|
-
response.end(JSON.stringify({status: 'ok'}));
|
|
670
|
-
console.log(`Testaro report ${id} was received from Testaro agent ${agentID}`);
|
|
671
698
|
const [timeStamp, jobID] = id.split('-');
|
|
672
699
|
// Save the report.
|
|
673
700
|
await fs.writeFile(getReportPath(timeStamp, jobID), getJSON(report));
|
|
674
|
-
//
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
url
|
|
678
|
-
};
|
|
679
|
-
// Save the log.
|
|
680
|
-
await fs.writeFile(getLogPath(timeStamp, jobID), getJSON(log));
|
|
681
|
-
// Annotate the report and mark it as annotated in the log.
|
|
701
|
+
// Update the data on the available reports.
|
|
702
|
+
await makeReportsExtract();
|
|
703
|
+
// Annotate the report.
|
|
682
704
|
await annotateReport(ruleIDs, timeStamp, jobID);
|
|
683
|
-
console.log(`Testaro report ${id} was annotated, saved, and
|
|
705
|
+
console.log(`Testaro report ${id} was annotated, saved, and indexed`);
|
|
684
706
|
// Check the monetary balances and send alerts if nearing exhaustion.
|
|
685
707
|
await checkBalancesForAlerts(report);
|
|
686
708
|
// Delete the job.
|
|
687
709
|
await fs.unlink(path.join(claimedPath, `${id}.json`));
|
|
688
710
|
console.log(`Completed job ${id} deleted`);
|
|
711
|
+
// Acknowledge receipt.
|
|
712
|
+
response.setHeader('content-type', 'application/json; charset=utf-8');
|
|
713
|
+
response.end(JSON.stringify({status: 'ok'}));
|
|
714
|
+
console.log(`Testaro report ${id} was received from Testaro agent ${agentID}`);
|
|
689
715
|
}
|
|
690
716
|
// Otherwise, i.e. if the request is invalid:
|
|
691
717
|
else {
|
|
692
|
-
await serveError({message: 'ERROR:
|
|
718
|
+
await serveError({message: 'ERROR: Request invalid'}, response, false);
|
|
693
719
|
}
|
|
694
720
|
}
|
|
695
|
-
// Otherwise, if the service is
|
|
721
|
+
// Otherwise, i.e. if the service is invalid:
|
|
696
722
|
else {
|
|
697
723
|
await serveError(
|
|
698
724
|
{message: 'ERROR: Invalid service request from Testaro agent'}, response, false
|
|
699
725
|
);
|
|
700
726
|
}
|
|
701
727
|
}
|
|
702
|
-
// Otherwise, if the first segment is the
|
|
703
|
-
else if (segments[0]
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
//
|
|
708
|
-
const
|
|
709
|
-
.response([description,
|
|
710
|
-
// Send
|
|
728
|
+
// Otherwise, if the first segment is not the ID of the Testaro agent:
|
|
729
|
+
else if (segments[0] !== testaroAgent) {
|
|
730
|
+
// If the service is to receive a test request:
|
|
731
|
+
if (segments[0] === 'requestTest') {
|
|
732
|
+
const {description, URL, reason} = postData;
|
|
733
|
+
// Get the response body.
|
|
734
|
+
const responseBody = await require(path.join(__dirname, 'api', 'requestTest'))
|
|
735
|
+
.response([description, URL, reason]);
|
|
736
|
+
// Send it.
|
|
711
737
|
setHeaders('application/json', null, 'ultra');
|
|
712
|
-
response.end(JSON.stringify(
|
|
738
|
+
response.end(JSON.stringify(responseBody));
|
|
713
739
|
}
|
|
714
|
-
// Otherwise,
|
|
715
|
-
else {
|
|
716
|
-
|
|
717
|
-
|
|
740
|
+
// Otherwise, if the service is to receive a retest request:
|
|
741
|
+
else if (segments[0] === 'requestRetest') {
|
|
742
|
+
const {reason} = postData;
|
|
743
|
+
// Get the response body.
|
|
744
|
+
const responseBody = await require(path.join(__dirname, 'api', 'requestRetest'))
|
|
745
|
+
.response(segments.slice(1).concat(reason));
|
|
746
|
+
// Send it.
|
|
747
|
+
setHeaders('application/json', null, 'ultra');
|
|
748
|
+
response.end(JSON.stringify(responseBody));
|
|
718
749
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
} = postData;
|
|
727
|
-
// If the payload is a valid test recommendation:
|
|
728
|
-
if (what && isURL(url) && why) {
|
|
729
|
-
let responseData;
|
|
730
|
-
// If a report on the page is already available:
|
|
731
|
-
if (await isReportAvailable(what, url)) {
|
|
732
|
-
// Report this.
|
|
733
|
-
responseData = {
|
|
734
|
-
status: 'warning',
|
|
735
|
-
message: 'A report on the page is already available'
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
// Otherwise, i.e. if no report on the page is available:
|
|
739
|
-
else {
|
|
740
|
-
// Process the recommendation and get the response data.
|
|
741
|
-
responseData = await require(path.join(__dirname, 'testRecForm', 'api'))
|
|
742
|
-
.response(what, url, why);
|
|
743
|
-
}
|
|
744
|
-
// Send the response data.
|
|
750
|
+
// Otherwise, if the service is to receive a feature request:
|
|
751
|
+
else if (segments[0] === 'requestFeature') {
|
|
752
|
+
const {request} = postData;
|
|
753
|
+
// Get the response body.
|
|
754
|
+
const responseBody = await require(path.join(__dirname, 'api', 'requestFeature'))
|
|
755
|
+
.response(segments.slice(1).concat(request));
|
|
756
|
+
// Send it.
|
|
745
757
|
setHeaders('application/json', null, 'ultra');
|
|
746
|
-
response.end(JSON.stringify(
|
|
758
|
+
response.end(JSON.stringify(responseBody));
|
|
747
759
|
}
|
|
748
|
-
// Otherwise, i.e. if
|
|
760
|
+
// Otherwise, i.e. if the service is invalid:
|
|
749
761
|
else {
|
|
750
|
-
|
|
751
|
-
|
|
762
|
+
await serveError(
|
|
763
|
+
{message: 'ERROR: Invalid request'}, response, false
|
|
764
|
+
);
|
|
752
765
|
}
|
|
753
766
|
}
|
|
754
767
|
// Otherwise, i.e. if the request is invalid:
|
|
@@ -790,7 +803,7 @@ const requestHandler = async (request, response) => {
|
|
|
790
803
|
|
|
791
804
|
const serve = async (protocolModule, options) => {
|
|
792
805
|
// Create any missing directories.
|
|
793
|
-
for (const path of [queuePath, claimedPath, failedPath,
|
|
806
|
+
for (const path of [queuePath, claimedPath, failedPath, reportsPath]) {
|
|
794
807
|
await fs.mkdir(path, {recursive: true});
|
|
795
808
|
}
|
|
796
809
|
const server = protocolModule === 'https'
|
package/issues/index.js
CHANGED
|
@@ -7,14 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
const {sendAlert} = require('../alerts');
|
|
9
9
|
const {
|
|
10
|
-
|
|
10
|
+
getLatestReportExtracts,
|
|
11
11
|
getReport,
|
|
12
12
|
getToolNamesString,
|
|
13
|
-
getLogs,
|
|
14
13
|
getWCAGLink,
|
|
15
14
|
getWeightName,
|
|
16
15
|
objectSort,
|
|
17
|
-
ruleIDs
|
|
18
16
|
} = require('../util');
|
|
19
17
|
const {issues} = require('testilo/procs/score/tic');
|
|
20
18
|
const fs = require('fs/promises');
|
|
@@ -23,20 +21,19 @@ const path = require('path');
|
|
|
23
21
|
// FUNCTIONS
|
|
24
22
|
|
|
25
23
|
// Gets summary data on the issues reported in a set of reports.
|
|
26
|
-
const getIssuesSummary = async
|
|
24
|
+
const getIssuesSummary = async () => {
|
|
27
25
|
// Initialize the summary.
|
|
28
|
-
const summary = {
|
|
26
|
+
const summary = {
|
|
27
|
+
totalCount: 0,
|
|
28
|
+
issues: []
|
|
29
|
+
};
|
|
29
30
|
// Initialize data for a summary.
|
|
30
31
|
const issuesData = {};
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (! annotated) {
|
|
37
|
-
// Annotate it and mark it as annotated in the log.
|
|
38
|
-
await annotateReport(ruleIDs, timeStamp, jobID);
|
|
39
|
-
}
|
|
32
|
+
// Get extracts of the latest available report on each page.
|
|
33
|
+
const latestReportExtracts = await getLatestReportExtracts();
|
|
34
|
+
// For each of them:
|
|
35
|
+
for (const reportExtract of latestReportExtracts) {
|
|
36
|
+
const {timeStamp, jobID} = reportExtract;
|
|
40
37
|
// Get the corresponding report.
|
|
41
38
|
const report = await getReport(timeStamp, jobID);
|
|
42
39
|
const {acts = [], error} = report;
|
|
@@ -68,9 +65,6 @@ const getIssuesSummary = async logs => {
|
|
|
68
65
|
}
|
|
69
66
|
});
|
|
70
67
|
}
|
|
71
|
-
// Initialize the summary properties.
|
|
72
|
-
summary.totalCount = 0;
|
|
73
|
-
summary.issues = [];
|
|
74
68
|
// For each issue:
|
|
75
69
|
Object.entries(issuesData).forEach(([issueID, data]) => {
|
|
76
70
|
const {count, reporters} = data;
|
|
@@ -109,10 +103,8 @@ const getIssuesSummary = async logs => {
|
|
|
109
103
|
};
|
|
110
104
|
// Adds parameters to a query for the answer page.
|
|
111
105
|
const populateQuery = async query => {
|
|
112
|
-
// Get the logs of the latest reports on the tested targets.
|
|
113
|
-
const targetLogs = (await getLogs()).filter(log => ! log.superseded);
|
|
114
106
|
// Get summary data on the issues.
|
|
115
|
-
const issuesSummary = await getIssuesSummary(
|
|
107
|
+
const issuesSummary = await getIssuesSummary();
|
|
116
108
|
// If this failed:
|
|
117
109
|
if (issuesSummary.error) {
|
|
118
110
|
// Populate the query with the reason.
|
|
@@ -160,7 +152,7 @@ const populateQuery = async query => {
|
|
|
160
152
|
}
|
|
161
153
|
});
|
|
162
154
|
lines.push(`${margin}</ul>`);
|
|
163
|
-
if (!
|
|
155
|
+
if (!existsIssue) {
|
|
164
156
|
lines.push(`${margin}<p>No issues with this priority.</p>`);
|
|
165
157
|
}
|
|
166
158
|
});
|
package/jsconfig.json
ADDED
package/llms-full.txt
CHANGED
|
@@ -90,7 +90,7 @@ The implementation is currently in an alpha phase and offers these tools:
|
|
|
90
90
|
- `summarizeOneReport`
|
|
91
91
|
- method: `GET`
|
|
92
92
|
- purpose: summarize one report
|
|
93
|
-
- path: `/api/
|
|
93
|
+
- path: `/api/reportFacts/{timeStamp}/{jobID}`
|
|
94
94
|
- parameters
|
|
95
95
|
- `timeStamp`: initial segment of report identifier
|
|
96
96
|
- `jobID`: final segment of report identifier
|
|
@@ -156,7 +156,7 @@ More information about Kilotest features and internals:
|
|
|
156
156
|
|
|
157
157
|
### For language models and AI platforms
|
|
158
158
|
|
|
159
|
-
- [AI-
|
|
159
|
+
- [AI-TOOLS file](https://github.com/jrpool/kilotest/blob/main/docs/AI-TOOLS.md)
|
|
160
160
|
- [OpenAPI specification for Kilotest](https://kilotest.com/openapi.yaml)
|
|
161
161
|
- [Tool caller for testing](https://github.com/jrpool/kilotest/blob/main/researchAgent.js)
|
|
162
162
|
|