codebuff 1.0.149 → 1.0.151
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/dist/__tests__/browser-runner.test.d.ts +1 -0
- package/dist/__tests__/browser-runner.test.js +15 -0
- package/dist/__tests__/browser-runner.test.js.map +1 -0
- package/dist/browser-runner.d.ts +34 -0
- package/dist/browser-runner.js +622 -0
- package/dist/browser-runner.js.map +1 -0
- package/dist/chat-storage.d.ts +2 -4
- package/dist/chat-storage.js +79 -46
- package/dist/chat-storage.js.map +1 -1
- package/dist/cli.d.ts +27 -10
- package/dist/cli.js +321 -308
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +5 -4
- package/dist/client.js +24 -18
- package/dist/client.js.map +1 -1
- package/dist/code-map/languages.js +7 -17
- package/dist/code-map/languages.js.map +1 -1
- package/dist/code-map/parse.js +7 -17
- package/dist/code-map/parse.js.map +1 -1
- package/dist/code-map/tsconfig.tsbuildinfo +1 -1
- package/dist/common/actions.d.ts +301 -106
- package/dist/common/actions.js +17 -0
- package/dist/common/actions.js.map +1 -1
- package/dist/common/advanced-analyzer.d.ts +19 -0
- package/dist/common/advanced-analyzer.js +140 -0
- package/dist/common/advanced-analyzer.js.map +1 -0
- package/dist/common/browser-actions.d.ts +4354 -0
- package/dist/common/browser-actions.js +336 -0
- package/dist/common/browser-actions.js.map +1 -0
- package/dist/common/constants.d.ts +9 -2
- package/dist/common/constants.js +3 -2
- package/dist/common/constants.js.map +1 -1
- package/dist/common/message-image-handling.d.ts +41 -0
- package/dist/common/message-image-handling.js +57 -0
- package/dist/common/message-image-handling.js.map +1 -0
- package/dist/common/project-file-tree.js +7 -7
- package/dist/common/project-file-tree.js.map +1 -1
- package/dist/common/types/usage.d.ts +2 -2
- package/dist/common/util/credentials.d.ts +2 -2
- package/dist/common/util/file.js +5 -3
- package/dist/common/util/file.js.map +1 -1
- package/dist/common/util/min-heap.d.ts +15 -0
- package/dist/common/util/min-heap.js +73 -0
- package/dist/common/util/min-heap.js.map +1 -0
- package/dist/common/util/string.d.ts +10 -0
- package/dist/common/util/string.js +29 -1
- package/dist/common/util/string.js.map +1 -1
- package/dist/common/websockets/websocket-schema.d.ts +478 -244
- package/dist/create-template-project.d.ts +1 -1
- package/dist/create-template-project.js +27 -29
- package/dist/create-template-project.js.map +1 -1
- package/dist/credentials.d.ts +1 -0
- package/dist/credentials.js +7 -3
- package/dist/credentials.js.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/menu.js +7 -17
- package/dist/menu.js.map +1 -1
- package/dist/project-files.d.ts +3 -0
- package/dist/project-files.js +41 -19
- package/dist/project-files.js.map +1 -1
- package/dist/tool-handlers.d.ts +3 -1
- package/dist/tool-handlers.js +59 -6
- package/dist/tool-handlers.js.map +1 -1
- package/dist/utils/terminal.js +10 -22
- package/dist/utils/terminal.js.map +1 -1
- package/dist/web-scraper.d.ts +1 -1
- package/dist/web-scraper.js +9 -7
- package/dist/web-scraper.js.map +1 -1
- package/package.json +4 -4
- package/dist/common/logger.d.ts +0 -1
- package/dist/common/logger.js +0 -7
- package/dist/common/logger.js.map +0 -1
- package/dist/common/util/constants.d.ts +0 -1
- package/dist/common/util/constants.js +0 -7
- package/dist/common/util/constants.js.map +0 -1
- package/dist/common/util/helpers.d.ts +0 -1
- package/dist/common/util/helpers.js +0 -6
- package/dist/common/util/helpers.js.map +0 -1
- package/dist/common/util/messages.d.ts +0 -1
- package/dist/common/util/messages.js +0 -7
- package/dist/common/util/messages.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const browser_runner_1 = require("../browser-runner");
|
|
4
|
+
// Add your tests here
|
|
5
|
+
describe('BrowserRunner', () => {
|
|
6
|
+
let runner;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
runner = new browser_runner_1.BrowserRunner();
|
|
9
|
+
});
|
|
10
|
+
afterEach(async () => {
|
|
11
|
+
await runner.shutdown();
|
|
12
|
+
});
|
|
13
|
+
// Add test cases...
|
|
14
|
+
});
|
|
15
|
+
//# sourceMappingURL=browser-runner.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-runner.test.js","sourceRoot":"","sources":["../../src/__tests__/browser-runner.test.ts"],"names":[],"mappings":";;AAAA,sDAAiD;AAGjD,sBAAsB;AACtB,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,IAAI,MAAqB,CAAA;IAEzB,UAAU,CAAC,GAAG,EAAE;QACd,MAAM,GAAG,IAAI,8BAAa,EAAE,CAAA;IAC9B,CAAC,CAAC,CAAA;IAEF,SAAS,CAAC,KAAK,IAAI,EAAE;QACnB,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAA;IACzB,CAAC,CAAC,CAAA;IAEF,oBAAoB;AACtB,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { BrowserAction, BrowserResponse } from './common/browser-actions';
|
|
2
|
+
export declare class BrowserRunner {
|
|
3
|
+
getLogs(): BrowserResponse['logs'];
|
|
4
|
+
getNetworkEvents(): BrowserResponse['networkEvents'];
|
|
5
|
+
private browser;
|
|
6
|
+
private page;
|
|
7
|
+
private logs;
|
|
8
|
+
private jsErrorCount;
|
|
9
|
+
private retryCount;
|
|
10
|
+
private startTime;
|
|
11
|
+
private consecutiveErrors;
|
|
12
|
+
private totalErrors;
|
|
13
|
+
constructor();
|
|
14
|
+
private maxConsecutiveErrors;
|
|
15
|
+
private totalErrorThreshold;
|
|
16
|
+
private performanceMetrics;
|
|
17
|
+
private networkEvents;
|
|
18
|
+
private executeWithRetry;
|
|
19
|
+
private executeAction;
|
|
20
|
+
private logErrorForAnalysis;
|
|
21
|
+
private startBrowser;
|
|
22
|
+
private navigate;
|
|
23
|
+
private typeText;
|
|
24
|
+
private scroll;
|
|
25
|
+
private takeScreenshot;
|
|
26
|
+
private attachPageListeners;
|
|
27
|
+
private collectPerformanceMetrics;
|
|
28
|
+
private collectMetrics;
|
|
29
|
+
private filterLogs;
|
|
30
|
+
execute(action: BrowserAction): Promise<BrowserResponse>;
|
|
31
|
+
shutdown(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
export declare const handleBrowserInstruction: (action: BrowserAction) => Promise<BrowserResponse>;
|
|
34
|
+
export declare const activeBrowserRunner: BrowserRunner;
|
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.activeBrowserRunner = exports.handleBrowserInstruction = exports.BrowserRunner = void 0;
|
|
30
|
+
const puppeteer_1 = __importDefault(require("puppeteer"));
|
|
31
|
+
const child_process_1 = require("child_process");
|
|
32
|
+
const promise_1 = require("./common/util/promise");
|
|
33
|
+
const browser_actions_1 = require("./common/browser-actions");
|
|
34
|
+
const fs = __importStar(require("fs"));
|
|
35
|
+
const path = __importStar(require("path"));
|
|
36
|
+
const project_files_1 = require("./project-files");
|
|
37
|
+
const file_1 = require("./common/util/file");
|
|
38
|
+
class BrowserRunner {
|
|
39
|
+
// Add getter methods for diagnostic loop
|
|
40
|
+
getLogs() {
|
|
41
|
+
return this.logs;
|
|
42
|
+
}
|
|
43
|
+
getNetworkEvents() {
|
|
44
|
+
return this.networkEvents;
|
|
45
|
+
}
|
|
46
|
+
browser = null;
|
|
47
|
+
page = null;
|
|
48
|
+
logs = [];
|
|
49
|
+
jsErrorCount = 0;
|
|
50
|
+
retryCount = 0;
|
|
51
|
+
startTime = 0;
|
|
52
|
+
// Error tracking
|
|
53
|
+
consecutiveErrors = 0;
|
|
54
|
+
totalErrors = 0;
|
|
55
|
+
constructor() { }
|
|
56
|
+
// Error tracking configuration
|
|
57
|
+
maxConsecutiveErrors = 3;
|
|
58
|
+
totalErrorThreshold = 10;
|
|
59
|
+
performanceMetrics = {};
|
|
60
|
+
networkEvents = [];
|
|
61
|
+
async executeWithRetry(action) {
|
|
62
|
+
const retryOptions = action.retryOptions ?? browser_actions_1.BROWSER_DEFAULTS.retryOptions;
|
|
63
|
+
let lastError = null;
|
|
64
|
+
for (let attempt = 0; attempt <= (retryOptions.maxRetries ?? 3); attempt++) {
|
|
65
|
+
try {
|
|
66
|
+
const result = await this.executeAction(action);
|
|
67
|
+
// Reset consecutive errors on success
|
|
68
|
+
this.consecutiveErrors = 0;
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
// Track errors
|
|
73
|
+
this.consecutiveErrors++;
|
|
74
|
+
this.totalErrors++;
|
|
75
|
+
// Log error analysis
|
|
76
|
+
this.logErrorForAnalysis(error);
|
|
77
|
+
// Check error thresholds
|
|
78
|
+
if (this.consecutiveErrors >= this.maxConsecutiveErrors) {
|
|
79
|
+
const msg = `Max consecutive errors reached (${this.maxConsecutiveErrors}).`;
|
|
80
|
+
this.logs.push({
|
|
81
|
+
type: 'error',
|
|
82
|
+
message: msg,
|
|
83
|
+
timestamp: Date.now(),
|
|
84
|
+
source: 'tool',
|
|
85
|
+
});
|
|
86
|
+
await this.shutdown();
|
|
87
|
+
return {
|
|
88
|
+
success: false,
|
|
89
|
+
error: msg,
|
|
90
|
+
logs: this.logs,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (this.totalErrors >= this.totalErrorThreshold) {
|
|
94
|
+
const msg = `Total error threshold reached (${this.totalErrorThreshold}).`;
|
|
95
|
+
this.logs.push({
|
|
96
|
+
type: 'error',
|
|
97
|
+
message: msg,
|
|
98
|
+
timestamp: Date.now(),
|
|
99
|
+
source: 'tool',
|
|
100
|
+
});
|
|
101
|
+
await this.shutdown();
|
|
102
|
+
return {
|
|
103
|
+
success: false,
|
|
104
|
+
error: msg,
|
|
105
|
+
logs: this.logs,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
lastError = error;
|
|
109
|
+
const shouldRetry = retryOptions.retryOnErrors?.includes(error.name);
|
|
110
|
+
if (!shouldRetry || attempt === retryOptions.maxRetries) {
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
await new Promise((resolve) => setTimeout(resolve, retryOptions.retryDelay ?? 1000));
|
|
114
|
+
this.logs.push({
|
|
115
|
+
type: 'info',
|
|
116
|
+
message: `Retrying action (attempt ${attempt + 1}/${retryOptions.maxRetries})`,
|
|
117
|
+
timestamp: Date.now(),
|
|
118
|
+
category: 'retry',
|
|
119
|
+
source: 'tool',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
throw lastError;
|
|
124
|
+
}
|
|
125
|
+
async executeAction(action) {
|
|
126
|
+
try {
|
|
127
|
+
switch (action.type) {
|
|
128
|
+
case 'start':
|
|
129
|
+
await this.startBrowser(action);
|
|
130
|
+
break;
|
|
131
|
+
case 'navigate':
|
|
132
|
+
await this.navigate(action);
|
|
133
|
+
break;
|
|
134
|
+
case 'click':
|
|
135
|
+
console.log('Clicking has not been implemented yet');
|
|
136
|
+
break;
|
|
137
|
+
case 'type':
|
|
138
|
+
await this.typeText(action);
|
|
139
|
+
break;
|
|
140
|
+
case 'scroll':
|
|
141
|
+
await this.scroll(action);
|
|
142
|
+
break;
|
|
143
|
+
case 'screenshot':
|
|
144
|
+
return await this.takeScreenshot(action);
|
|
145
|
+
case 'stop':
|
|
146
|
+
await this.shutdown();
|
|
147
|
+
return {
|
|
148
|
+
success: true,
|
|
149
|
+
logs: this.logs,
|
|
150
|
+
metrics: await this.collectMetrics(),
|
|
151
|
+
};
|
|
152
|
+
default:
|
|
153
|
+
throw new Error(`Unknown action type: ${action.type}`);
|
|
154
|
+
}
|
|
155
|
+
const metrics = await this.collectMetrics();
|
|
156
|
+
const response = {
|
|
157
|
+
success: true,
|
|
158
|
+
logs: this.logs,
|
|
159
|
+
metrics,
|
|
160
|
+
};
|
|
161
|
+
return response;
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
await this.shutdown();
|
|
165
|
+
return {
|
|
166
|
+
success: false,
|
|
167
|
+
error: err?.message ?? String(err),
|
|
168
|
+
logs: this.logs,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
logErrorForAnalysis(error) {
|
|
173
|
+
// Add helpful hints based on error patterns
|
|
174
|
+
const errorPatterns = {
|
|
175
|
+
'not defined': 'Check for missing script dependencies or undefined variables',
|
|
176
|
+
'Failed to fetch': 'Verify endpoint URLs and network connectivity',
|
|
177
|
+
'404': 'Resource not found - verify URLs and paths',
|
|
178
|
+
SSL: 'SSL certificate error - check HTTPS configuration',
|
|
179
|
+
ERR_NAME_NOT_RESOLVED: 'DNS resolution failed - check domain name',
|
|
180
|
+
ERR_CONNECTION_TIMED_OUT: 'Connection timeout - check network or firewall',
|
|
181
|
+
ERR_NETWORK_CHANGED: 'Network changed during request - retry operation',
|
|
182
|
+
ERR_INTERNET_DISCONNECTED: 'No internet connection',
|
|
183
|
+
'Navigation timeout': 'Page took too long to load - check performance or timeouts',
|
|
184
|
+
WebSocket: 'WebSocket connection issue - check server status',
|
|
185
|
+
ERR_TUNNEL_CONNECTION_FAILED: 'Proxy or VPN connection issue',
|
|
186
|
+
ERR_CERT_: 'SSL/TLS certificate validation error',
|
|
187
|
+
ERR_BLOCKED_BY_CLIENT: 'Request blocked by browser extension or policy',
|
|
188
|
+
ERR_TOO_MANY_REDIRECTS: 'Redirect loop detected',
|
|
189
|
+
'Frame detached': 'Target frame or element no longer exists',
|
|
190
|
+
'Node is detached': 'Element was removed from DOM',
|
|
191
|
+
ERR_ABORTED: 'Request was aborted - possible navigation or reload',
|
|
192
|
+
ERR_CONTENT_LENGTH_MISMATCH: 'Incomplete response - check server stability',
|
|
193
|
+
ERR_RESPONSE_HEADERS_TRUNCATED: 'Response headers too large or malformed',
|
|
194
|
+
};
|
|
195
|
+
for (const [pattern, hint] of Object.entries(errorPatterns)) {
|
|
196
|
+
if (error.message.includes(pattern)) {
|
|
197
|
+
this.logs.push({
|
|
198
|
+
type: 'info',
|
|
199
|
+
message: `Hint: ${hint}`,
|
|
200
|
+
timestamp: Date.now(),
|
|
201
|
+
category: 'hint',
|
|
202
|
+
source: 'tool',
|
|
203
|
+
});
|
|
204
|
+
break; // Stop after first matching pattern
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
this.logs.push({
|
|
208
|
+
type: 'error',
|
|
209
|
+
message: `Action error: ${error.message}`,
|
|
210
|
+
timestamp: Date.now(),
|
|
211
|
+
stack: error.stack,
|
|
212
|
+
source: 'tool',
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
async startBrowser(action) {
|
|
216
|
+
if (this.browser) {
|
|
217
|
+
await this.shutdown();
|
|
218
|
+
}
|
|
219
|
+
// Set start time for session tracking
|
|
220
|
+
this.startTime = Date.now();
|
|
221
|
+
// Update session configuration
|
|
222
|
+
this.maxConsecutiveErrors =
|
|
223
|
+
action.maxConsecutiveErrors ?? browser_actions_1.BROWSER_DEFAULTS.maxConsecutiveErrors;
|
|
224
|
+
this.totalErrorThreshold =
|
|
225
|
+
action.totalErrorThreshold ?? browser_actions_1.BROWSER_DEFAULTS.totalErrorThreshold;
|
|
226
|
+
// Reset error counters
|
|
227
|
+
this.consecutiveErrors = 0;
|
|
228
|
+
this.totalErrors = 0;
|
|
229
|
+
// Set up user data directory for profile persistence, scoped to current project
|
|
230
|
+
let userDataDir = undefined;
|
|
231
|
+
try {
|
|
232
|
+
userDataDir = path.join((0, project_files_1.getProjectDataDir)(), browser_actions_1.BROWSER_DEFAULTS.userDataDir);
|
|
233
|
+
(0, file_1.ensureDirectoryExists)(userDataDir);
|
|
234
|
+
}
|
|
235
|
+
catch (error) { }
|
|
236
|
+
try {
|
|
237
|
+
this.browser = await puppeteer_1.default.launch({
|
|
238
|
+
defaultViewport: { width: 1280, height: 720 },
|
|
239
|
+
headless: browser_actions_1.BROWSER_DEFAULTS.headless,
|
|
240
|
+
userDataDir,
|
|
241
|
+
args: ['--no-sandbox', '--restore-last-session=false'],
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
// If launch fails, try installing/updating Chrome and retry
|
|
246
|
+
console.log('Browser launch failed, attempting to install/update Chrome...');
|
|
247
|
+
(0, child_process_1.execSync)('npx puppeteer browsers install chrome', { stdio: 'inherit' });
|
|
248
|
+
this.browser = await puppeteer_1.default.launch({
|
|
249
|
+
defaultViewport: { width: 1280, height: 720 },
|
|
250
|
+
headless: browser_actions_1.BROWSER_DEFAULTS.headless,
|
|
251
|
+
userDataDir,
|
|
252
|
+
args: ['--no-sandbox', '--restore-last-session=false'],
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
this.logs.push({
|
|
256
|
+
type: 'info',
|
|
257
|
+
message: 'Browser started',
|
|
258
|
+
timestamp: Date.now(),
|
|
259
|
+
source: 'tool',
|
|
260
|
+
});
|
|
261
|
+
const pages = await this.browser.pages();
|
|
262
|
+
this.page = pages.length > 0 ? pages[0] : await this.browser.newPage();
|
|
263
|
+
this.attachPageListeners();
|
|
264
|
+
return {
|
|
265
|
+
success: true,
|
|
266
|
+
logs: [
|
|
267
|
+
{
|
|
268
|
+
type: 'info',
|
|
269
|
+
message: `Opened ${action.url} in browser`,
|
|
270
|
+
timestamp: Date.now(),
|
|
271
|
+
source: 'tool',
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
networkEvents: [],
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
async navigate(action) {
|
|
278
|
+
// If headless state needs to change, restart browser
|
|
279
|
+
if (!this.browser) {
|
|
280
|
+
await this.startBrowser({
|
|
281
|
+
...action,
|
|
282
|
+
type: 'start',
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (!this.page)
|
|
286
|
+
throw new Error('No browser page found; call start first.');
|
|
287
|
+
try {
|
|
288
|
+
await this.page.goto(action.url, {
|
|
289
|
+
waitUntil: action.waitUntil ?? browser_actions_1.BROWSER_DEFAULTS.waitUntil,
|
|
290
|
+
timeout: action.timeout ?? browser_actions_1.BROWSER_DEFAULTS.timeout,
|
|
291
|
+
});
|
|
292
|
+
this.logs.push({
|
|
293
|
+
type: 'info',
|
|
294
|
+
message: `Navigated to ${action.url}`,
|
|
295
|
+
timestamp: Date.now(),
|
|
296
|
+
source: 'tool',
|
|
297
|
+
});
|
|
298
|
+
// Add a small delay after navigation to ensure page is stable
|
|
299
|
+
await (0, promise_1.sleep)(1000);
|
|
300
|
+
return {
|
|
301
|
+
success: true,
|
|
302
|
+
logs: this.logs,
|
|
303
|
+
networkEvents: [],
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
// Explicitly type as any since we know we want to access .message
|
|
308
|
+
const errorMessage = error?.message || 'Unknown navigation error';
|
|
309
|
+
this.logs.push({
|
|
310
|
+
type: 'error',
|
|
311
|
+
message: `Navigation failed: ${errorMessage}`,
|
|
312
|
+
timestamp: Date.now(),
|
|
313
|
+
source: 'tool',
|
|
314
|
+
});
|
|
315
|
+
return {
|
|
316
|
+
success: false,
|
|
317
|
+
error: errorMessage,
|
|
318
|
+
logs: this.logs,
|
|
319
|
+
networkEvents: [],
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
async typeText(action) {
|
|
324
|
+
if (!this.page)
|
|
325
|
+
throw new Error('No browser page found; call start first.');
|
|
326
|
+
await this.page.type(action.selector, action.text, {
|
|
327
|
+
delay: action.delay ?? browser_actions_1.BROWSER_DEFAULTS.delay,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
async scroll(action) {
|
|
331
|
+
if (!this.page)
|
|
332
|
+
throw new Error('No browser page found; call start first.');
|
|
333
|
+
// Get viewport height
|
|
334
|
+
const viewport = this.page.viewport();
|
|
335
|
+
if (!viewport)
|
|
336
|
+
throw new Error('No viewport found');
|
|
337
|
+
// Default to scrolling down if no direction specified
|
|
338
|
+
const direction = action.direction ?? 'down';
|
|
339
|
+
const scrollAmount = direction === 'up' ? -viewport.height : viewport.height;
|
|
340
|
+
await this.page.evaluate((amount) => {
|
|
341
|
+
window.scrollBy(0, amount);
|
|
342
|
+
}, scrollAmount);
|
|
343
|
+
this.logs.push({
|
|
344
|
+
type: 'info',
|
|
345
|
+
message: `Scrolled ${direction}`,
|
|
346
|
+
timestamp: Date.now(),
|
|
347
|
+
source: 'tool',
|
|
348
|
+
});
|
|
349
|
+
return {
|
|
350
|
+
success: true,
|
|
351
|
+
logs: [],
|
|
352
|
+
networkEvents: [],
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
async takeScreenshot(action) {
|
|
356
|
+
if (!this.page)
|
|
357
|
+
throw new Error('No browser page found; call start first.');
|
|
358
|
+
// Take a screenshot with aggressive compression settings
|
|
359
|
+
const screenshot = await this.page.screenshot({
|
|
360
|
+
fullPage: browser_actions_1.BROWSER_DEFAULTS.fullPage, // action.fullPage ?? BROWSER_DEFAULTS.fullPage,
|
|
361
|
+
type: 'jpeg',
|
|
362
|
+
quality: action.screenshotCompressionQuality ??
|
|
363
|
+
browser_actions_1.BROWSER_DEFAULTS.screenshotCompressionQuality,
|
|
364
|
+
encoding: 'base64',
|
|
365
|
+
});
|
|
366
|
+
// Log screenshot capture and size
|
|
367
|
+
const sizeInKB = Math.round((screenshot.length * 3) / 4 / 1024);
|
|
368
|
+
this.logs.push({
|
|
369
|
+
type: 'info',
|
|
370
|
+
message: `Captured screenshot (${sizeInKB}KB)`,
|
|
371
|
+
timestamp: Date.now(),
|
|
372
|
+
category: 'screenshot',
|
|
373
|
+
source: 'tool',
|
|
374
|
+
});
|
|
375
|
+
// Format screenshot in Anthropic's image content format
|
|
376
|
+
const imageContent = {
|
|
377
|
+
type: 'image',
|
|
378
|
+
source: {
|
|
379
|
+
type: 'base64',
|
|
380
|
+
media_type: 'image/jpeg',
|
|
381
|
+
data: screenshot,
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
// If debug mode is enabled, save the screenshot
|
|
385
|
+
if (action.debug) {
|
|
386
|
+
console.debug({
|
|
387
|
+
message: 'Saving screenshot to disk...',
|
|
388
|
+
timestamp: Date.now(),
|
|
389
|
+
source: 'tool',
|
|
390
|
+
});
|
|
391
|
+
try {
|
|
392
|
+
const chatDir = (0, project_files_1.getCurrentChatDir)();
|
|
393
|
+
const screenshotsDir = path.join(chatDir, 'screenshots');
|
|
394
|
+
(0, file_1.ensureDirectoryExists)(screenshotsDir);
|
|
395
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
396
|
+
const filename = `screenshot-${timestamp}.jpg`;
|
|
397
|
+
const filepath = path.join(screenshotsDir, filename);
|
|
398
|
+
fs.writeFileSync(filepath, Buffer.from(screenshot, 'base64'));
|
|
399
|
+
console.debug({
|
|
400
|
+
type: 'debug',
|
|
401
|
+
message: `Saved screenshot to ${filepath}`,
|
|
402
|
+
timestamp: Date.now(),
|
|
403
|
+
source: 'tool',
|
|
404
|
+
});
|
|
405
|
+
// Save metadata
|
|
406
|
+
const metadataPath = path.join(screenshotsDir, `${timestamp}-metadata.json`);
|
|
407
|
+
const metadata = {
|
|
408
|
+
timestamp,
|
|
409
|
+
format: 'jpeg',
|
|
410
|
+
quality: 25,
|
|
411
|
+
fullPage: action.fullPage ?? browser_actions_1.BROWSER_DEFAULTS.fullPage,
|
|
412
|
+
metrics: await this.collectMetrics(),
|
|
413
|
+
};
|
|
414
|
+
fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
console.error({
|
|
418
|
+
message: `Failed to save screenshot: ${error.message}`,
|
|
419
|
+
timestamp: Date.now(),
|
|
420
|
+
source: 'tool',
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const metrics = await this.collectMetrics();
|
|
425
|
+
return {
|
|
426
|
+
success: true,
|
|
427
|
+
logs: this.logs,
|
|
428
|
+
screenshot: imageContent,
|
|
429
|
+
metrics,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
attachPageListeners() {
|
|
433
|
+
if (!this.page)
|
|
434
|
+
return;
|
|
435
|
+
// Console messages
|
|
436
|
+
this.page.on('console', (msg) => {
|
|
437
|
+
const type = msg.type() === 'error' ? 'error' : msg.type();
|
|
438
|
+
this.logs.push({
|
|
439
|
+
type,
|
|
440
|
+
message: msg.text(),
|
|
441
|
+
timestamp: Date.now(),
|
|
442
|
+
source: 'browser',
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
// Page errors
|
|
446
|
+
this.page.on('pageerror', (err) => {
|
|
447
|
+
this.logs.push({
|
|
448
|
+
type: 'error',
|
|
449
|
+
message: err.message,
|
|
450
|
+
timestamp: Date.now(),
|
|
451
|
+
stack: err.stack,
|
|
452
|
+
source: 'browser',
|
|
453
|
+
});
|
|
454
|
+
this.jsErrorCount++;
|
|
455
|
+
});
|
|
456
|
+
// Network requests
|
|
457
|
+
this.page.on('request', (request) => {
|
|
458
|
+
const method = request.method();
|
|
459
|
+
if (method) {
|
|
460
|
+
this.networkEvents.push({
|
|
461
|
+
url: request.url(),
|
|
462
|
+
method,
|
|
463
|
+
timestamp: Date.now(),
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
// Network responses
|
|
468
|
+
this.page.on('response', async (response) => {
|
|
469
|
+
const req = response.request();
|
|
470
|
+
const index = this.networkEvents.findIndex((evt) => evt.url === req.url() && evt.method === req.method());
|
|
471
|
+
const status = response.status();
|
|
472
|
+
const errorText = status >= 400 ? await response.text().catch(() => '') : undefined;
|
|
473
|
+
if (index !== -1) {
|
|
474
|
+
this.networkEvents[index].status = status;
|
|
475
|
+
this.networkEvents[index].errorText = errorText;
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
const method = req.method();
|
|
479
|
+
if (method) {
|
|
480
|
+
this.networkEvents.push({
|
|
481
|
+
url: req.url(),
|
|
482
|
+
method,
|
|
483
|
+
status,
|
|
484
|
+
errorText,
|
|
485
|
+
timestamp: Date.now(),
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
// Log network errors
|
|
490
|
+
if (status >= 400) {
|
|
491
|
+
this.logs.push({
|
|
492
|
+
type: 'error',
|
|
493
|
+
message: `Network error ${status} for ${req.url()}`,
|
|
494
|
+
timestamp: Date.now(),
|
|
495
|
+
source: 'tool',
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
async collectPerformanceMetrics() {
|
|
501
|
+
if (!this.page)
|
|
502
|
+
return;
|
|
503
|
+
// Collect Web Vitals and other performance metrics
|
|
504
|
+
const metrics = await this.page.evaluate(() => {
|
|
505
|
+
const lcpEntry = performance.getEntriesByType('largest-contentful-paint')[0];
|
|
506
|
+
const navEntry = performance.getEntriesByType('navigation')[0];
|
|
507
|
+
const fcpEntry = performance
|
|
508
|
+
.getEntriesByType('paint')
|
|
509
|
+
.find((entry) => entry.name === 'first-contentful-paint');
|
|
510
|
+
return {
|
|
511
|
+
ttfb: navEntry?.responseStart - navEntry?.requestStart,
|
|
512
|
+
lcp: lcpEntry?.startTime,
|
|
513
|
+
fcp: fcpEntry?.startTime,
|
|
514
|
+
domContentLoaded: navEntry?.domContentLoadedEventEnd - navEntry?.startTime,
|
|
515
|
+
};
|
|
516
|
+
});
|
|
517
|
+
this.performanceMetrics = metrics;
|
|
518
|
+
}
|
|
519
|
+
async collectMetrics() {
|
|
520
|
+
if (!this.page)
|
|
521
|
+
return undefined;
|
|
522
|
+
const perfEntries = JSON.parse(await this.page.evaluate(() => JSON.stringify(performance.getEntriesByType('navigation'))));
|
|
523
|
+
let loadTime = 0;
|
|
524
|
+
if (perfEntries && perfEntries.length > 0) {
|
|
525
|
+
const navTiming = perfEntries[0];
|
|
526
|
+
loadTime = navTiming.loadEventEnd - navTiming.startTime;
|
|
527
|
+
}
|
|
528
|
+
const memoryUsed = await this.page
|
|
529
|
+
.metrics()
|
|
530
|
+
.then((m) => m.JSHeapUsedSize || 0);
|
|
531
|
+
await this.collectPerformanceMetrics();
|
|
532
|
+
return {
|
|
533
|
+
loadTime,
|
|
534
|
+
memoryUsage: memoryUsed,
|
|
535
|
+
jsErrors: this.jsErrorCount,
|
|
536
|
+
networkErrors: this.networkEvents.filter((e) => e.status && e.status >= 400).length,
|
|
537
|
+
ttfb: this.performanceMetrics.ttfb,
|
|
538
|
+
lcp: this.performanceMetrics.lcp,
|
|
539
|
+
fcp: this.performanceMetrics.fcp,
|
|
540
|
+
domContentLoaded: this.performanceMetrics.domContentLoaded,
|
|
541
|
+
sessionDuration: Date.now() - this.startTime,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
filterLogs(logs, filter) {
|
|
545
|
+
// First deduplicate logs
|
|
546
|
+
const seen = new Set();
|
|
547
|
+
logs = logs.filter((log) => {
|
|
548
|
+
const key = `${log.type}|${log.message}|${log.timestamp}|${log.source}`;
|
|
549
|
+
if (seen.has(key)) {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
seen.add(key);
|
|
553
|
+
return true;
|
|
554
|
+
});
|
|
555
|
+
// Then apply any filters
|
|
556
|
+
if (!filter)
|
|
557
|
+
return logs;
|
|
558
|
+
return logs.filter((log) => {
|
|
559
|
+
if (filter.types && !filter.types.includes(log.type))
|
|
560
|
+
return false;
|
|
561
|
+
if (filter.minLevel && log.level && log.level < filter.minLevel)
|
|
562
|
+
return false;
|
|
563
|
+
if (filter.categories &&
|
|
564
|
+
log.category &&
|
|
565
|
+
!filter.categories.includes(log.category))
|
|
566
|
+
return false;
|
|
567
|
+
return true;
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
async execute(action) {
|
|
571
|
+
try {
|
|
572
|
+
const response = await this.executeWithRetry(action);
|
|
573
|
+
// Filter and deduplicate logs
|
|
574
|
+
response.logs = this.filterLogs(response.logs, action.logFilter ?? undefined);
|
|
575
|
+
this.logs = []; // Clear logs after sending them in response
|
|
576
|
+
return response;
|
|
577
|
+
}
|
|
578
|
+
catch (error) {
|
|
579
|
+
if (error.name === 'TargetClosedError') {
|
|
580
|
+
this.logs.push({
|
|
581
|
+
type: 'error',
|
|
582
|
+
message: 'Browser crashed or was closed unexpectedly. Attempting recovery...',
|
|
583
|
+
timestamp: Date.now(),
|
|
584
|
+
category: 'browser',
|
|
585
|
+
source: 'tool',
|
|
586
|
+
});
|
|
587
|
+
// Try to recover by restarting browser
|
|
588
|
+
await this.shutdown();
|
|
589
|
+
if (action.type !== 'stop') {
|
|
590
|
+
await this.startBrowser({
|
|
591
|
+
type: 'start',
|
|
592
|
+
url: 'about:blank',
|
|
593
|
+
timeout: 15000,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
throw error;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
async shutdown() {
|
|
601
|
+
const browser = this.browser;
|
|
602
|
+
if (browser) {
|
|
603
|
+
// Clear references first to prevent double shutdown
|
|
604
|
+
this.browser = null;
|
|
605
|
+
this.page = null;
|
|
606
|
+
try {
|
|
607
|
+
await browser.close();
|
|
608
|
+
}
|
|
609
|
+
catch (err) {
|
|
610
|
+
console.error('Error closing browser:', err);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
exports.BrowserRunner = BrowserRunner;
|
|
616
|
+
const handleBrowserInstruction = async (action) => {
|
|
617
|
+
const response = await exports.activeBrowserRunner.execute(action);
|
|
618
|
+
return response;
|
|
619
|
+
};
|
|
620
|
+
exports.handleBrowserInstruction = handleBrowserInstruction;
|
|
621
|
+
exports.activeBrowserRunner = new BrowserRunner();
|
|
622
|
+
//# sourceMappingURL=browser-runner.js.map
|