@empiricalrun/test-gen 0.61.0 → 0.63.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.
Files changed (98) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/agent/browsing/run.d.ts +2 -0
  3. package/dist/agent/browsing/run.d.ts.map +1 -1
  4. package/dist/agent/browsing/run.js +11 -8
  5. package/dist/agent/browsing/utils.d.ts.map +1 -1
  6. package/dist/agent/browsing/utils.js +1 -1
  7. package/dist/agent/chat/agent-loop.js +2 -3
  8. package/dist/agent/chat/exports.d.ts +2 -2
  9. package/dist/agent/chat/exports.d.ts.map +1 -1
  10. package/dist/agent/chat/exports.js +1 -1
  11. package/dist/agent/chat/index.d.ts.map +1 -1
  12. package/dist/agent/chat/index.js +24 -4
  13. package/dist/agent/chat/models.d.ts +1 -3
  14. package/dist/agent/chat/models.d.ts.map +1 -1
  15. package/dist/agent/chat/models.js +4 -25
  16. package/dist/agent/cua/computer.d.ts +6 -6
  17. package/dist/agent/cua/computer.d.ts.map +1 -1
  18. package/dist/agent/cua/computer.js +38 -83
  19. package/dist/agent/cua/index.d.ts +2 -1
  20. package/dist/agent/cua/index.d.ts.map +1 -1
  21. package/dist/agent/cua/index.js +26 -33
  22. package/dist/agent/cua/pw-codegen/element-from-point.d.ts +8 -0
  23. package/dist/agent/cua/pw-codegen/element-from-point.d.ts.map +1 -0
  24. package/dist/agent/cua/pw-codegen/element-from-point.js +118 -0
  25. package/dist/agent/cua/pw-codegen/pw-pause/index.d.ts +15 -0
  26. package/dist/agent/cua/pw-codegen/pw-pause/index.d.ts.map +1 -0
  27. package/dist/agent/cua/pw-codegen/pw-pause/index.js +84 -0
  28. package/dist/agent/cua/pw-codegen/pw-pause/utils.d.ts +16 -0
  29. package/dist/agent/cua/pw-codegen/pw-pause/utils.d.ts.map +1 -0
  30. package/dist/agent/cua/pw-codegen/pw-pause/utils.js +98 -0
  31. package/dist/agent/cua/pw-codegen/types.d.ts +46 -0
  32. package/dist/agent/cua/pw-codegen/types.d.ts.map +1 -0
  33. package/dist/agent/cua/pw-codegen/types.js +2 -0
  34. package/dist/agent/master/browser-tests/cua.spec.js +13 -1
  35. package/dist/artifacts/index.d.ts +52 -0
  36. package/dist/artifacts/index.d.ts.map +1 -0
  37. package/dist/artifacts/index.js +237 -0
  38. package/dist/bin/index.js +7 -11
  39. package/dist/bin/utils/index.d.ts +5 -3
  40. package/dist/bin/utils/index.d.ts.map +1 -1
  41. package/dist/bin/utils/index.js +13 -0
  42. package/dist/bin/utils/platform/web/index.d.ts +1 -1
  43. package/dist/bin/utils/platform/web/index.d.ts.map +1 -1
  44. package/dist/bin/utils/platform/web/index.js +3 -2
  45. package/dist/bin/utils/scenarios/index.d.ts +3 -3
  46. package/dist/file/client.d.ts +2 -0
  47. package/dist/file/client.d.ts.map +1 -1
  48. package/dist/file/client.js +16 -0
  49. package/dist/file/server.d.ts +3 -1
  50. package/dist/file/server.d.ts.map +1 -1
  51. package/dist/file/server.js +27 -3
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +4 -1
  54. package/dist/test-build/index.d.ts +6 -2
  55. package/dist/test-build/index.d.ts.map +1 -1
  56. package/dist/test-build/index.js +9 -7
  57. package/dist/tool-call-service/index.d.ts +14 -7
  58. package/dist/tool-call-service/index.d.ts.map +1 -1
  59. package/dist/tool-call-service/index.js +36 -10
  60. package/dist/tools/commit-and-create-pr.d.ts.map +1 -1
  61. package/dist/tools/commit-and-create-pr.js +11 -4
  62. package/dist/tools/diagnosis-fetcher.d.ts.map +1 -1
  63. package/dist/tools/diagnosis-fetcher.js +4 -3
  64. package/dist/tools/download-build.d.ts.map +1 -1
  65. package/dist/tools/download-build.js +3 -3
  66. package/dist/tools/environment-crud.d.ts.map +1 -1
  67. package/dist/tools/environment-crud.js +6 -4
  68. package/dist/tools/grep/index.d.ts.map +1 -1
  69. package/dist/tools/grep/index.js +13 -11
  70. package/dist/tools/str_replace_editor.d.ts +1 -1
  71. package/dist/tools/str_replace_editor.d.ts.map +1 -1
  72. package/dist/tools/str_replace_editor.js +38 -28
  73. package/dist/tools/test-gen-browser.d.ts.map +1 -1
  74. package/dist/tools/test-gen-browser.js +18 -4
  75. package/dist/tools/test-run-fetcher/index.d.ts.map +1 -1
  76. package/dist/tools/test-run-fetcher/index.js +2 -1
  77. package/dist/tools/test-run.d.ts.map +1 -1
  78. package/dist/tools/test-run.js +10 -8
  79. package/dist/tools/utils/index.d.ts +17 -2
  80. package/dist/tools/utils/index.d.ts.map +1 -1
  81. package/dist/tools/utils/index.js +51 -7
  82. package/dist/utils/checkpoint.d.ts +5 -1
  83. package/dist/utils/checkpoint.d.ts.map +1 -1
  84. package/dist/utils/checkpoint.js +8 -3
  85. package/dist/utils/exec.d.ts +2 -0
  86. package/dist/utils/exec.d.ts.map +1 -1
  87. package/dist/utils/exec.js +4 -1
  88. package/dist/utils/git.d.ts +12 -7
  89. package/dist/utils/git.d.ts.map +1 -1
  90. package/dist/utils/git.js +27 -17
  91. package/dist/utils/slug.d.ts +16 -0
  92. package/dist/utils/slug.d.ts.map +1 -1
  93. package/dist/utils/slug.js +27 -1
  94. package/package.json +6 -4
  95. package/tsconfig.tsbuildinfo +1 -1
  96. package/dist/utils/pw-test.d.ts +0 -2
  97. package/dist/utils/pw-test.d.ts.map +0 -1
  98. package/dist/utils/pw-test.js +0 -13
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ToolCallService = void 0;
4
4
  const chat_1 = require("@empiricalrun/llm/chat");
