@empiricalrun/test-gen 0.53.8 → 0.53.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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @empiricalrun/test-gen
2
2
 
3
+ ## 0.53.10
4
+
5
+ ### Patch Changes
6
+
7
+ - 088545c: feat: upload test reports from run-test tool calls
8
+ - d003ea8: feat: add description to PRs created by chat agent
9
+ - Updated dependencies [088545c]
10
+ - @empiricalrun/test-run@0.7.7
11
+
12
+ ## 0.53.9
13
+
14
+ ### Patch Changes
15
+
16
+ - 2e5d412: fix: grep tool should first return stdout, not stderr
17
+ - 2619c58: fix: add selected model info to chat state
18
+ - 58d170d: fix: skip all serial tests after browser agent is done
19
+
3
20
  ## 0.53.8
4
21
 
5
22
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/run.ts"],"names":[],"mappings":"AAiBA,KAAK,iBAAiB,GAAG;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,yBAAyB,EAAE,OAAO,CAAC;CACpC,CAAC;AAEF,wBAAsB,6BAA6B,CAAC,EAClD,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,OAAO,EACP,yBAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC,CAqFD"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/agent/browsing/run.ts"],"names":[],"mappings":"AAiBA,KAAK,iBAAiB,GAAG;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,yBAAyB,EAAE,OAAO,CAAC;CACpC,CAAC;AAEF,wBAAsB,6BAA6B,CAAC,EAClD,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,OAAO,EACP,yBAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC,CAsFD"}
@@ -14,14 +14,15 @@ async function generateTestsUsingMasterAgent({ testFilePath, filePathToUpdate, p
14
14
  if (!fs_extra_1.default.existsSync(testFilePath)) {
15
15
  throw new Error(`File for master agent to run not found: ${testFilePath}`);
16
16
  }
17
- // detect available http port on the machine
18
- const port = await (0, detect_port_1.default)(3030);
17
+ const pm = new exec_1.ProcessManager();
18
+ const availablePort = await (0, detect_port_1.default)(3030);
19
19
  // start a file service to handle file updates from agent
20
20
  // - also update the file path with updates when agent is done spitting out code
21
21
  const fileServer = new server_1.FileServiceServer({
22
- port,
22
+ port: availablePort,
23
23
  repoDir,
24
24
  updateFile: editFileWithGeneratedCode,
25
+ onComplete: () => pm.terminate(),
25
26
  });
26
27
  await fileServer.startFileService();
27
28
  fileServer.setFilePath(filePathToUpdate);
@@ -44,9 +45,9 @@ async function generateTestsUsingMasterAgent({ testFilePath, filePathToUpdate, p
44
45
  if (!isTestRunTriggeredForTeardown) {
45
46
  removeListeners = await teardowns.skipAll();
46
47
  }
47
- await (0, exec_1.cmd)(command.split(" "), {
48
+ await pm.execute(command.split(" "), {
48
49
  env: {
49
- IPC_FILE_SERVICE_PORT: port.toString(),
50
+ IPC_FILE_SERVICE_PORT: availablePort.toString(),
50
51
  PW_TEST_HTML_REPORT_OPEN: "never",
51
52
  // pass the test gen token so that the agent has the same configuration as cli
52
53
  TEST_GEN_TOKEN: testGenToken,
@@ -84,7 +84,7 @@ async function chatAgentLoop({ chatModel, selectedModel, reporter, trace, }) {
84
84
  }
85
85
  chatModel.pushMessage(response);
86
86
  const latest = chatModel.getHumanReadableLatestMessage();
87
- await reporter((0, state_1.chatStateFromModel)(chatModel), latest);
87
+ await reporter((0, state_1.chatStateFromModel)(chatModel, selectedModel), latest);
88
88
  }
89
89
  (0, chat_1.cleanupBackupFiles)(process.cwd());
90
90
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/agent/chat/index.ts"],"names":[],"mappings":"AAYA,OAAO,EAAoB,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAgBhE,wBAAsB,kBAAkB,CAAC,EACvC,mBAAmB,EACnB,aAAa,EACb,oBAAoB,GACrB,EAAE;IACD,aAAa,EAAE,mBAAmB,CAAC;IACnC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,oBAAoB,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C,iBAoFA;AAqBD,wBAAsB,wBAAwB,CAAC,EAC7C,aAAa,EACb,aAAa,GACd,EAAE;IACD,aAAa,EAAE,mBAAmB,CAAC;IACnC,aAAa,EAAE,MAAM,CAAC;CACvB,iBA+BA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/agent/chat/index.ts"],"names":[],"mappings":"AAYA,OAAO,EAAoB,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAiBhE,wBAAsB,kBAAkB,CAAC,EACvC,mBAAmB,EACnB,aAAa,EACb,oBAAoB,GACrB,EAAE;IACD,aAAa,EAAE,mBAAmB,CAAC;IACnC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,oBAAoB,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C,iBAoFA;AAqBD,wBAAsB,wBAAwB,CAAC,EAC7C,aAAa,EACb,aAAa,GACd,EAAE;IACD,aAAa,EAAE,mBAAmB,CAAC;IACnC,aAAa,EAAE,MAAM,CAAC;CACvB,iBA+BA"}
@@ -11,10 +11,10 @@ const state_1 = require("./state");
11
11
  function stopCriteria(userPrompt) {
12
12
  return userPrompt?.toLowerCase() === "stop";
13
13
  }
14
- function concludeAgent(chatModel, useDiskForChatState) {
14
+ function concludeAgent(chatModel, useDiskForChatState, selectedModel) {
15
15
  console.log(`\n${(0, picocolors_1.gray)("Usage summary -> " + chatModel.getUsageSummary())}`);
16
16
  if (useDiskForChatState) {
17
- (0, state_1.saveToDisk)(chatModel.messages);
17
+ (0, state_1.saveToDisk)(chatModel.messages, selectedModel);
18
18
  }
19
19
  }
20
20
  async function runChatAgentForCLI({ useDiskForChatState, selectedModel, initialPromptContent, }) {
@@ -39,7 +39,7 @@ async function runChatAgentForCLI({ useDiskForChatState, selectedModel, initialP
39
39
  }
40
40
  }
41
41
  const handleSigInt = () => {
42
- concludeAgent(chatModel, useDiskForChatState);
42
+ concludeAgent(chatModel, useDiskForChatState, selectedModel);
43
43
  process.exit(0);
44
44
  };
45
45
  process.once("SIGINT", handleSigInt);
@@ -47,7 +47,7 @@ async function runChatAgentForCLI({ useDiskForChatState, selectedModel, initialP
47
47
  let userPrompt;
48
48
  let reporterFunc = async (chatState, latest) => {
49
49
  if (useDiskForChatState) {
50
- (0, state_1.saveToDisk)(chatState.messages);
50
+ (0, state_1.saveToDisk)(chatState.messages, selectedModel);
51
51
  }
52
52
  if (latest) {
53
53
  console.log(`${(0, picocolors_1.blue)(latest.role)}: ${latest.textMessage}`);
@@ -72,7 +72,7 @@ async function runChatAgentForCLI({ useDiskForChatState, selectedModel, initialP
72
72
  catch (e) {
73
73
  // https://github.com/SBoudrias/Inquirer.js/issues/1502#issuecomment-2275991680
74
74
  if (e instanceof Error && e.name === "ExitPromptError") {
75
- concludeAgent(chatModel, useDiskForChatState);
75
+ concludeAgent(chatModel, useDiskForChatState, selectedModel);
76
76
  process.exit(0);
77
77
  }
78
78
  throw e;
@@ -4,11 +4,12 @@ export declare const CURRENT_CHAT_STATE_VERSION = "20250327.1";
4
4
  export declare const CHAT_STATE_PATH: string;
5
5
  export type ChatStateOnDisk<T> = {
6
6
  version: typeof CURRENT_CHAT_STATE_VERSION;
7
+ model: SupportedChatModels;
7
8
  messages: T[];
8
9
  };
9
10
  export declare function createChatState(userPrompt: string, existingState: ChatStateOnDisk<any>, selectedModel: SupportedChatModels): ChatStateOnDisk<unknown>;
10
- export declare function createChatStateForMessages<T>(messages: any): ChatStateOnDisk<T>;
11
- export declare function chatStateFromModel<T>(chatModel: IChatModel<T>): ChatStateOnDisk<unknown>;
11
+ export declare function createChatStateForMessages<T>(messages: any, selectedModel: SupportedChatModels): ChatStateOnDisk<T>;
12
+ export declare function chatStateFromModel<T>(chatModel: IChatModel<T>, selectedModel: SupportedChatModels): ChatStateOnDisk<unknown>;
12
13
  export declare function loadChatState<T>(): ChatStateOnDisk<T> | undefined;
13
- export declare function saveToDisk<T>(messages: Array<T>): void;
14
+ export declare function saveToDisk<T>(messages: Array<T>, selectedModel: SupportedChatModels): void;
14
15
  //# sourceMappingURL=state.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/agent/chat/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAKpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAE9C,eAAO,MAAM,0BAA0B,eAAe,CAAC;AAEvD,eAAO,MAAM,eAAe,QAI3B,CAAC;AAEF,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI;IAC/B,OAAO,EAAE,OAAO,0BAA0B,CAAC;IAC3C,QAAQ,EAAE,CAAC,EAAE,CAAC;CACf,CAAC;AAEF,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,eAAe,CAAC,GAAG,CAAC,EACnC,aAAa,EAAE,mBAAmB,4BAMnC;AAED,wBAAgB,0BAA0B,CAAC,CAAC,EAC1C,QAAQ,EAAE,GAAG,GACZ,eAAe,CAAC,CAAC,CAAC,CAMpB;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,4BAE7D;AAED,wBAAgB,aAAa,CAAC,CAAC,KAAK,eAAe,CAAC,CAAC,CAAC,GAAG,SAAS,CAajE;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,QAmB/C"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/agent/chat/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAKpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAE9C,eAAO,MAAM,0BAA0B,eAAe,CAAC;AAEvD,eAAO,MAAM,eAAe,QAI3B,CAAC;AAEF,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI;IAC/B,OAAO,EAAE,OAAO,0BAA0B,CAAC;IAC3C,KAAK,EAAE,mBAAmB,CAAC;IAC3B,QAAQ,EAAE,CAAC,EAAE,CAAC;CACf,CAAC;AAEF,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,eAAe,CAAC,GAAG,CAAC,EACnC,aAAa,EAAE,mBAAmB,4BAMnC;AAED,wBAAgB,0BAA0B,CAAC,CAAC,EAC1C,QAAQ,EAAE,GAAG,EACb,aAAa,EAAE,mBAAmB,GACjC,eAAe,CAAC,CAAC,CAAC,CAOpB;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,EACxB,aAAa,EAAE,mBAAmB,4BAGnC;AAED,wBAAgB,aAAa,CAAC,CAAC,KAAK,eAAe,CAAC,CAAC,CAAC,GAAG,SAAS,CAajE;AAED,wBAAgB,UAAU,CAAC,CAAC,EAC1B,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,EAClB,aAAa,EAAE,mBAAmB,QAsBnC"}
@@ -13,19 +13,20 @@ function createChatState(userPrompt, existingState, selectedModel) {
13
13
  const messages = existingState.messages || [];
14
14
  const chatModel = (0, model_1.createChatModel)(messages, selectedModel);
15
15
  chatModel.pushUserMessage(userPrompt);
16
- return createChatStateForMessages(chatModel.messages);
16
+ return createChatStateForMessages(chatModel.messages, selectedModel);
17
17
  }
18
18
  exports.createChatState = createChatState;
19
- function createChatStateForMessages(messages) {
19
+ function createChatStateForMessages(messages, selectedModel) {
20
20
  // TODO: Add better types for messages
21
21
  return {
22
22
  version: exports.CURRENT_CHAT_STATE_VERSION,
23
+ model: selectedModel,
23
24
  messages: messages,
24
25
  };
25
26
  }
26
27
  exports.createChatStateForMessages = createChatStateForMessages;
27
- function chatStateFromModel(chatModel) {
28
- return createChatStateForMessages(chatModel.messages);
28
+ function chatStateFromModel(chatModel, selectedModel) {
29
+ return createChatStateForMessages(chatModel.messages, selectedModel);
29
30
  }
30
31
  exports.chatStateFromModel = chatStateFromModel;
31
32
  function loadChatState() {
@@ -40,10 +41,11 @@ function loadChatState() {
40
41
  return state;
41
42
  }
42
43
  exports.loadChatState = loadChatState;
43
- function saveToDisk(messages) {
44
+ function saveToDisk(messages, selectedModel) {
44
45
  const statePath = exports.CHAT_STATE_PATH;
45
46
  let existingState = {
46
47
  version: exports.CURRENT_CHAT_STATE_VERSION,
48
+ model: selectedModel,
47
49
  messages: [],
48
50
  };
49
51
  // Ensure directory exists before trying to read/write
@@ -57,6 +59,7 @@ function saveToDisk(messages) {
57
59
  const newState = {
58
60
  ...existingState,
59
61
  messages: messages,
62
+ model: selectedModel,
60
63
  };
61
64
  fs_1.default.writeFileSync(statePath, JSON.stringify(newState, null, 2));
62
65
  }
@@ -5,10 +5,12 @@ export declare class FileServiceServer {
5
5
  private server;
6
6
  private actionsSummary;
7
7
  private updateFile;
8
- constructor({ port, repoDir, updateFile, }: {
8
+ private onComplete?;
9
+ constructor({ port, repoDir, updateFile, onComplete, }: {
9
10
  port: number;
10
11
  repoDir: string;
11
12
  updateFile: boolean;
13
+ onComplete?: () => void;
12
14
  });
13
15
  getActionsSummary(): string | undefined;
14
16
  setFilePath(filePath: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/file/server.ts"],"names":[],"mappings":"AAWA,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,MAAM,CAA4C;IAC1D,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,UAAU,CAAkB;gBAExB,EACV,IAAI,EACJ,OAAO,EACP,UAAU,GACX,EAAE;QACD,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,OAAO,CAAC;KACrB;IAMD,iBAAiB;IAIjB,WAAW,CAAC,QAAQ,EAAE,MAAM;IAItB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IA+CnC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAgB5B;AAED,wBAAsB,gBAAgB,kBAAK"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/file/server.ts"],"names":[],"mappings":"AAWA,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,MAAM,CAA4C;IAC1D,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,UAAU,CAAC,CAAa;gBAEpB,EACV,IAAI,EACJ,OAAO,EACP,UAAU,EACV,UAAU,GACX,EAAE;QACD,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,OAAO,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;KACzB;IAOD,iBAAiB;IAIjB,WAAW,CAAC,QAAQ,EAAE,MAAM;IAItB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IAmDnC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAgB5B;AAED,wBAAsB,gBAAgB,kBAAK"}
@@ -16,10 +16,12 @@ class FileServiceServer {
16
16
  server;
17
17
  actionsSummary;
18
18
  updateFile = false;
19
- constructor({ port, repoDir, updateFile, }) {
19
+ onComplete;
20
+ constructor({ port, repoDir, updateFile, onComplete, }) {
20
21
  this.port = port;
21
22
  this.repoDir = repoDir;
22
23
  this.updateFile = updateFile;
24
+ this.onComplete = onComplete;
23
25
  }
24
26
  getActionsSummary() {
25
27
  return this.actionsSummary;
@@ -35,6 +37,9 @@ class FileServiceServer {
35
37
  const { generatedCode, importPaths, actionsSummary } = req.body;
36
38
  this.actionsSummary = actionsSummary;
37
39
  if (!this.updateFile) {
40
+ // Not updating the file in this scenario
41
+ if (this.onComplete)
42
+ this.onComplete();
38
43
  return res.send({ success: true });
39
44
  }
40
45
  try {
@@ -45,6 +50,8 @@ class FileServiceServer {
45
50
  const importStatements = await (0, web_1.importAllExportsStmtFromFilePaths)(this.repoDir, importPaths, this.filePath);
46
51
  fs_1.default.writeFileSync(testFilePath, `${importStatements.join("\n")}\n${newContents}`, "utf-8");
47
52
  await (0, web_1.lintErrors)(testFilePath);
53
+ if (this.onComplete)
54
+ this.onComplete();
48
55
  return res.send({ success: true });
49
56
  }
50
57
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAQlC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAqBpC,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,SAAS,iBAyD3E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAQlC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAoBpC,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,SAAS,iBAsD3E"}
package/dist/index.js CHANGED
@@ -11,7 +11,6 @@ const scenarios_1 = require("./bin/utils/scenarios");
11
11
  const client_1 = __importDefault(require("./file/client"));
12
12
  const reporter_1 = require("./reporter");
13
13
  const session_1 = require("./session");
14
- const pw_test_1 = require("./utils/pw-test");
15
14
  const flushEvents = async () => {
16
15
  await (0, llm_1.flushAllTraces)();
17
16
  };
@@ -68,8 +67,6 @@ async function createTest(task, page, scope) {
68
67
  importPaths,
69
68
  actionsSummary,
70
69
  });
71
- // skip the rest of the test once generation is over
72
- await (0, pw_test_1.skipTest)();
73
70
  }
74
71
  finally {
75
72
  // Ensure listeners are removed even if an error occurs
@@ -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;AAQnD,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAiB1D;AAiBD,eAAO,MAAM,wBAAwB,EAAE,IAkFtC,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;AAQnD,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,wBAAwB,EAAE,IA8FtC,CAAC"}
@@ -28,7 +28,24 @@ const CommitAndPushChangesSchema = zod_1.z.object({
28
28
  commitMessage: zod_1.z
29
29
  .string()
30
30
  .describe("A short message to use for the commit. Should not be more than 8 words. Should follow conventional commit format."),
31
+ description: zod_1.z.string().describe(`A longer description of the changes you made. This will be used as the description of a pull request on GitHub, and so you should follow markdown formatting.
32
+ Your code will be reviewed by a human, and you should include everything that will provide context and improve the reviewer's confidence in the changes.
33
+
34
+ For example, if you used the test run tool, you should include the results (and the report URL if available). Report URL is especially important, because it contains
35
+ videos and other artifacts that help the reviewer gain more context and confidence in the changes. If tests pass, reviewer will see the video and merge the PR.
36
+ If tests fail, reviewer will see the video and the test artifacts, and will be able to help you debug the issue.`),
31
37
  });
38
+ function formatDescriptionWithTimestamp(description, existingBody, type = "create") {
39
+ const timestamp = new Date()
40
+ .toISOString()
41
+ .replace("T", " ")
42
+ .replace("Z", " UTC");
43
+ const timestampText = `<sup>${type === "create" ? "Created" : "Updated"} at ${timestamp}</sup>`;
44
+ if (existingBody) {
45
+ return `${existingBody}\n\n---\n${description}\n\n${timestampText}`;
46
+ }
47
+ return `${description}\n\n${timestampText}`;
48
+ }
32
49
  exports.commitAndPushChangesTool = {
33
50
  schema: {
34
51
  name: "commitAndPushChanges",
@@ -41,7 +58,7 @@ Returns the URL of the created or updated pull request.`,
41
58
  },
42
59
  execute: async (input) => {
43
60
  try {
44
- const { commitMessage } = input;
61
+ const { commitMessage, description } = input;
45
62
  const currentBranch = (0, child_process_1.execSync)("git rev-parse --abbrev-ref HEAD")
46
63
  .toString()
47
64
  .trim();
@@ -82,11 +99,21 @@ Returns the URL of the created or updated pull request.`,
82
99
  (0, child_process_1.execSync)(`git push origin ${branchName} --set-upstream`);
83
100
  const existingPR = existingPRs?.find((pr) => pr.head.ref === branchName);
84
101
  if (existingPR) {
102
+ // Append the new description to the existing PR description
103
+ const updatedDescription = formatDescriptionWithTimestamp(description, existingPR.body, "update");
104
+ await (0, utils_1.callGitHubProxy)({
105
+ method: "PATCH",
106
+ url: `https://api.github.com/repos/${owner}/${repo}/pulls/${existingPR.number}`,
107
+ body: {
108
+ body: updatedDescription,
109
+ },
110
+ });
85
111
  return {
86
112
  isError: false,
87
113
  result: `Committed and pushed changes to existing PR: ${existingPR.html_url}`,
88
114
  };
89
115
  }
116
+ const initialDescription = formatDescriptionWithTimestamp(description);
90
117
  const pr = (await (0, utils_1.callGitHubProxy)({
91
118
  method: "POST",
92
119
  url: `https://api.github.com/repos/${owner}/${repo}/pulls`,
@@ -94,7 +121,7 @@ Returns the URL of the created or updated pull request.`,
94
121
  title: commitMessage,
95
122
  head: branchName,
96
123
  base: "main",
97
- body: "Created via CommitAndPushChanges tool",
124
+ body: initialDescription,
98
125
  },
99
126
  }));
100
127
  return {
@@ -41,15 +41,15 @@ exports.grepTool = {
41
41
  cmd = `find ${dir} ${excludeFind} -name "${input.filePattern}" -exec grep -rin "${input.pattern}" {} \\;`;
42
42
  }
43
43
  const { stdout, stderr } = await execAsync(cmd);
44
- if (stderr) {
44
+ if (stdout) {
45
45
  return {
46
- isError: true,
47
- result: stderr,
46
+ isError: false,
47
+ result: stdout,
48
48
  };
49
49
  }
50
50
  return {
51
- isError: false,
52
- result: stdout,
51
+ isError: true,
52
+ result: stderr,
53
53
  };
54
54
  }
55
55
  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;AAuBnD,eAAO,MAAM,WAAW,EAAE,IAgCzB,CAAC"}
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,IAuDzB,CAAC"}
@@ -12,11 +12,25 @@ const RunTestSchema = zod_1.z.object({
12
12
  .string()
13
13
  .describe("The name of the file where the test is located. File name must end with .spec.ts"),
14
14
  project: zod_1.z.string().describe("The project to run the test on"),
15
- headed: zod_1.z
16
- .boolean()
17
- .describe("Whether to run the test in headed mode (default is false, which is headless)")
18
- .optional(),
19
15
  });
16
+ function hasCloudflareCredentials() {
17
+ return (process.env.R2_ACCOUNT_ID &&
18
+ process.env.R2_ACCESS_KEY_ID &&
19
+ process.env.R2_SECRET_ACCESS_KEY);
20
+ }
21
+ function buildReportUrl(projectName, testRunId) {
22
+ return `https://reports.empirical.run/${projectName}/${testRunId}/index.html`;
23
+ }
24
+ function buildResult({ hasTestPassed, summaryJson, reportUrl, }) {
25
+ return `
26
+ Test run is complete. Result: ${hasTestPassed ? "Passed" : "Failed"}
27
+
28
+ ${reportUrl ? `Report URL: ${reportUrl}` : ""}
29
+
30
+ # Raw result (in Playwright JSON format)
31
+ ${JSON.stringify(summaryJson)}
32
+ `;
33
+ }
20
34
  exports.runTestTool = {
21
35
  schema: {
22
36
  name: "runTest",
@@ -24,7 +38,18 @@ exports.runTestTool = {
24
38
  parameters: RunTestSchema,
25
39
  },
26
40
  execute: async (input) => {
27
- const { testName, suites, fileName, project, headed } = input;
41
+ let reportUrl = undefined;
42
+ let projectName = undefined;
43
+ let testRunId = undefined;
44
+ if (hasCloudflareCredentials()) {
45
+ projectName = "test-gen-chat-agent";
46
+ testRunId = Date.now().toString();
47
+ reportUrl = buildReportUrl(projectName, testRunId);
48
+ }
49
+ else {
50
+ console.warn("R2 credentials not found: report artifacts will not be uploaded");
51
+ }
52
+ const { testName, suites, fileName, project } = input;
28
53
  try {
29
54
  // {"project":"chromium","suites":[],"fileName":"tests/quizizz-for-work/group.spec.ts","testName":"Create a group"}
30
55
  // This runs all tests - TODO: Debug this, should only run the testName
@@ -33,10 +58,20 @@ exports.runTestTool = {
33
58
  suites,
34
59
  fileName,
35
60
  projects: [project],
36
- headed,
61
+ // Adding these to enforce report artifacts are uploaded
62
+ envOverrides: projectName && testRunId
63
+ ? {
64
+ PROJECT_NAME: projectName,
65
+ TEST_RUN_GITHUB_ACTION_ID: testRunId,
66
+ }
67
+ : undefined,
37
68
  });
38
69
  return {
39
- result: JSON.stringify(result),
70
+ result: buildResult({
71
+ hasTestPassed: result.hasTestPassed,
72
+ summaryJson: result.summaryJson,
73
+ reportUrl: reportUrl,
74
+ }),
40
75
  isError: false,
41
76
  };
42
77
  }
@@ -1,3 +1,11 @@
1
+ export declare class ProcessManager {
2
+ private childProcess;
3
+ execute(command: string[], options: {
4
+ env?: Record<string, string>;
5
+ }): Promise<number>;
6
+ terminate(): void;
7
+ isRunning(): boolean;
8
+ }
1
9
  export declare function cmd(command: string[], options: {
2
10
  env?: Record<string, string>;
3
11
  }): Promise<number>;
@@ -1 +1 @@
1
- {"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../src/utils/exec.ts"],"names":[],"mappings":"AAGA,wBAAsB,GAAG,CACvB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACxC,OAAO,CAAC,MAAM,CAAC,CAuCjB"}
1
+ {"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../src/utils/exec.ts"],"names":[],"mappings":"AAQA,qBAAa,cAAc;IACzB,OAAO,CAAC,YAAY,CAA6B;IAE3C,OAAO,CACX,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GACxC,OAAO,CAAC,MAAM,CAAC;IAkDlB,SAAS,IAAI,IAAI;IASjB,SAAS,IAAI,OAAO;CAGrB;AAED,wBAAsB,GAAG,CACvB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACxC,OAAO,CAAC,MAAM,CAAC,CAGjB"}
@@ -3,43 +3,73 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.cmd = void 0;
6
+ exports.cmd = exports.ProcessManager = void 0;
7
7
  const child_process_1 = require("child_process");
8
8
  const process_1 = __importDefault(require("process"));
9
- async function cmd(command, options) {
10
- let errorLogs = [];
11
- return new Promise((resolveFunc, rejectFunc) => {
12
- if (!command[0]) {
13
- rejectFunc(new Error("Command cannot be empty"));
14
- return;
9
+ const acceptableExitCodes = [
10
+ 0, // Implies successful execution (no errors)
11
+ 130, // Implies user interrupted the process (for SIGINT)
12
+ ];
13
+ class ProcessManager {
14
+ childProcess = null;
15
+ async execute(command, options) {
16
+ if (this.childProcess) {
17
+ throw new Error("Process is already running");
15
18
  }
16
- let p = (0, child_process_1.spawn)(command[0], command.slice(1), {
17
- env: { ...process_1.default.env, ...options.env },
18
- });
19
- p.stdout.on("data", (x) => {
20
- const log = x.toString();
21
- if (log.includes("Error")) {
22
- errorLogs.push(log);
23
- }
24
- process_1.default.stdout.write(log);
25
- });
26
- p.stderr.on("data", (x) => {
27
- const log = x.toString();
28
- process_1.default.stderr.write(log);
29
- errorLogs.push(log);
30
- });
31
- p.on("error", async (err) => {
32
- rejectFunc(err);
33
- });
34
- p.on("exit", async (code) => {
35
- if (code !== 0) {
36
- const errorMessage = errorLogs.slice(-3).join("\n");
37
- rejectFunc(new Error(errorMessage));
38
- }
39
- else {
40
- resolveFunc(code ?? 1);
19
+ let errorLogs = [];
20
+ return new Promise((resolveFunc, rejectFunc) => {
21
+ if (!command[0]) {
22
+ rejectFunc(new Error("Command cannot be empty"));
23
+ return;
41
24
  }
25
+ const p = (0, child_process_1.spawn)(command[0], command.slice(1), {
26
+ env: { ...process_1.default.env, ...options.env },
27
+ detached: true, // Create process group so we can terminate all child processes
28
+ });
29
+ this.childProcess = p;
30
+ p.stdout.on("data", (x) => {
31
+ const log = x.toString();
32
+ if (log.includes("Error")) {
33
+ errorLogs.push(log);
34
+ }
35
+ process_1.default.stdout.write(log);
36
+ });
37
+ p.stderr.on("data", (x) => {
38
+ const log = x.toString();
39
+ process_1.default.stderr.write(log);
40
+ errorLogs.push(log);
41
+ });
42
+ p.on("error", async (err) => {
43
+ this.childProcess = null;
44
+ rejectFunc(err);
45
+ });
46
+ p.on("exit", async (code) => {
47
+ this.childProcess = null;
48
+ if (!acceptableExitCodes.includes(code ?? 1)) {
49
+ const errorMessage = errorLogs.slice(-3).join("\n");
50
+ rejectFunc(new Error(errorMessage));
51
+ }
52
+ else {
53
+ resolveFunc(code ?? 1);
54
+ }
55
+ });
42
56
  });
43
- });
57
+ }
58
+ terminate() {
59
+ if (!this.childProcess) {
60
+ throw new Error("No process is currently running");
61
+ }
62
+ if (this.childProcess.pid) {
63
+ process_1.default.kill(-this.childProcess.pid, "SIGINT");
64
+ }
65
+ }
66
+ isRunning() {
67
+ return this.childProcess !== null;
68
+ }
69
+ }
70
+ exports.ProcessManager = ProcessManager;
71
+ async function cmd(command, options) {
72
+ const manager = new ProcessManager();
73
+ return manager.execute(command, options);
44
74
  }
45
75
  exports.cmd = cmd;
@@ -1,3 +1,2 @@
1
1
  export declare function getTestFixtureModuleFromRepo(repoDir: string): Promise<any>;
2
- export declare function skipTest(): Promise<void>;
3
2
  //# sourceMappingURL=pw-test.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pw-test.d.ts","sourceRoot":"","sources":["../../src/utils/pw-test.ts"],"names":[],"mappings":"AAEA,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,GAAG,CAAC,CAKd;AAED,wBAAsB,QAAQ,kBAS7B"}
1
+ {"version":3,"file":"pw-test.d.ts","sourceRoot":"","sources":["../../src/utils/pw-test.ts"],"names":[],"mappings":"AAEA,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,GAAG,CAAC,CAKd"}
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.skipTest = exports.getTestFixtureModuleFromRepo = void 0;
6
+ exports.getTestFixtureModuleFromRepo = void 0;
7
7
  const api_1 = __importDefault(require("tsx/cjs/api"));
8
8
  async function getTestFixtureModuleFromRepo(repoDir) {
9
9
  const [lastDir] = repoDir.split("/").reverse();
@@ -12,15 +12,3 @@ async function getTestFixtureModuleFromRepo(repoDir) {
12
12
  return test;
13
13
  }
14
14
  exports.getTestFixtureModuleFromRepo = getTestFixtureModuleFromRepo;
15
- async function skipTest() {
16
- let test;
17
- let repoDir = process.cwd();
18
- try {
19
- test = await getTestFixtureModuleFromRepo(repoDir);
20
- }
21
- catch (e) {
22
- console.error("Error while importing fixture module to extract test:", e);
23
- }
24
- test.skip();
25
- }
26
- exports.skipTest = skipTest;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/test-gen",
3
- "version": "0.53.8",
3
+ "version": "0.53.10",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -56,9 +56,9 @@
56
56
  "tsx": "^4.16.2",
57
57
  "typescript": "^5.3.3",
58
58
  "zod": "^3.23.8",
59
+ "@empiricalrun/llm": "^0.14.5",
59
60
  "@empiricalrun/r2-uploader": "^0.3.8",
60
- "@empiricalrun/test-run": "^0.7.6",
61
- "@empiricalrun/llm": "^0.14.5"
61
+ "@empiricalrun/test-run": "^0.7.7"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@playwright/test": "1.47.1",