testdriverai 5.3.9 ā 5.3.10
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/agent.js +39 -13
- package/docs/features/auto-healing.mdx +1 -1
- package/docs/getting-started/vscode.mdx +1 -1
- package/lib/upload-secrets.js +55 -55
- package/package.json +1 -1
package/agent.js
CHANGED
|
@@ -239,7 +239,13 @@ const dieOnFatal = async (error) => {
|
|
|
239
239
|
// creates a new "thread" in which the AI is given an error
|
|
240
240
|
// and responds. notice `actOnMarkdown` which will continue
|
|
241
241
|
// the thread until there are no more codeblocks to execute
|
|
242
|
-
const haveAIResolveError = async (
|
|
242
|
+
const haveAIResolveError = async (
|
|
243
|
+
error,
|
|
244
|
+
markdown,
|
|
245
|
+
depth = 0,
|
|
246
|
+
undo = true,
|
|
247
|
+
shouldSave,
|
|
248
|
+
) => {
|
|
243
249
|
if (error.fatal) {
|
|
244
250
|
return await dieOnFatal(error);
|
|
245
251
|
}
|
|
@@ -299,7 +305,7 @@ const haveAIResolveError = async (error, markdown, depth = 0, undo = true) => {
|
|
|
299
305
|
mdStream.end();
|
|
300
306
|
|
|
301
307
|
if (response?.data) {
|
|
302
|
-
return await actOnMarkdown(response.data, depth, true);
|
|
308
|
+
return await actOnMarkdown(response.data, depth, true, false, shouldSave);
|
|
303
309
|
}
|
|
304
310
|
};
|
|
305
311
|
|
|
@@ -350,7 +356,7 @@ const check = async () => {
|
|
|
350
356
|
// command is transformed from a single yml entry generated by the AI into a JSON object
|
|
351
357
|
// it is mapped via `commander` to the `commands` module so the yaml
|
|
352
358
|
// parameters can be mapped to actual functions
|
|
353
|
-
const runCommand = async (command, depth) => {
|
|
359
|
+
const runCommand = async (command, depth, shouldSave) => {
|
|
354
360
|
let yml = await yaml.dump(command);
|
|
355
361
|
|
|
356
362
|
logger.debug(`running command: \n\n${yml}`);
|
|
@@ -372,7 +378,7 @@ const runCommand = async (command, depth) => {
|
|
|
372
378
|
}
|
|
373
379
|
|
|
374
380
|
if (response && typeof response === "string") {
|
|
375
|
-
return await actOnMarkdown(response, depth);
|
|
381
|
+
return await actOnMarkdown(response, depth, true, false);
|
|
376
382
|
}
|
|
377
383
|
} catch (error) {
|
|
378
384
|
return await haveAIResolveError(
|
|
@@ -380,6 +386,7 @@ const runCommand = async (command, depth) => {
|
|
|
380
386
|
yaml.dump({ commands: [yml] }),
|
|
381
387
|
depth,
|
|
382
388
|
true,
|
|
389
|
+
shouldSave,
|
|
383
390
|
);
|
|
384
391
|
}
|
|
385
392
|
};
|
|
@@ -392,6 +399,7 @@ const executeCommands = async (
|
|
|
392
399
|
depth,
|
|
393
400
|
pushToHistory = false,
|
|
394
401
|
dry = false,
|
|
402
|
+
shouldSave = false,
|
|
395
403
|
) => {
|
|
396
404
|
if (commands?.length) {
|
|
397
405
|
for (const command of commands) {
|
|
@@ -400,7 +408,11 @@ const executeCommands = async (
|
|
|
400
408
|
}
|
|
401
409
|
|
|
402
410
|
if (!dry) {
|
|
403
|
-
await runCommand(command, depth);
|
|
411
|
+
await runCommand(command, depth, shouldSave);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (shouldSave) {
|
|
415
|
+
await save({ silent: true });
|
|
404
416
|
}
|
|
405
417
|
|
|
406
418
|
let timeToComplete = (new Date().getTime() - lastCommand) / 1000;
|
|
@@ -419,6 +431,7 @@ const executeCodeBlocks = async (
|
|
|
419
431
|
depth,
|
|
420
432
|
pushToHistory = false,
|
|
421
433
|
dry = false,
|
|
434
|
+
shouldSave = false,
|
|
422
435
|
) => {
|
|
423
436
|
depth = depth + 1;
|
|
424
437
|
|
|
@@ -434,23 +447,33 @@ const executeCodeBlocks = async (
|
|
|
434
447
|
e,
|
|
435
448
|
yaml.dump(parser.getYAMLFromCodeBlock(codeblock)),
|
|
436
449
|
depth,
|
|
450
|
+
shouldSave,
|
|
437
451
|
);
|
|
438
452
|
}
|
|
439
453
|
|
|
440
|
-
await executeCommands(commands, depth, pushToHistory, dry);
|
|
454
|
+
await executeCommands(commands, depth, pushToHistory, dry, shouldSave);
|
|
441
455
|
}
|
|
442
456
|
};
|
|
443
457
|
|
|
444
458
|
// this is the main function that interacts with the ai, runs commands, and checks the results
|
|
445
459
|
// notice that depth is 0 here. when this function resolves, the task is considered complete
|
|
446
460
|
// notice the call to `check()` which validates the prompt is complete
|
|
447
|
-
const aiExecute = async (
|
|
461
|
+
const aiExecute = async (
|
|
462
|
+
message,
|
|
463
|
+
validateAndLoop = false,
|
|
464
|
+
dry = false,
|
|
465
|
+
shouldSave = false,
|
|
466
|
+
) => {
|
|
448
467
|
executionHistory.push({ prompt: lastPrompt, commands: [] });
|
|
449
468
|
|
|
469
|
+
if (shouldSave) {
|
|
470
|
+
await save({ silent: true });
|
|
471
|
+
}
|
|
472
|
+
|
|
450
473
|
logger.debug("kicking off exploratory loop");
|
|
451
474
|
|
|
452
475
|
// kick everything off
|
|
453
|
-
await actOnMarkdown(message, 0, true, dry);
|
|
476
|
+
await actOnMarkdown(message, 0, true, dry, shouldSave);
|
|
454
477
|
|
|
455
478
|
if (validateAndLoop) {
|
|
456
479
|
logger.debug("exploratory loop resolved, check your work");
|
|
@@ -461,7 +484,7 @@ const aiExecute = async (message, validateAndLoop = false, dry = false) => {
|
|
|
461
484
|
try {
|
|
462
485
|
checkCodeblocks = await parser.findCodeBlocks(response);
|
|
463
486
|
} catch (error) {
|
|
464
|
-
return await haveAIResolveError(error, response, 0);
|
|
487
|
+
return await haveAIResolveError(error, response, 0, true, true);
|
|
465
488
|
}
|
|
466
489
|
|
|
467
490
|
logger.debug(`found ${checkCodeblocks.length} codeblocks`);
|
|
@@ -560,6 +583,7 @@ const exploratoryLoop = async (
|
|
|
560
583
|
currentTask,
|
|
561
584
|
dry = false,
|
|
562
585
|
validateAndLoop = false,
|
|
586
|
+
shouldSave = true,
|
|
563
587
|
) => {
|
|
564
588
|
lastPrompt = currentTask;
|
|
565
589
|
checkCount = 0;
|
|
@@ -594,7 +618,7 @@ const exploratoryLoop = async (
|
|
|
594
618
|
mdStream.end();
|
|
595
619
|
|
|
596
620
|
if (message) {
|
|
597
|
-
await aiExecute(message.data, validateAndLoop, dry);
|
|
621
|
+
await aiExecute(message.data, validateAndLoop, dry, shouldSave);
|
|
598
622
|
logger.debug("showing prompt from exploratoryLoop response check");
|
|
599
623
|
}
|
|
600
624
|
|
|
@@ -714,6 +738,7 @@ const actOnMarkdown = async (
|
|
|
714
738
|
depth,
|
|
715
739
|
pushToHistory = false,
|
|
716
740
|
dry = false,
|
|
741
|
+
shouldSave,
|
|
717
742
|
) => {
|
|
718
743
|
logger.debug("%j", {
|
|
719
744
|
message: "actOnMarkdown called",
|
|
@@ -725,7 +750,7 @@ const actOnMarkdown = async (
|
|
|
725
750
|
codeblocks = await parser.findCodeBlocks(content);
|
|
726
751
|
} catch (error) {
|
|
727
752
|
pushToHistory = false;
|
|
728
|
-
return await haveAIResolveError(error, content, depth);
|
|
753
|
+
return await haveAIResolveError(error, content, depth, false, shouldSave);
|
|
729
754
|
}
|
|
730
755
|
|
|
731
756
|
if (codeblocks.length) {
|
|
@@ -734,6 +759,7 @@ const actOnMarkdown = async (
|
|
|
734
759
|
depth,
|
|
735
760
|
pushToHistory,
|
|
736
761
|
dry,
|
|
762
|
+
shouldSave,
|
|
737
763
|
);
|
|
738
764
|
return executions;
|
|
739
765
|
} else {
|
|
@@ -864,7 +890,7 @@ const firstPrompt = async () => {
|
|
|
864
890
|
logger.error(result.error.result.stdout);
|
|
865
891
|
}
|
|
866
892
|
} else {
|
|
867
|
-
await exploratoryLoop(input, false, true);
|
|
893
|
+
await exploratoryLoop(input, false, true, true);
|
|
868
894
|
}
|
|
869
895
|
|
|
870
896
|
setTerminalWindowTransparency(false);
|
|
@@ -1104,7 +1130,7 @@ ${yaml.dump(step)}
|
|
|
1104
1130
|
logger.debug("load calling actOnMarkdown");
|
|
1105
1131
|
|
|
1106
1132
|
lastPrompt = step.prompt;
|
|
1107
|
-
await actOnMarkdown(markdown, 0, true);
|
|
1133
|
+
await actOnMarkdown(markdown, 0, true, false, shouldSave);
|
|
1108
1134
|
|
|
1109
1135
|
if (shouldSave) {
|
|
1110
1136
|
await save({ silent: true });
|
|
@@ -12,7 +12,7 @@ The TestDriver.ai VSCode extension is available for download on our GitHub relea
|
|
|
12
12
|
<Card
|
|
13
13
|
title="Download the VSCode Extension"
|
|
14
14
|
icon="download"
|
|
15
|
-
href="https://github.com/testdriverai/vscode/releases"
|
|
15
|
+
href="https://github.com/testdriverai/vscode/releases/latest/download/testdriver.vsix"
|
|
16
16
|
>
|
|
17
17
|
Get the latest version of the TestDriver.ai VSCode extension from our GitHub releases page.
|
|
18
18
|
</Card>
|
package/lib/upload-secrets.js
CHANGED
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
const fs = require(
|
|
2
|
-
const { execSync } = require(
|
|
3
|
-
const dotenv = require(
|
|
4
|
-
const prompts = require(
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const { execSync } = require("child_process");
|
|
3
|
+
const dotenv = require("dotenv");
|
|
4
|
+
const prompts = require("prompts");
|
|
5
5
|
|
|
6
6
|
module.exports = async () => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// 2. Load and parse .env file
|
|
22
|
-
const envPath = '.env';
|
|
23
|
-
if (!fs.existsSync(envPath)) {
|
|
24
|
-
console.error('ā .env file not found.');
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const env = dotenv.parse(fs.readFileSync(envPath));
|
|
29
|
-
const tdSecrets = Object.entries(env).filter(([key]) => key.startsWith('TD_'));
|
|
30
|
-
|
|
31
|
-
if (tdSecrets.length === 0) {
|
|
32
|
-
console.log('ā¹ļø No secrets found in .env that start with TD_');
|
|
33
|
-
process.exit(0);
|
|
34
|
-
}
|
|
7
|
+
// 1. Check for GitHub CLI
|
|
8
|
+
try {
|
|
9
|
+
execSync("gh --version", { stdio: "ignore" });
|
|
10
|
+
} catch (err) {
|
|
11
|
+
console.log(err);
|
|
12
|
+
console.error("ā GitHub CLI (gh) is not installed.\n");
|
|
13
|
+
console.log("š Install it from https://cli.github.com/");
|
|
14
|
+
console.log(" macOS: brew install gh");
|
|
15
|
+
console.log(" Windows: winget install GitHub.cli");
|
|
16
|
+
console.log(" Ubuntu: sudo apt install gh\n");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
35
19
|
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
20
|
+
// 2. Load and parse .env file
|
|
21
|
+
const envPath = ".env";
|
|
22
|
+
if (!fs.existsSync(envPath)) {
|
|
23
|
+
console.error("ā .env file not found.");
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
40
26
|
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
initial: false,
|
|
46
|
-
});
|
|
27
|
+
const env = dotenv.parse(fs.readFileSync(envPath));
|
|
28
|
+
const tdSecrets = Object.entries(env).filter(([key]) =>
|
|
29
|
+
key.startsWith("TD_"),
|
|
30
|
+
);
|
|
47
31
|
|
|
48
|
-
if (
|
|
49
|
-
console.log(
|
|
32
|
+
if (tdSecrets.length === 0) {
|
|
33
|
+
console.log("ā¹ļø No secrets found in .env that start with TD_");
|
|
50
34
|
process.exit(0);
|
|
51
35
|
}
|
|
52
36
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
execSync(cmd, { stdio: 'inherit' });
|
|
58
|
-
} catch (err) {
|
|
59
|
-
console.error(`ā Failed to upload ${key}: ${err.message}`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
37
|
+
// 3. Prompt user for confirmation
|
|
38
|
+
(async () => {
|
|
39
|
+
console.log("\nš The following TD_ secrets will be uploaded:");
|
|
40
|
+
tdSecrets.forEach(([key]) => console.log(`- ${key}`));
|
|
62
41
|
|
|
63
|
-
|
|
42
|
+
const response = await prompts({
|
|
43
|
+
type: "confirm",
|
|
44
|
+
name: "confirm",
|
|
45
|
+
message:
|
|
46
|
+
"\nAre you sure you want to upload these secrets to the current GitHub repo?",
|
|
47
|
+
initial: false,
|
|
48
|
+
});
|
|
64
49
|
|
|
65
|
-
|
|
50
|
+
if (!response.confirm) {
|
|
51
|
+
console.log("ā Upload cancelled.");
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 4. Upload secrets using GitHub CLI
|
|
56
|
+
for (const [key, value] of tdSecrets) {
|
|
57
|
+
try {
|
|
58
|
+
const cmd = `gh secret set ${key} --body "${value}"`;
|
|
59
|
+
execSync(cmd, { stdio: "inherit" });
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error(`ā Failed to upload ${key}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
})();
|
|
65
|
+
};
|