5
+ const artifacts_1 = require("../artifacts");
5
6
  const commit_and_create_pr_1 = require("../tools/commit-and-create-pr");
6
7
  const diagnosis_fetcher_1 = require("../tools/diagnosis-fetcher");
7
8
  const download_build_1 = require("../tools/download-build");
@@ -19,10 +20,16 @@ class ToolCallService {
19
20
  chatSessionId;
20
21
  selectedModel;
21
22
  branchName;
22
- constructor(chatSessionId, selectedModel, branchName) {
23
+ repoPath;
24
+ apiKey;
25
+ trace;
26
+ constructor({ chatSessionId, selectedModel, branchName, repoPath, apiKey, trace, }) {
23
27
  this.chatSessionId = chatSessionId;
24
28
  this.selectedModel = selectedModel;
25
29
  this.branchName = branchName;
30
+ this.trace = trace;
31
+ this.repoPath = repoPath;
32
+ this.apiKey = apiKey;
26
33
  this.tools = [
27
34
  grep_1.grepTool,
28
35
  test_run_1.runTestTool,
@@ -33,8 +40,6 @@ class ToolCallService {
33
40
  environment_crud_1.getEnvironmentTool,
34
41
  download_build_1.downloadBuildTool,
35
42
  ];
36
- }
37
- async getTools() {
38
43
  if ((0, chat_1.getProviderForModel)(this.selectedModel) !== "claude") {
39
44
  this.tools.push(...str_replace_editor_1.textEditorTools);
40
45
  }
@@ -42,9 +47,13 @@ class ToolCallService {
42
47
  this.toolExecutors[tool.schema.name] = tool.execute;
43
48
  });
44
49
  if ((0, chat_1.getProviderForModel)(this.selectedModel) === "claude") {
45
- this.toolExecutors["str_replace_editor"] = str_replace_editor_1.strReplaceEditorExecutor;
50
+ this.toolExecutors = {
51
+ ...this.toolExecutors,
52
+ // Support for Claude 3x and 4: They use different tool names
53
+ str_replace_editor: str_replace_editor_1.strReplaceEditorExecutor,
54
+ str_replace_based_edit_tool: str_replace_editor_1.strReplaceEditorExecutor,
55
+ };
46
56
  }
47
- return { tools: this.tools };
48
57
  }
49
58
  async sendToQueue(toolCalls) {
50
59
  const requestId = toolCalls[0]?.id;
@@ -59,8 +68,8 @@ class ToolCallService {
59
68
  branchName: this.branchName,
60
69
  });
61
70
  }
62
- async execute(toolCalls, trace) {
63
- const executeSpan = trace?.span({
71
+ async execute(toolCalls) {
72
+ const executeSpan = this.trace?.span({
64
73
  name: "execute_tools",
65
74
  input: { toolCalls: toolCalls.map((tc) => ({ name: tc.name })) },
66
75
  });
@@ -70,31 +79,48 @@ class ToolCallService {
70
79
  name: `tool: ${toolCall.name}`,
71
80
  input: toolCall.input,
72
81
  });
82
+ const uploadArtifactsQueue = new artifacts_1.UploadArtifactsQueue(this.repoPath, toolCall.id);
83
+ const collectArtifactsFn = (artifactsInput) => {
84
+ uploadArtifactsQueue.addTask(artifactsInput).catch((error) => {
85
+ console.error("Error collecting artifacts:", error);
86
+ });
87
+ return;
88
+ };
73
89
  const toolExecutor = this.toolExecutors[toolCall.name];
74
90
  if (!toolExecutor) {
75
91
  const errorResult = {
76
92
  isError: true,
77
93
  result: `Invalid function/tool call: ${toolCall.name} not found`,
94
+ artifacts: null,
78
95
  };
79
96
  toolResults.push(errorResult);
80
97
  span?.end({ output: errorResult });
81
98
  continue;
82
99
  }
100
+ if (!this.apiKey) {
101
+ throw new Error("API key is required for tool execution");
102
+ }
83
103
  try {
84
- const result = await toolExecutor(toolCall.input, trace);
85
- toolResults.push(result);
104
+ const result = await toolExecutor(toolCall.input, this.repoPath, this.apiKey, this.trace, collectArtifactsFn);
105
+ const artifacts = await uploadArtifactsQueue.waitForCompletion();
106
+ toolResults.push({ ...result, artifacts });
86
107
  span?.end({ output: result });
87
108
  }
88
109
  catch (error) {
89
110
  const errorResult = {
90
111
  isError: true,
91
112
  result: error instanceof Error ? error.message : String(error),
113
+ artifacts: null,
92
114
  };
93
115
  toolResults.push(errorResult);
94
116
  span?.end({ output: errorResult });
95
117
  }
96
118
  }
97
- await (0, checkpoint_1.createCheckpoint)(toolCalls, this.branchName);
119
+ await (0, checkpoint_1.createCheckpoint)({
120
+ toolCalls,
121
+ branchName: this.branchName,
122
+ repoPath: this.repoPath,
123
+ });
98
124
  executeSpan?.end({ output: { toolResults } });
99
125
  return toolResults;
100
126
  }
@@ -1 +1 @@
1
- {"version":3,"file":"commit-and-create-pr.d.ts","sourceRoot":"","sources":["../../src/tools/commit-and-create-pr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAOnD,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAiB1D;AA2CD,eAAO,MAAM,qBAAqB,EAAE,IA2EnC,CAAC"}
1
+ {"version":3,"file":"commit-and-create-pr.d.ts","sourceRoot":"","sources":["../../src/tools/commit-and-create-pr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAOnD,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAiB1D;AA2CD,eAAO,MAAM,qBAAqB,EAAE,IAsFnC,CAAC"}
@@ -55,11 +55,13 @@ Don't ask the user for this information, just come up with it yourself.
55
55
  `,
56
56
  parameters: createPullRequestSchema,
57
57
  },
58
- execute: async (input) => {
58
+ execute: async (input, repoPath, apiKey) => {
59
59
  try {
60
60
  const { pullRequestTitle, pullRequestDescription } = input;
61
- const branchName = await (0, git_1.getCurrentBranchName)();
62
- const repoUrl = (0, child_process_1.execSync)("git config --get remote.origin.url")
61
+ const branchName = await (0, git_1.getCurrentBranchName)(repoPath);
62
+ const repoUrl = (0, child_process_1.execSync)("git config --get remote.origin.url", {
63
+ cwd: repoPath,
64
+ })
63
65
  .toString()
64
66
  .trim();
65
67
  const { owner, repo } = parseGitHubUrl(repoUrl);
@@ -70,8 +72,11 @@ Don't ask the user for this information, just come up with it yourself.
70
72
  head: `${owner}:${branchName}`,
71
73
  state: "open",
72
74
  },
75
+ apiKey,
73
76
  }));
74
- (0, child_process_1.execSync)(`git push origin ${branchName} --set-upstream`);
77
+ (0, child_process_1.execSync)(`git push origin ${branchName} --set-upstream`, {
78
+ cwd: repoPath,
79
+ });
75
80
  const existingPR = existingPRs?.find((pr) => pr.head.ref === branchName);
76
81
  if (existingPR) {
77
82
  // Append the new description to the existing PR description
@@ -82,6 +87,7 @@ Don't ask the user for this information, just come up with it yourself.
82
87
  body: {
83
88
  body: updatedDescription,
84
89
  },
90
+ apiKey,
85
91
  });
86
92
  return {
87
93
  isError: false,
@@ -98,6 +104,7 @@ Don't ask the user for this information, just come up with it yourself.
98
104
  base: "main",
99
105
  body: initialDescription,
100
106
  },
107
+ apiKey,
101
108
  }));
102
109
  return {
103
110
  isError: false,
@@ -1 +1 @@
1
- {"version":3,"file":"diagnosis-fetcher.d.ts","sourceRoot":"","sources":["../../src/tools/diagnosis-fetcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAenD,eAAO,MAAM,wBAAwB,EAAE,IAgFtC,CAAC"}
1
+ {"version":3,"file":"diagnosis-fetcher.d.ts","sourceRoot":"","sources":["../../src/tools/diagnosis-fetcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAenD,eAAO,MAAM,wBAAwB,EAAE,IAqFtC,CAAC"}
@@ -19,7 +19,7 @@ exports.fetchDiagnosisReportTool = {
19
19
  description: "Fetch details about a test case diagnosis using its URL or slug",
20
20
  parameters: DiagnosisSchema,
21
21
  },
22
- execute: async (input) => {
22
+ execute: async (input, repoPath, apiKey) => {
23
23
  const { diagnosisUrl } = input;
24
24
  // Extract the slug from the URL - it's the part after the last '--'
25
25
  const slug = diagnosisUrl.split("--").pop();
@@ -33,6 +33,7 @@ exports.fetchDiagnosisReportTool = {
33
33
  try {
34
34
  data = await (0, utils_1.makeDashboardRequest)({
35
35
  path: `/api/diagnosis/${slug}/detailed`,
36
+ apiKey,
36
37
  });
37
38
  }
38
39
  catch (error) {
@@ -43,8 +44,8 @@ exports.fetchDiagnosisReportTool = {
43
44
  }
44
45
  const { test_case, diagnosis } = data.data;
45
46
  const project = diagnosis?.test_project || "unknown";
46
- const sourceContext = await promises_1.default.readFile(path_1.default.join("tests", test_case.file_path), "utf-8");
47
- const repoName = path_1.default.basename(process.cwd());
47
+ const sourceContext = await promises_1.default.readFile(path_1.default.join(repoPath, "tests", test_case.file_path), "utf-8");
48
+ const repoName = path_1.default.basename(repoPath);
48
49
  const cleanErrorStack = diagnosis?.failed_run_metadata?.stack?.replace(`"/runner/_work/${repoName}/${repoName}/source-repo/"`, "");
49
50
  // Format the response as markdown
50
51
  const markdownResponse = `
@@ -1 +1 @@
1
- {"version":3,"file":"download-build.d.ts","sourceRoot":"","sources":["../../src/tools/download-build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,uBAAuB;;;;;;EAElC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE7E,eAAO,MAAM,iBAAiB,EAAE,IA8B/B,CAAC"}
1
+ {"version":3,"file":"download-build.d.ts","sourceRoot":"","sources":["../../src/tools/download-build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,uBAAuB;;;;;;EAElC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE7E,eAAO,MAAM,iBAAiB,EAAE,IAkC/B,CAAC"}
@@ -14,8 +14,8 @@ have a build URL, you can try getting the environment details with the getEnviro
14
14
  Environment details will include the build URL.`,
15
15
  parameters: exports.downloadBuildToolSchema,
16
16
  },
17
- execute: async (input) => {
18
- if (!(await (0, test_build_1.hasDownloadScript)())) {
17
+ execute: async (input, repoPath, apiKey) => {
18
+ if (!(await (0, test_build_1.hasDownloadScript)(repoPath))) {
19
19
  return {
20
20
  isError: true,
21
21
  result: `This repo does not have a download script in package.json.
@@ -24,7 +24,7 @@ You probably don't need to worry about this, since it means this repo does not h
24
24
  }
25
25
  const { buildUrl } = input;
26
26
  try {
27
- await (0, test_build_1.downloadBuild)(buildUrl);
27
+ await (0, test_build_1.downloadBuild)({ buildUrl, repoPath, apiKey });
28
28
  return {
29
29
  isError: false,
30
30
  result: "Build downloaded successfully",
@@ -1 +1 @@
1
- {"version":3,"file":"environment-crud.d.ts","sourceRoot":"","sources":["../../src/tools/environment-crud.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAyDnD,eAAO,MAAM,kBAAkB,EAAE,IAwEhC,CAAC;AAGF,eAAO,MAAM,gBAAgB,EAAE,IAAI,EAAyB,CAAC"}
1
+ {"version":3,"file":"environment-crud.d.ts","sourceRoot":"","sources":["../../src/tools/environment-crud.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AA6DnD,eAAO,MAAM,kBAAkB,EAAE,IA6EhC,CAAC;AAGF,eAAO,MAAM,gBAAgB,EAAE,IAAI,EAAyB,CAAC"}
@@ -5,10 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.environmentTools = exports.getEnvironmentTool = void 0;
7
7
  const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
8
9
  const zod_1 = require("zod");
9
10
  const utils_1 = require("./utils");
10
- const getProjectRepoName = () => {
11
- const packageJson = fs_1.default.readFileSync("package.json", "utf8");
11
+ const getProjectRepoName = (repoPath) => {
12
+ const packageJson = fs_1.default.readFileSync(path_1.default.join(repoPath, "package.json"), "utf8");
12
13
  if (!packageJson) {
13
14
  throw new Error("Could not find or read package.json file");
14
15
  }
@@ -30,11 +31,11 @@ exports.getEnvironmentTool = {
30
31
  description: "Fetch details of an existing environment",
31
32
  parameters: GetEnvironmentSchema,
32
33
  },
33
- execute: async (input) => {
34
+ execute: async (input, repoPath, apiKey) => {
34
35
  // Get project repo name
35
36
  let projectRepoName;
36
37
  try {
37
- projectRepoName = getProjectRepoName();
38
+ projectRepoName = getProjectRepoName(repoPath);
38
39
  }
39
40
  catch (error) {
40
41
  return {
@@ -52,6 +53,7 @@ exports.getEnvironmentTool = {
52
53
  response = await (0, utils_1.makeDashboardRequest)({
53
54
  path: `/api/environments?${queryParams.toString()}`,
54
55
  method: "GET",
56
+ apiKey,
55
57
  });
56
58
  }
57
59
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/grep/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,wBAAwB,CAAC;AAyH/D,eAAO,MAAM,QAAQ,EAAE,IAmBtB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/grep/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,wBAAwB,CAAC;AA6H/D,eAAO,MAAM,QAAQ,EAAE,IAoBtB,CAAC"}
@@ -23,9 +23,9 @@ const GrepInputSchema = zod_1.z.object({
23
23
  .optional()
24
24
  .describe("File pattern to search in (e.g., '*.ts' for TypeScript files)"),
25
25
  });
26
- async function usingSystemGrep(input) {
26
+ async function usingSystemGrep(input, repoPath) {
27
27
  try {
28
- const dir = input.directory || process.cwd();
28
+ const dir = path_1.default.join(repoPath, input.directory || "");
29
29
  // Create exclude pattern for grep
30
30
  const excludePatterns = repo_tree_1.DEFAULT_EXCLUDE.map((pattern) => typeof pattern === "string" ? pattern : pattern.source)
31
31
  .map((pattern) => `--exclude-dir="${pattern}"`)
@@ -71,11 +71,13 @@ async function usingSystemGrep(input) {
71
71
  };
72
72
  }
73
73
  }
74
- async function usingRipgrep(input) {
74
+ async function usingRipgrep(input, repoPath) {
75
75
  try {
76
- const dir = path_1.default.join(process.cwd(), input.directory || "");
77
- const escapedPattern = input.pattern.replace(/\s+/g, "\\ ");
78
- const results = await (0, ripgrep_1.ripgrep)(dir, {
76
+ const dir = path_1.default.join(repoPath, input.directory || "");
77
+ const escapedPattern = input.pattern
78
+ .replace(/\s+/g, "\\ ")
79
+ .replace(/([[\]()])/g, "\\$1");
80
+ const results = (0, ripgrep_1.ripgrep)(dir, {
79
81
  string: escapedPattern,
80
82
  globs: input.filePattern ? [input.filePattern] : undefined,
81
83
  });
@@ -84,7 +86,7 @@ async function usingRipgrep(input) {
84
86
  // Can add submatches and offset info to the summary if needed
85
87
  return {
86
88
  lines: result.lines.text,
87
- path: path_1.default.relative(process.cwd(), result.path.text),
89
+ path: path_1.default.relative(repoPath, result.path.text),
88
90
  // line number is 1-indexed
89
91
  line_number: result.line_number,
90
92
  };
@@ -96,7 +98,7 @@ ${result.path}:${result.line_number}
96
98
  ${result.lines}\`\`\`
97
99
  `;
98
100
  });
99
- const relDir = path_1.default.relative(process.cwd(), dir);
101
+ const relDir = path_1.default.relative(repoPath, dir);
100
102
  const header = `Found ${resultsSummary.length} results for "${input.pattern}" in "${relDir}".
101
103
  All paths are relative to the current working directory.`;
102
104
  return {
@@ -121,13 +123,13 @@ If ripgrep is not available, it will fall back to using system grep.
121
123
  Search is case insensitive and regex patterns are not supported.`,
122
124
  parameters: GrepInputSchema,
123
125
  },
124
- execute: async (input) => {
126
+ execute: async (input, repoPath) => {
125
127
  if ((0, ripgrep_1.isRgAvailable)()) {
126
- return usingRipgrep(input);
128
+ return usingRipgrep(input, repoPath);
127
129
  }
128
130
  else {
129
131
  console.warn("ripgrep is not available, falling back to system grep.");
130
- return usingSystemGrep(input);
132
+ return usingSystemGrep(input, repoPath);
131
133
  }
132
134
  },
133
135
  };
@@ -17,7 +17,7 @@ export declare function cleanupBackupFiles(repoDir: string): number;
17
17
  * Our implementation of Claude's built-in text editor tool
18
18
  * https://docs.anthropic.com/en/docs/build-with-claude/tool-use/text-editor-tool
19
19
  */
20
- export declare function strReplaceEditorExecutor(input: StrReplaceInput): Promise<ToolResult>;
20
+ export declare function strReplaceEditorExecutor(input: StrReplaceInput, repoPath: string): Promise<ToolResult>;
21
21
  export declare const textEditorTools: Tool[];
22
22
  export {};
23
23
  //# sourceMappingURL=str_replace_editor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"str_replace_editor.d.ts","sourceRoot":"","sources":["../../src/tools/str_replace_editor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AA2B1D,UAAU,eAAe;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAqED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAwC1D;AAMD;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,UAAU,CAAC,CAuMrB;AAiGD,eAAO,MAAM,eAAe,EAAE,IAAI,EAKjC,CAAC"}
1
+ {"version":3,"file":"str_replace_editor.d.ts","sourceRoot":"","sources":["../../src/tools/str_replace_editor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AA2B1D,UAAU,eAAe;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAqED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAwC1D;AAMD;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,KAAK,EAAE,eAAe,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,CAAC,CAkNrB;AAuHD,eAAO,MAAM,eAAe,EAAE,IAAI,EAKjC,CAAC"}
@@ -139,23 +139,25 @@ function escapeRegExp(text) {
139
139
  * Our implementation of Claude's built-in text editor tool
140
140
  * https://docs.anthropic.com/en/docs/build-with-claude/tool-use/text-editor-tool
141
141
  */
142
- async function strReplaceEditorExecutor(input) {
142
+ async function strReplaceEditorExecutor(input, repoPath) {
143
+ const repoDir = repoPath;
143
144
  const { path: filePath } = input;
145
+ const absoluteFilePath = path_1.default.join(repoDir, filePath);
144
146
  try {
145
147
  let content;
146
148
  let lines;
147
149
  let newContent;
148
150
  switch (input.command) {
149
151
  case "view":
150
- if (!fs_1.default.existsSync(filePath)) {
152
+ if (!fs_1.default.existsSync(absoluteFilePath)) {
151
153
  return {
152
154
  result: "Error: File not found",
153
155
  isError: true,
154
156
  };
155
157
  }
156
158
  // Handle directory view
157
- if (fs_1.default.statSync(filePath).isDirectory()) {
158
- const files = fs_1.default.readdirSync(filePath);
159
+ if (fs_1.default.statSync(absoluteFilePath).isDirectory()) {
160
+ const files = fs_1.default.readdirSync(absoluteFilePath);
159
161
  return {
160
162
  result: files.join("\n"),
161
163
  isError: false,
@@ -164,7 +166,7 @@ async function strReplaceEditorExecutor(input) {
164
166
  else {
165
167
  // Check if file is binary, which is not supported
166
168
  const { isBinary } = await import("istextorbinary");
167
- const binary = isBinary(filePath);
169
+ const binary = isBinary(absoluteFilePath);
168
170
  if (binary) {
169
171
  return {
170
172
  result: "Error: File is binary, which is not supported",
@@ -173,7 +175,7 @@ async function strReplaceEditorExecutor(input) {
173
175
  }
174
176
  }
175
177
  // Handle file view
176
- content = fs_1.default.readFileSync(filePath, "utf8");
178
+ content = fs_1.default.readFileSync(absoluteFilePath, "utf8");
177
179
  lines = content.split("\n");
178
180
  if (input.view_range) {
179
181
  const [start, end] = input.view_range;
@@ -194,13 +196,19 @@ async function strReplaceEditorExecutor(input) {
194
196
  if (input.file_text === undefined || input.file_text === null) {
195
197
  throw new Error("file_text is required for create command");
196
198
  }
197
- const parentDir = path_1.default.dirname(filePath);
199
+ if (filePath.endsWith("test.ts")) {
200
+ throw new Error("Creating test.ts files is not allowed. Did you mean spec.ts?");
201
+ }
202
+ if (filePath.endsWith("spec.ts") && !filePath.startsWith("tests/")) {
203
+ throw new Error("Creating spec.ts files is not allowed outside tests/ directory");
204
+ }
205
+ const parentDir = path_1.default.dirname(absoluteFilePath);
198
206
  if (parentDir !== "." && !fs_1.default.existsSync(parentDir)) {
199
207
  // Ensure parent directory exists
200
208
  fs_1.default.mkdirSync(parentDir, { recursive: true });
201
209
  }
202
- fs_1.default.writeFileSync(filePath, input.file_text);
203
- let createTypescriptResult = await (0, web_1.runTypescriptCompiler)();
210
+ fs_1.default.writeFileSync(absoluteFilePath, input.file_text);
211
+ let createTypescriptResult = await (0, web_1.runTypescriptCompiler)(repoDir);
204
212
  if (!createTypescriptResult.success) {
205
213
  return {
206
214
  result: `File ${filePath} has been created. However, type checks are failing with errors:\n${createTypescriptResult.errors.join("\n")}`,
@@ -219,8 +227,8 @@ async function strReplaceEditorExecutor(input) {
219
227
  // "" is valid as new_str, so we check for nullish -- not falsy
220
228
  throw new Error("new_str is required for str_replace command");
221
229
  }
222
- createBackup(filePath);
223
- content = fs_1.default.readFileSync(filePath, "utf8");
230
+ createBackup(absoluteFilePath);
231
+ content = fs_1.default.readFileSync(absoluteFilePath, "utf8");
224
232
  // Normalize newlines in both the content and search string
225
233
  const normalizedContent = content.replace(/\r\n/g, "\n");
226
234
  const normalizedOldStr = input.old_str
@@ -252,8 +260,8 @@ async function strReplaceEditorExecutor(input) {
252
260
  };
253
261
  }
254
262
  newContent = normalizedContent.replace(normalizedOldStr, input.new_str);
255
- fs_1.default.writeFileSync(filePath, newContent);
256
- let strReplaceTypescriptResult = await (0, web_1.runTypescriptCompiler)();
263
+ fs_1.default.writeFileSync(absoluteFilePath, newContent);
264
+ let strReplaceTypescriptResult = await (0, web_1.runTypescriptCompiler)(repoDir);
257
265
  if (!strReplaceTypescriptResult.success) {
258
266
  return {
259
267
  result: `Edits to file ${filePath} have been applied. However, type checks are failing with errors:\n${strReplaceTypescriptResult.errors.join("\n")}`,
@@ -271,8 +279,8 @@ async function strReplaceEditorExecutor(input) {
271
279
  if (input.insert_line === undefined || !input.new_str) {
272
280
  throw new Error("insert_line and new_str are required for insert command");
273
281
  }
274
- createBackup(filePath);
275
- content = fs_1.default.readFileSync(filePath, "utf8");
282
+ createBackup(absoluteFilePath);
283
+ content = fs_1.default.readFileSync(absoluteFilePath, "utf8");
276
284
  lines = content.split("\n");
277
285
  if (input.insert_line < 1) {
278
286
  throw new Error("insert_line must be greater than or equal to 1 (line numbers are 1-indexed).");
@@ -281,8 +289,8 @@ async function strReplaceEditorExecutor(input) {
281
289
  throw new Error(`The file at ${filePath} has only ${lines.length} lines, so insert_line must be less than or equal to ${lines.length + 1}. At the maximum value of ${lines.length + 1}, you can insert at the end of the file.`);
282
290
  }
283
291
  lines.splice(input.insert_line - 1, 0, input.new_str);
284
- fs_1.default.writeFileSync(filePath, lines.join("\n"));
285
- let insertTypescriptResult = await (0, web_1.runTypescriptCompiler)();
292
+ fs_1.default.writeFileSync(absoluteFilePath, lines.join("\n"));
293
+ let insertTypescriptResult = await (0, web_1.runTypescriptCompiler)(repoDir);
286
294
  if (!insertTypescriptResult.success) {
287
295
  return {
288
296
  result: `Insertion in file ${filePath} was applied. However, type checks are failing with errors:\n${insertTypescriptResult.errors.join("\n")}`,
@@ -296,8 +304,8 @@ async function strReplaceEditorExecutor(input) {
296
304
  };
297
305
  }
298
306
  case "undo_edit":
299
- if (hasBackup(filePath)) {
300
- restoreBackup(filePath);
307
+ if (hasBackup(absoluteFilePath)) {
308
+ restoreBackup(absoluteFilePath);
301
309
  return {
302
310
  result: `Successfully restored ${filePath} from backup`,
303
311
  isError: false,
@@ -335,11 +343,11 @@ File contents are returned with line numbers, starting from 1.
335
343
  path: zod_1.z.string().describe("The path to the file or directory to view."),
336
344
  }),
337
345
  },
338
- execute: async (input) => {
346
+ execute: async (input, repoPath) => {
339
347
  return strReplaceEditorExecutor({
340
348
  command: "view",
341
349
  path: input.path,
342
- });
350
+ }, repoPath);
343
351
  },
344
352
  };
345
353
  const fileCreateTool = {
@@ -351,12 +359,12 @@ const fileCreateTool = {
351
359
  file_text: zod_1.z.string().describe("The contents of the new file."),
352
360
  }),
353
361
  },
354
- execute: async (input) => {
362
+ execute: async (input, repoPath) => {
355
363
  return strReplaceEditorExecutor({
356
364
  command: "create",
357
365
  path: input.path,
358
366
  file_text: input.file_text,
359
- });
367
+ }, repoPath);
360
368
  },
361
369
  };
362
370
  const stringReplaceTool = {
@@ -370,13 +378,13 @@ in the file. If old_str is not unique, the tool will return an error.`,
370
378
  new_str: zod_1.z.string().describe("The string to replace old_str with."),
371
379
  }),
372
380
  },
373
- execute: async (input) => {
381
+ execute: async (input, repoPath) => {
374
382
  return strReplaceEditorExecutor({
375
383
  command: "str_replace",
376
384
  path: input.path,
377
385
  old_str: input.old_str,
378
386
  new_str: input.new_str,
379
- });
387
+ }, repoPath);
380
388
  },
381
389
  };
382
390
  const stringInsertTool = {
@@ -389,17 +397,19 @@ const stringInsertTool = {
389
397
  .number()
390
398
  .int()
391
399
  .min(1)
392
- .describe("The line number after which to insert the text (1 for beginning of file)."),
400
+ .describe(`The line number on which to insert the text (1 for beginning of file).
401
+ To insert a string at the beginning of the file, you should use insert_line = 1.
402
+ To insert a string at the end of the file, you should use insert_line = (total lines + 1).`),
393
403
  new_str: zod_1.z.string().describe("The string to insert."),
394
404
  }),
395
405
  },
396
- execute: async (input) => {
406
+ execute: async (input, repoPath) => {
397
407
  return strReplaceEditorExecutor({
398
408
  command: "insert",
399
409
  path: input.path,
400
410
  insert_line: input.insert_line,
401
411
  new_str: input.new_str,
402
- });
412
+ }, repoPath);
403
413
  },
404
414
  };
405
415
  exports.textEditorTools = [
@@ -1 +1 @@
1
- {"version":3,"file":"test-gen-browser.d.ts","sourceRoot":"","sources":["../../src/tools/test-gen-browser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAsFnD,eAAO,MAAM,4BAA4B,EAAE,IAqF1C,CAAC"}
1
+ {"version":3,"file":"test-gen-browser.d.ts","sourceRoot":"","sources":["../../src/tools/test-gen-browser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AA2FnD,eAAO,MAAM,4BAA4B,EAAE,IA8G1C,CAAC"}
@@ -9,6 +9,7 @@ const promises_1 = __importDefault(require("fs/promises"));
9
9
  const zod_1 = require("zod");
10
10
  const run_1 = require("../agent/browsing/run");
11
11
  const utils_1 = require("../agent/browsing/utils");
12
+ const pw_pause_1 = require("../agent/cua/pw-codegen/pw-pause");
12
13
  const web_1 = require("../bin/utils/platform/web");
13
14
  const scenarios_1 = require("../bin/utils/scenarios");
14
15
  const BrowserAgentSchema = zod_1.z.object({
@@ -87,8 +88,8 @@ exports.generateTestWithBrowserAgent = {
87
88
  description: BROWSER_AGENT_DESCRIPTION,
88
89
  parameters: BrowserAgentSchema,
89
90
  },
90
- execute: async (input, trace) => {
91
- const repoDir = process.cwd();
91
+ execute: async (input, repoPath, apiKey, trace, collectArtifacts) => {
92
+ const repoDir = repoPath;
92
93
  const { testName, testSuites, fileName, changeToMake, project } = input;
93
94
  try {
94
95
  const { projects } = await (0, test_run_1.getAllPlaywrightProjects)(repoDir);
@@ -113,6 +114,7 @@ exports.generateTestWithBrowserAgent = {
113
114
  result: `Test block not found for test name: "${testName}" in file: "${fileName}" with describe blocks: "${testSuites.join(", ")}"`,
114
115
  };
115
116
  }
117
+ // Prepare the file for the browser agent
116
118
  const fileBackup = await promises_1.default.readFile(fileName, "utf-8");
117
119
  try {
118
120
  await (0, utils_1.replaceTodoWithCreateTest)(fileName);
@@ -125,6 +127,14 @@ exports.generateTestWithBrowserAgent = {
125
127
  result: `Error running tool: ${error}`,
126
128
  };
127
129
  }
130
+ try {
131
+ // Prepare playwright for codegen
132
+ console.log("[generateTestWithBrowserAgent] Preparing playwright for codegen");
133
+ await (0, pw_pause_1.preparePlaywrightForCodegen)(repoDir);
134
+ }
135
+ catch (err) {
136
+ console.warn("[generateTestWithBrowserAgent] Error preparing playwright for codegen", err);
137
+ }
128
138
  const testGenToken = (0, scenarios_1.buildTokenFromOptions)({ name: testName, file: fileName, prompt: changeToMake }, { useComputerUseAgent: true });
129
139
  console.log("[generateTestWithBrowserAgent] Validations passed, starting agent");
130
140
  const toolResult = await (0, run_1.generateTestsUsingMasterAgent)({
@@ -138,9 +148,13 @@ exports.generateTestWithBrowserAgent = {
138
148
  repoDir,
139
149
  editFileWithGeneratedCode: false,
140
150
  });
141
- // Undo the TODO -> createTest changes
151
+ // Cleanup: Undo the TODO -> createTest changes
152
+ await (0, pw_pause_1.revertToOriginalPwCode)(repoDir);
142
153
  await promises_1.default.writeFile(fileName, fileBackup, "utf-8");
143
- const { isError, error, actionsSummary } = toolResult;
154
+ const { isError, error, actionsSummary, artifacts } = toolResult;
155
+ if (artifacts) {
156
+ void collectArtifacts?.(artifacts);
157
+ }
144
158
  if (!isError) {
145
159
  return {
146
160
  isError,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/test-run-fetcher/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAcnD,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOnE;AAED,eAAO,MAAM,sBAAsB,EAAE,IAwHpC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/test-run-fetcher/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAcnD,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOnE;AAED,eAAO,MAAM,sBAAsB,EAAE,IA6HpC,CAAC"}
@@ -24,7 +24,7 @@ exports.fetchTestRunReportTool = {
24
24
  description: "Fetch details about a test run using its URL",
25
25
  parameters: TestRunSchema,
26
26
  },
27
- execute: async (input) => {
27
+ execute: async (input, repoPath, apiKey) => {
28
28
  const { testRunUrl } = input;
29
29
  // Remove query parameters if they exist
30
30
  const urlWithoutParams = testRunUrl.split("?")[0] || testRunUrl;
@@ -42,6 +42,7 @@ exports.fetchTestRunReportTool = {
42
42
  try {
43
43
  data = await (0, utils_1.makeDashboardRequest)({
44
44
  path: `/api/test-runs/${runId}?repo_name=${repoName}`,
45
+ apiKey,
45
46
  });
46
47
  }
47
48
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"test-run.d.ts","sourceRoot":"","sources":["../../src/tools/test-run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAgDnD,eAAO,MAAM,WAAW,EAAE,IAiDzB,CAAC"}
1
+ {"version":3,"file":"test-run.d.ts","sourceRoot":"","sources":["../../src/tools/test-run.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,wBAAwB,CAAC;AA4C/D,eAAO,MAAM,WAAW,EAAE,IAiEzB,CAAC"}