testdriverai 7.2.91 → 7.3.1

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/index.js CHANGED
@@ -214,9 +214,6 @@ class TestDriverAgent extends EventEmitter2 {
214
214
  // Ignore sandbox close errors during exit
215
215
  }
216
216
  }
217
-
218
- // Clean up IDE session file
219
- this.cleanupIdeSessionFile();
220
217
 
221
218
  shouldRunPostrun =
222
219
  !this.hasRunPostrun &&
@@ -440,10 +437,13 @@ class TestDriverAgent extends EventEmitter2 {
440
437
  this.emitter.emit(events.log.narration, theme.dim("checking..."));
441
438
 
442
439
  // check asks the ai if the task is complete
443
- let thisScreenshot = await this.system.captureScreenBase64(1, false, true);
440
+ // Parallelize system calls for better performance
441
+ const [thisScreenshot, mousePosition, activeWindow] = await Promise.all([
442
+ this.system.captureScreenBase64(1, false, true),
443
+ this.system.getMousePosition(),
444
+ this.system.activeWin(),
445
+ ]);
444
446
  let images = [this.lastScreenshot, thisScreenshot];
445
- let mousePosition = await this.system.getMousePosition();
446
- let activeWindow = await this.system.activeWin();
447
447
 
448
448
  let response = await this.sdk.req("check", {
449
449
  tasks: this.tasks,
@@ -905,12 +905,18 @@ commands:
905
905
 
906
906
  this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
907
907
 
908
- this.lastScreenshot = await this.system.captureScreenBase64();
908
+ // Parallelize system calls for better performance
909
+ const [screenshot, mousePosition, activeWindow] = await Promise.all([
910
+ this.system.captureScreenBase64(),
911
+ this.system.getMousePosition(),
912
+ this.system.activeWin(),
913
+ ]);
914
+ this.lastScreenshot = screenshot;
909
915
 
910
916
  let message = await this.sdk.req("input", {
911
917
  input: currentTask,
912
- mousePosition: await this.system.getMousePosition(),
913
- activeWindow: await this.system.activeWin(),
918
+ mousePosition,
919
+ activeWindow,
914
920
  image: this.lastScreenshot,
915
921
  });
916
922
 
@@ -941,13 +947,15 @@ commands:
941
947
 
942
948
  this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
943
949
 
944
- let image = await this.system.captureScreenBase64();
945
-
946
950
  const streamId = `generate-${Date.now()}`;
947
951
  this.emitter.emit(events.log.markdown.start, streamId);
948
952
 
949
- let mouse = await this.system.getMousePosition();
950
- let activeWindow = await this.system.activeWin();
953
+ // Parallelize system calls for better performance
954
+ const [image, mouse, activeWindow] = await Promise.all([
955
+ this.system.captureScreenBase64(),
956
+ this.system.getMousePosition(),
957
+ this.system.activeWin(),
958
+ ]);
951
959
 
952
960
  let message = await this.sdk.req(
953
961
  "generate",
@@ -2042,8 +2050,8 @@ ${regression}
2042
2050
  const previewMode = this.config.TD_PREVIEW || "browser";
2043
2051
 
2044
2052
  if (previewMode === "ide") {
2045
- // Write session file for VSCode extension to pick up
2046
- this.writeIdeSessionFile(urlToOpen, data);
2053
+ // Send session to VS Code extension via HTTP
2054
+ this.sendIdeSessionNotification(urlToOpen, data);
2047
2055
  } else if (previewMode !== "none") {
2048
2056
  // Open in browser (default behavior)
2049
2057
  this.emitter.emit(events.showWindow, urlToOpen);
@@ -2052,64 +2060,135 @@ ${regression}
2052
2060
  }
2053
2061
  }
2054
2062
 
2055
- // Write session file for IDE preview mode
2056
- writeIdeSessionFile(debuggerUrl, data) {
2063
+ // Find the VS Code instance that contains the test file
2064
+ findTargetIdeInstance(testFilePath) {
2057
2065
  const fs = require("fs");
2058
2066
  const os = require("os");
2059
2067
  const path = require("path");
2060
2068
 
2061
- const sessionDir = path.join(os.homedir(), ".testdriver");
2062
- const sessionsDir = path.join(sessionDir, "ide-sessions");
2069
+ const instancesDir = path.join(os.homedir(), ".testdriver", "ide-instances");
2063
2070
 
2064
- // Generate a unique session ID based on test file and timestamp
2065
- const testFileName = (data.testFile || this.thisFile || "test")
2066
- .split(path.sep).pop()
2067
- .replace(/\.[^/.]+$/, ""); // Remove file extension
2068
- const sessionId = `${testFileName}-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
2069
- const sessionFile = path.join(sessionsDir, `${sessionId}.json`);
2070
-
2071
- try {
2072
- // Ensure directories exist
2073
- if (!fs.existsSync(sessionDir)) {
2074
- fs.mkdirSync(sessionDir, { recursive: true });
2075
- }
2076
- if (!fs.existsSync(sessionsDir)) {
2077
- fs.mkdirSync(sessionsDir, { recursive: true });
2078
- }
2071
+ if (!fs.existsSync(instancesDir)) {
2072
+ return null;
2073
+ }
2079
2074
 
2080
- const sessionData = {
2081
- sessionId: sessionId,
2082
- debuggerUrl: debuggerUrl,
2083
- resolution: data.resolution || this.config.TD_RESOLUTION,
2084
- testFile: data.testFile || this.thisFile,
2085
- os: data.os || this.sandboxOs || "linux",
2086
- timestamp: Date.now(),
2087
- };
2075
+ const files = fs.readdirSync(instancesDir);
2076
+ const normalizedTestPath = testFilePath ? path.normalize(testFilePath) : null;
2077
+
2078
+ let matchingInstance = null;
2079
+ let longestMatchLength = 0;
2088
2080
 
2089
- fs.writeFileSync(sessionFile, JSON.stringify(sessionData, null, 2));
2090
- logger.log(`IDE session file written: ${sessionFile}`);
2081
+ for (const file of files) {
2082
+ if (!file.endsWith('.json')) continue;
2091
2083
 
2092
- // Store session file path for cleanup on exit
2093
- this._ideSessionFile = sessionFile;
2094
- } catch (error) {
2095
- logger.warn(`Failed to write IDE session file: ${error.message}`);
2096
- }
2097
- }
2098
-
2099
- // Clean up IDE session file when test completes
2100
- cleanupIdeSessionFile() {
2101
- if (this._ideSessionFile) {
2102
- const fs = require("fs");
2103
2084
  try {
2104
- if (fs.existsSync(this._ideSessionFile)) {
2105
- fs.unlinkSync(this._ideSessionFile);
2106
- logger.log(`IDE session file cleaned up: ${this._ideSessionFile}`);
2085
+ const registrationPath = path.join(instancesDir, file);
2086
+ const registration = JSON.parse(fs.readFileSync(registrationPath, 'utf-8'));
2087
+
2088
+ // Check if this instance is still alive (registration within last 60 seconds or process exists)
2089
+ const isRecent = Date.now() - registration.timestamp < 60000;
2090
+
2091
+ // Skip stale registrations
2092
+ if (!isRecent) {
2093
+ // Try to clean up stale file
2094
+ try { fs.unlinkSync(registrationPath); } catch {}
2095
+ continue;
2096
+ }
2097
+
2098
+ // If we have a test file path, find the best matching workspace
2099
+ if (normalizedTestPath && registration.workspacePaths) {
2100
+ for (const workspacePath of registration.workspacePaths) {
2101
+ const normalizedWorkspace = path.normalize(workspacePath);
2102
+ if (normalizedTestPath.startsWith(normalizedWorkspace + path.sep) ||
2103
+ normalizedTestPath === normalizedWorkspace) {
2104
+ // Prefer longest match (most specific workspace)
2105
+ if (normalizedWorkspace.length > longestMatchLength) {
2106
+ longestMatchLength = normalizedWorkspace.length;
2107
+ matchingInstance = registration;
2108
+ }
2109
+ }
2110
+ }
2111
+ } else if (!matchingInstance) {
2112
+ // If no test file path, just use the first available instance
2113
+ matchingInstance = registration;
2107
2114
  }
2108
2115
  } catch (error) {
2109
- // Ignore cleanup errors
2116
+ // Ignore malformed registration files
2110
2117
  }
2111
- this._ideSessionFile = null;
2112
2118
  }
2119
+
2120
+ return matchingInstance;
2121
+ }
2122
+
2123
+ // Send session notification to VS Code extension via HTTP
2124
+ sendIdeSessionNotification(debuggerUrl, data) {
2125
+ const http = require("http");
2126
+ const path = require("path");
2127
+
2128
+ const testFilePath = data.testFile || this.thisFile;
2129
+ const targetInstance = this.findTargetIdeInstance(testFilePath);
2130
+
2131
+ if (!targetInstance) {
2132
+ logger.warn("No VS Code instance found for IDE preview. Make sure VS Code with TestDriver extension is open.");
2133
+ // Fall back to browser
2134
+ this.emitter.emit(events.showWindow, debuggerUrl);
2135
+ return;
2136
+ }
2137
+
2138
+ // Generate a unique session ID
2139
+ const testFileName = (testFilePath || "test")
2140
+ .split(path.sep).pop()
2141
+ .replace(/\.[^/.]+$/, "");
2142
+ const sessionId = `${testFileName}-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
2143
+
2144
+ const sessionData = {
2145
+ sessionId: sessionId,
2146
+ debuggerUrl: debuggerUrl,
2147
+ resolution: data.resolution || this.config.TD_RESOLUTION,
2148
+ testFile: testFilePath,
2149
+ os: data.os || this.sandboxOs || "linux",
2150
+ timestamp: Date.now(),
2151
+ };
2152
+
2153
+ const postData = JSON.stringify(sessionData);
2154
+
2155
+ const options = {
2156
+ hostname: '127.0.0.1',
2157
+ port: targetInstance.port,
2158
+ path: '/session',
2159
+ method: 'POST',
2160
+ headers: {
2161
+ 'Content-Type': 'application/json',
2162
+ 'Content-Length': Buffer.byteLength(postData)
2163
+ },
2164
+ timeout: 5000
2165
+ };
2166
+
2167
+ const req = http.request(options, (res) => {
2168
+ if (res.statusCode === 200) {
2169
+ logger.log(`IDE session notification sent to port ${targetInstance.port}`);
2170
+ } else {
2171
+ logger.warn(`IDE session notification failed with status ${res.statusCode}`);
2172
+ // Fall back to browser on failure
2173
+ this.emitter.emit(events.showWindow, debuggerUrl);
2174
+ }
2175
+ });
2176
+
2177
+ req.on('error', (error) => {
2178
+ logger.warn(`Failed to send IDE session notification: ${error.message}`);
2179
+ // Fall back to browser on error
2180
+ this.emitter.emit(events.showWindow, debuggerUrl);
2181
+ });
2182
+
2183
+ req.on('timeout', () => {
2184
+ req.destroy();
2185
+ logger.warn('IDE session notification timed out');
2186
+ // Fall back to browser on timeout
2187
+ this.emitter.emit(events.showWindow, debuggerUrl);
2188
+ });
2189
+
2190
+ req.write(postData);
2191
+ req.end();
2113
2192
  }
2114
2193
 
2115
2194
  async connectToSandboxService() {
@@ -10,7 +10,10 @@ const createAnalytics = (emitter, config, sessionInstance) => {
10
10
  return;
11
11
  }
12
12
  if (Math.random() <= 0.01) {
13
- await sdk.req("analytics", { event, data });
13
+ // Fire-and-forget: don't await analytics calls
14
+ sdk.req("analytics", { event, data }).catch((err) => {
15
+ console.warn("Analytics track failed:", err.message);
16
+ });
14
17
  }
15
18
  },
16
19
  };