@loxia-labs/loxia-autopilot-one 1.0.1 → 1.0.4
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/README.md +44 -54
- package/bin/cli.js +1 -115
- package/bin/loxia-terminal-v2.js +3 -0
- package/bin/loxia-terminal.js +3 -0
- package/bin/start-with-terminal.js +3 -0
- package/package.json +15 -15
- package/scripts/install-scanners.js +1 -235
- package/src/analyzers/CSSAnalyzer.js +1 -297
- package/src/analyzers/ConfigValidator.js +1 -690
- package/src/analyzers/ESLintAnalyzer.js +1 -320
- package/src/analyzers/JavaScriptAnalyzer.js +1 -261
- package/src/analyzers/PrettierFormatter.js +1 -247
- package/src/analyzers/PythonAnalyzer.js +1 -266
- package/src/analyzers/SecurityAnalyzer.js +1 -729
- package/src/analyzers/TypeScriptAnalyzer.js +1 -247
- package/src/analyzers/codeCloneDetector/analyzer.js +1 -344
- package/src/analyzers/codeCloneDetector/detector.js +1 -203
- package/src/analyzers/codeCloneDetector/index.js +1 -160
- package/src/analyzers/codeCloneDetector/parser.js +1 -199
- package/src/analyzers/codeCloneDetector/reporter.js +1 -148
- package/src/analyzers/codeCloneDetector/scanner.js +1 -59
- package/src/core/agentPool.js +1 -1474
- package/src/core/agentScheduler.js +1 -2147
- package/src/core/contextManager.js +1 -709
- package/src/core/messageProcessor.js +1 -732
- package/src/core/orchestrator.js +1 -548
- package/src/core/stateManager.js +1 -877
- package/src/index.js +1 -631
- package/src/interfaces/cli.js +1 -549
- package/src/interfaces/terminal/__tests__/smoke/advancedFeatures.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/agentControl.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/agents.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/components.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/connection.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/imports.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/messages.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/tools.test.js +1 -0
- package/src/interfaces/terminal/api/apiClient.js +1 -0
- package/src/interfaces/terminal/api/messageRouter.js +1 -0
- package/src/interfaces/terminal/api/session.js +1 -0
- package/src/interfaces/terminal/api/websocket.js +1 -0
- package/src/interfaces/terminal/components/AgentCreator.js +1 -0
- package/src/interfaces/terminal/components/AgentEditor.js +1 -0
- package/src/interfaces/terminal/components/AgentSwitcher.js +1 -0
- package/src/interfaces/terminal/components/ErrorBoundary.js +1 -0
- package/src/interfaces/terminal/components/ErrorPanel.js +1 -0
- package/src/interfaces/terminal/components/Header.js +1 -0
- package/src/interfaces/terminal/components/HelpPanel.js +1 -0
- package/src/interfaces/terminal/components/InputBox.js +1 -0
- package/src/interfaces/terminal/components/Layout.js +1 -0
- package/src/interfaces/terminal/components/LoadingSpinner.js +1 -0
- package/src/interfaces/terminal/components/MessageList.js +1 -0
- package/src/interfaces/terminal/components/MultilineTextInput.js +1 -0
- package/src/interfaces/terminal/components/SearchPanel.js +1 -0
- package/src/interfaces/terminal/components/SettingsPanel.js +1 -0
- package/src/interfaces/terminal/components/StatusBar.js +1 -0
- package/src/interfaces/terminal/components/TextInput.js +1 -0
- package/src/interfaces/terminal/config/agentEditorConstants.js +1 -0
- package/src/interfaces/terminal/config/constants.js +1 -0
- package/src/interfaces/terminal/index.js +1 -0
- package/src/interfaces/terminal/state/useAgentControl.js +1 -0
- package/src/interfaces/terminal/state/useAgents.js +1 -0
- package/src/interfaces/terminal/state/useConnection.js +1 -0
- package/src/interfaces/terminal/state/useMessages.js +1 -0
- package/src/interfaces/terminal/state/useTools.js +1 -0
- package/src/interfaces/terminal/utils/debugLogger.js +1 -0
- package/src/interfaces/terminal/utils/settingsStorage.js +1 -0
- package/src/interfaces/terminal/utils/theme.js +1 -0
- package/src/interfaces/webServer.js +1 -2162
- package/src/modules/fileExplorer/controller.js +1 -280
- package/src/modules/fileExplorer/index.js +1 -37
- package/src/modules/fileExplorer/middleware.js +1 -92
- package/src/modules/fileExplorer/routes.js +1 -125
- package/src/modules/fileExplorer/types.js +1 -44
- package/src/services/aiService.js +1 -1232
- package/src/services/apiKeyManager.js +1 -164
- package/src/services/benchmarkService.js +1 -366
- package/src/services/budgetService.js +1 -539
- package/src/services/contextInjectionService.js +1 -247
- package/src/services/conversationCompactionService.js +1 -637
- package/src/services/errorHandler.js +1 -810
- package/src/services/fileAttachmentService.js +1 -544
- package/src/services/modelRouterService.js +1 -366
- package/src/services/modelsService.js +1 -322
- package/src/services/qualityInspector.js +1 -796
- package/src/services/tokenCountingService.js +1 -536
- package/src/tools/agentCommunicationTool.js +1 -1344
- package/src/tools/agentDelayTool.js +1 -485
- package/src/tools/asyncToolManager.js +1 -604
- package/src/tools/baseTool.js +1 -800
- package/src/tools/browserTool.js +1 -920
- package/src/tools/cloneDetectionTool.js +1 -621
- package/src/tools/dependencyResolverTool.js +1 -1215
- package/src/tools/fileContentReplaceTool.js +1 -875
- package/src/tools/fileSystemTool.js +1 -1107
- package/src/tools/fileTreeTool.js +1 -853
- package/src/tools/imageTool.js +1 -901
- package/src/tools/importAnalyzerTool.js +1 -1060
- package/src/tools/jobDoneTool.js +1 -248
- package/src/tools/seekTool.js +1 -956
- package/src/tools/staticAnalysisTool.js +1 -1778
- package/src/tools/taskManagerTool.js +1 -2873
- package/src/tools/terminalTool.js +1 -2304
- package/src/tools/webTool.js +1 -1430
- package/src/types/agent.js +1 -519
- package/src/types/contextReference.js +1 -972
- package/src/types/conversation.js +1 -730
- package/src/types/toolCommand.js +1 -747
- package/src/utilities/attachmentValidator.js +1 -292
- package/src/utilities/configManager.js +1 -582
- package/src/utilities/constants.js +1 -722
- package/src/utilities/directoryAccessManager.js +1 -535
- package/src/utilities/fileProcessor.js +1 -307
- package/src/utilities/logger.js +1 -436
- package/src/utilities/tagParser.js +1 -1246
- package/src/utilities/toolConstants.js +1 -317
- package/web-ui/build/index.html +2 -2
- package/web-ui/build/static/{index-Dy2bYbOa.css → index-CClD1090.css} +1 -1
- package/web-ui/build/static/{index-CjkkcnFA.js → index-lCBai6dX.js} +66 -67
package/src/tools/webTool.js
CHANGED
|
@@ -1,1430 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WebTool - Web browsing and automation with Puppeteer
|
|
3
|
-
*
|
|
4
|
-
* Purpose:
|
|
5
|
-
* - Search the web using known search engines
|
|
6
|
-
* - Fetch web content in various formats
|
|
7
|
-
* - Interactive browser automation with command chaining
|
|
8
|
-
* - Tab management with agent isolation
|
|
9
|
-
* - Screenshot capture and AI-powered analysis
|
|
10
|
-
* - Mouse and keyboard event simulation
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { BaseTool } from './baseTool.js';
|
|
14
|
-
import TagParser from '../utilities/tagParser.js';
|
|
15
|
-
import puppeteer from 'puppeteer';
|
|
16
|
-
import path from 'path';
|
|
17
|
-
import fs from 'fs/promises';
|
|
18
|
-
import os from 'os';
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
TOOL_STATUS,
|
|
22
|
-
SYSTEM_DEFAULTS
|
|
23
|
-
} from '../utilities/constants.js';
|
|
24
|
-
|
|
25
|
-
class WebTool extends BaseTool {
|
|
26
|
-
constructor(config = {}, logger = null) {
|
|
27
|
-
super(config, logger);
|
|
28
|
-
|
|
29
|
-
// Tool metadata
|
|
30
|
-
this.requiresProject = false;
|
|
31
|
-
this.isAsync = true;
|
|
32
|
-
|
|
33
|
-
// Browser instance (singleton per system)
|
|
34
|
-
this.browser = null;
|
|
35
|
-
this.browserInitializing = false;
|
|
36
|
-
|
|
37
|
-
// Tab tracking: Map<agentId, Map<tabName, tabInfo>>
|
|
38
|
-
this.agentTabs = new Map();
|
|
39
|
-
|
|
40
|
-
// Known search engines
|
|
41
|
-
this.searchEngines = [
|
|
42
|
-
{
|
|
43
|
-
name: 'google',
|
|
44
|
-
url: 'https://www.google.com/search?q=',
|
|
45
|
-
searchSelector: 'input[name="q"]',
|
|
46
|
-
submitSelector: 'input[type="submit"], button[type="submit"]',
|
|
47
|
-
resultsSelector: '#search .g a, #search a[href]',
|
|
48
|
-
waitSelector: '#search'
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
name: 'bing',
|
|
52
|
-
url: 'https://www.bing.com/search?q=',
|
|
53
|
-
searchSelector: 'input[name="q"]',
|
|
54
|
-
submitSelector: 'input[type="submit"]',
|
|
55
|
-
resultsSelector: '.b_algo a',
|
|
56
|
-
waitSelector: '#b_results'
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
name: 'duckduckgo',
|
|
60
|
-
url: 'https://duckduckgo.com/?q=',
|
|
61
|
-
searchSelector: 'input[name="q"]',
|
|
62
|
-
submitSelector: 'button[type="submit"]',
|
|
63
|
-
resultsSelector: '.result__a, a[data-testid="result-title-a"]',
|
|
64
|
-
waitSelector: '#links, [data-testid="mainline"]'
|
|
65
|
-
}
|
|
66
|
-
];
|
|
67
|
-
|
|
68
|
-
// Configuration
|
|
69
|
-
this.TAB_IDLE_TIMEOUT = config.tabIdleTimeout || 60 * 60 * 1000; // 1 hour
|
|
70
|
-
this.CLEANUP_INTERVAL = config.cleanupInterval || 5 * 60 * 1000; // 5 minutes
|
|
71
|
-
this.DEFAULT_TIMEOUT = config.defaultTimeout || 60000; // 60 seconds
|
|
72
|
-
this.TEMP_DIR = config.tempDir || path.join(os.tmpdir(), 'webtool-screenshots');
|
|
73
|
-
|
|
74
|
-
// Start cleanup timer
|
|
75
|
-
this.cleanupTimer = null;
|
|
76
|
-
this.startCleanupTimer();
|
|
77
|
-
|
|
78
|
-
// Ensure temp directory exists
|
|
79
|
-
this.ensureTempDir();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Get tool description for LLM consumption
|
|
84
|
-
* @returns {string} Tool description
|
|
85
|
-
*/
|
|
86
|
-
getDescription() {
|
|
87
|
-
return `
|
|
88
|
-
Web Tool: Browse, search, and automate web interactions using a real browser (Puppeteer).
|
|
89
|
-
|
|
90
|
-
IMPORTANT: This tool supports command chaining - nest multiple actions together to execute them sequentially without waiting for responses between each action.
|
|
91
|
-
|
|
92
|
-
USAGE:
|
|
93
|
-
[tool id="web"]
|
|
94
|
-
<operation>search|fetch|interactive</operation>
|
|
95
|
-
<!-- Operation-specific content -->
|
|
96
|
-
[/tool]
|
|
97
|
-
|
|
98
|
-
ALTERNATIVE JSON FORMAT:
|
|
99
|
-
\`\`\`json
|
|
100
|
-
{
|
|
101
|
-
"toolId": "web",
|
|
102
|
-
"operation": "search|fetch|interactive",
|
|
103
|
-
"parameters": { ... }
|
|
104
|
-
}
|
|
105
|
-
\`\`\`
|
|
106
|
-
|
|
107
|
-
═══════════════════════════════════════════════════════════════
|
|
108
|
-
OPERATION 1: SEARCH THE WEB
|
|
109
|
-
═══════════════════════════════════════════════════════════════
|
|
110
|
-
|
|
111
|
-
Search using real browsers on known search engines (Google, Bing, DuckDuckGo).
|
|
112
|
-
|
|
113
|
-
XML SYNTAX:
|
|
114
|
-
[tool id="web"]
|
|
115
|
-
<operation>search</operation>
|
|
116
|
-
<query>puppeteer web scraping tutorial</query>
|
|
117
|
-
<engine>google</engine> <!-- Optional: google|bing|duckduckgo, default: google -->
|
|
118
|
-
<max-results>10</max-results> <!-- Optional, default: 10 -->
|
|
119
|
-
[/tool]
|
|
120
|
-
|
|
121
|
-
JSON SYNTAX:
|
|
122
|
-
\`\`\`json
|
|
123
|
-
{
|
|
124
|
-
"toolId": "web",
|
|
125
|
-
"operation": "search",
|
|
126
|
-
"query": "puppeteer web scraping tutorial",
|
|
127
|
-
"engine": "google",
|
|
128
|
-
"maxResults": 10
|
|
129
|
-
}
|
|
130
|
-
\`\`\`
|
|
131
|
-
|
|
132
|
-
OUTPUT: List of URLs with titles and descriptions
|
|
133
|
-
|
|
134
|
-
═══════════════════════════════════════════════════════════════
|
|
135
|
-
OPERATION 2: FETCH WEB CONTENT
|
|
136
|
-
═══════════════════════════════════════════════════════════════
|
|
137
|
-
|
|
138
|
-
Fetch content from a URL in various formats.
|
|
139
|
-
|
|
140
|
-
XML SYNTAX:
|
|
141
|
-
[tool id="web"]
|
|
142
|
-
<operation>fetch</operation>
|
|
143
|
-
<url>https://example.com</url>
|
|
144
|
-
<format>title,text,links</format> <!-- Options: title|text|links|html|console -->
|
|
145
|
-
[/tool]
|
|
146
|
-
|
|
147
|
-
JSON SYNTAX:
|
|
148
|
-
\`\`\`json
|
|
149
|
-
{
|
|
150
|
-
"toolId": "web",
|
|
151
|
-
"operation": "fetch",
|
|
152
|
-
"url": "https://example.com",
|
|
153
|
-
"formats": ["title", "text", "links", "html", "console"]
|
|
154
|
-
}
|
|
155
|
-
\`\`\`
|
|
156
|
-
|
|
157
|
-
FORMAT OPTIONS:
|
|
158
|
-
- title: Page title
|
|
159
|
-
- text: Plain text content (no HTML)
|
|
160
|
-
- links: All links on page
|
|
161
|
-
- html: Full HTML source
|
|
162
|
-
- console: Browser console messages
|
|
163
|
-
|
|
164
|
-
OUTPUT: Object with requested content formats
|
|
165
|
-
|
|
166
|
-
═══════════════════════════════════════════════════════════════
|
|
167
|
-
OPERATION 3: INTERACTIVE BROWSER AUTOMATION
|
|
168
|
-
═══════════════════════════════════════════════════════════════
|
|
169
|
-
|
|
170
|
-
Control a real browser with command chaining for complex workflows.
|
|
171
|
-
|
|
172
|
-
XML SYNTAX (RECOMMENDED FOR CHAINING):
|
|
173
|
-
[tool id="web"]
|
|
174
|
-
<operation>interactive</operation>
|
|
175
|
-
<headless>true</headless> <!-- true|false, default: true -->
|
|
176
|
-
<actions>
|
|
177
|
-
<open-tab name="search">
|
|
178
|
-
<navigate>https://github.com/trending</navigate>
|
|
179
|
-
<wait-for selector=".Box-row" timeout="5000" />
|
|
180
|
-
<click selector=".Box-row:first-child a" />
|
|
181
|
-
<wait-for selector="#readme" />
|
|
182
|
-
<screenshot format="file" path="readme.png" />
|
|
183
|
-
<analyze-screenshot>What is the main topic of this README?</analyze-screenshot>
|
|
184
|
-
<extract-text selector="#readme" />
|
|
185
|
-
<get-source />
|
|
186
|
-
</open-tab>
|
|
187
|
-
<open-tab name="docs">
|
|
188
|
-
<navigate>https://docs.example.com</navigate>
|
|
189
|
-
<type selector="input.search" text="installation" />
|
|
190
|
-
<press key="Enter" />
|
|
191
|
-
<extract-links selector="a.doc-link" />
|
|
192
|
-
</open-tab>
|
|
193
|
-
<list-tabs />
|
|
194
|
-
<close-tab name="search" />
|
|
195
|
-
</actions>
|
|
196
|
-
[/tool]
|
|
197
|
-
|
|
198
|
-
JSON SYNTAX (ALTERNATIVE):
|
|
199
|
-
\`\`\`json
|
|
200
|
-
{
|
|
201
|
-
"toolId": "web",
|
|
202
|
-
"operation": "interactive",
|
|
203
|
-
"headless": true,
|
|
204
|
-
"actions": [
|
|
205
|
-
{
|
|
206
|
-
"type": "open-tab",
|
|
207
|
-
"name": "search",
|
|
208
|
-
"url": "https://github.com/trending",
|
|
209
|
-
"nestedActions": [
|
|
210
|
-
{"type": "wait-for", "selector": ".Box-row", "timeout": 5000},
|
|
211
|
-
{"type": "click", "selector": ".Box-row:first-child a"},
|
|
212
|
-
{"type": "screenshot", "format": "base64"},
|
|
213
|
-
{"type": "extract-text", "selector": "#readme"}
|
|
214
|
-
]
|
|
215
|
-
},
|
|
216
|
-
{"type": "list-tabs"},
|
|
217
|
-
{"type": "close-tab", "name": "search"}
|
|
218
|
-
]
|
|
219
|
-
}
|
|
220
|
-
\`\`\`
|
|
221
|
-
|
|
222
|
-
SUPPORTED ACTIONS:
|
|
223
|
-
- open-tab: Open new tab with nested actions
|
|
224
|
-
- close-tab: Close specific tab
|
|
225
|
-
- switch-tab: Switch to existing tab
|
|
226
|
-
- list-tabs: List all active tabs for this agent
|
|
227
|
-
- navigate: Go to URL
|
|
228
|
-
- click: Click element (left|right|middle)
|
|
229
|
-
- type: Type text into element
|
|
230
|
-
- press: Press keyboard key
|
|
231
|
-
- wait-for: Wait for element to appear
|
|
232
|
-
- screenshot: Capture screenshot (file|base64)
|
|
233
|
-
- analyze-screenshot: AI analysis of current page
|
|
234
|
-
- extract-text: Extract text from selector
|
|
235
|
-
- extract-links: Extract all links
|
|
236
|
-
- get-source: Get HTML source
|
|
237
|
-
- get-console: Get console messages
|
|
238
|
-
- scroll: Scroll page
|
|
239
|
-
- hover: Hover over element
|
|
240
|
-
|
|
241
|
-
MOUSE EVENTS:
|
|
242
|
-
<click selector=".button" button="left" /> <!-- left|right|middle -->
|
|
243
|
-
<hover selector=".menu" />
|
|
244
|
-
<mouse-move selector=".element" />
|
|
245
|
-
|
|
246
|
-
KEYBOARD EVENTS:
|
|
247
|
-
<type selector="input" text="Hello World" />
|
|
248
|
-
<press key="Enter" />
|
|
249
|
-
<press key="Control+C" /> <!-- Supports modifier keys -->
|
|
250
|
-
|
|
251
|
-
SCREENSHOT OPTIONS:
|
|
252
|
-
<screenshot format="file" path="screenshot.png" /> <!-- Save to project dir -->
|
|
253
|
-
<screenshot format="file" /> <!-- Save to temp dir -->
|
|
254
|
-
<screenshot format="base64" /> <!-- Return as base64 string -->
|
|
255
|
-
|
|
256
|
-
AI SCREENSHOT ANALYSIS:
|
|
257
|
-
<analyze-screenshot>What products are visible on this page?</analyze-screenshot>
|
|
258
|
-
<analyze-screenshot model="gpt-4-vision">Describe the layout</analyze-screenshot>
|
|
259
|
-
|
|
260
|
-
TAB MANAGEMENT:
|
|
261
|
-
- Tabs are agent-isolated (each agent has its own tabs)
|
|
262
|
-
- Tabs auto-close after 1 hour of inactivity
|
|
263
|
-
- Tab names must be unique per agent
|
|
264
|
-
- Use descriptive names for easy identification
|
|
265
|
-
|
|
266
|
-
═══════════════════════════════════════════════════════════════
|
|
267
|
-
COMMAND CHAINING BENEFITS
|
|
268
|
-
═══════════════════════════════════════════════════════════════
|
|
269
|
-
|
|
270
|
-
Instead of:
|
|
271
|
-
1. Open tab → wait for response
|
|
272
|
-
2. Navigate → wait for response
|
|
273
|
-
3. Click → wait for response
|
|
274
|
-
4. Screenshot → wait for response
|
|
275
|
-
|
|
276
|
-
Do this (ONE REQUEST):
|
|
277
|
-
<open-tab name="task">
|
|
278
|
-
<navigate>URL</navigate>
|
|
279
|
-
<click selector=".button" />
|
|
280
|
-
<screenshot />
|
|
281
|
-
</open-tab>
|
|
282
|
-
|
|
283
|
-
All actions execute sequentially in one operation!
|
|
284
|
-
|
|
285
|
-
═══════════════════════════════════════════════════════════════
|
|
286
|
-
EXAMPLES
|
|
287
|
-
═══════════════════════════════════════════════════════════════
|
|
288
|
-
|
|
289
|
-
EXAMPLE 1: Search and analyze results
|
|
290
|
-
[tool id="web"]
|
|
291
|
-
<operation>interactive</operation>
|
|
292
|
-
<headless>true</headless>
|
|
293
|
-
<actions>
|
|
294
|
-
<open-tab name="search">
|
|
295
|
-
<navigate>https://google.com</navigate>
|
|
296
|
-
<type selector="input[name=q]">best web scraping tools 2025</type>
|
|
297
|
-
<press key="Enter" />
|
|
298
|
-
<wait-for selector="#search" />
|
|
299
|
-
<screenshot format="file" path="search-results.png" />
|
|
300
|
-
<analyze-screenshot>List the top 3 tools mentioned</analyze-screenshot>
|
|
301
|
-
<extract-links selector="#search .g a" />
|
|
302
|
-
</open-tab>
|
|
303
|
-
</actions>
|
|
304
|
-
[/tool]
|
|
305
|
-
|
|
306
|
-
EXAMPLE 2: Multi-tab workflow
|
|
307
|
-
[tool id="web"]
|
|
308
|
-
<operation>interactive</operation>
|
|
309
|
-
<actions>
|
|
310
|
-
<open-tab name="github">
|
|
311
|
-
<navigate>https://github.com/trending</navigate>
|
|
312
|
-
<extract-links selector=".Box-row a" />
|
|
313
|
-
</open-tab>
|
|
314
|
-
<open-tab name="npm">
|
|
315
|
-
<navigate>https://npmjs.com/package/puppeteer</navigate>
|
|
316
|
-
<extract-text selector=".package-description" />
|
|
317
|
-
</open-tab>
|
|
318
|
-
<list-tabs />
|
|
319
|
-
</actions>
|
|
320
|
-
[/tool]
|
|
321
|
-
|
|
322
|
-
EXAMPLE 3: Form interaction
|
|
323
|
-
[tool id="web"]
|
|
324
|
-
<operation>interactive</operation>
|
|
325
|
-
<headless>false</headless> <!-- Visible browser for debugging -->
|
|
326
|
-
<actions>
|
|
327
|
-
<open-tab name="form">
|
|
328
|
-
<navigate>https://example.com/contact</navigate>
|
|
329
|
-
<type selector="#name" text="John Doe" />
|
|
330
|
-
<type selector="#email" text="john@example.com" />
|
|
331
|
-
<type selector="#message" text="Hello!" />
|
|
332
|
-
<click selector="#submit" />
|
|
333
|
-
<wait-for selector=".success-message" />
|
|
334
|
-
<screenshot format="base64" />
|
|
335
|
-
</open-tab>
|
|
336
|
-
</actions>
|
|
337
|
-
[/tool]
|
|
338
|
-
|
|
339
|
-
EXAMPLE 4: Simple fetch
|
|
340
|
-
[tool id="web"]
|
|
341
|
-
<operation>fetch</operation>
|
|
342
|
-
<url>https://example.com</url>
|
|
343
|
-
<format>title,text,links</format>
|
|
344
|
-
[/tool]
|
|
345
|
-
|
|
346
|
-
EXAMPLE 5: Quick search
|
|
347
|
-
[tool id="web"]
|
|
348
|
-
<operation>search</operation>
|
|
349
|
-
<query>openai gpt-4 api documentation</query>
|
|
350
|
-
<engine>google</engine>
|
|
351
|
-
<max-results>5</max-results>
|
|
352
|
-
[/tool]
|
|
353
|
-
|
|
354
|
-
SECURITY NOTES:
|
|
355
|
-
- Browser runs in isolated context per agent
|
|
356
|
-
- Tabs auto-close after 1 hour of inactivity
|
|
357
|
-
- Screenshots stored in temp directory with auto-cleanup
|
|
358
|
-
- No access to local file system beyond allowed directories
|
|
359
|
-
|
|
360
|
-
BEST PRACTICES:
|
|
361
|
-
- Use command chaining to minimize round-trips
|
|
362
|
-
- Use descriptive tab names
|
|
363
|
-
- Close tabs when done to free resources
|
|
364
|
-
- Use headless mode for better performance
|
|
365
|
-
- Use visible mode only for debugging
|
|
366
|
-
`;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Parse parameters from tool command content
|
|
371
|
-
* @param {string} content - Raw tool command content
|
|
372
|
-
* @returns {Object} Parsed parameters
|
|
373
|
-
*/
|
|
374
|
-
parseParameters(content) {
|
|
375
|
-
try {
|
|
376
|
-
// Try JSON first
|
|
377
|
-
if (content.trim().startsWith('{')) {
|
|
378
|
-
return JSON.parse(content);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Parse XML-style tags
|
|
382
|
-
const params = {};
|
|
383
|
-
|
|
384
|
-
// Extract operation
|
|
385
|
-
const operationMatches = TagParser.extractContent(content, 'operation');
|
|
386
|
-
if (operationMatches.length > 0) {
|
|
387
|
-
params.operation = operationMatches[0].trim();
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Extract based on operation
|
|
391
|
-
switch (params.operation) {
|
|
392
|
-
case 'search':
|
|
393
|
-
params.query = TagParser.extractContent(content, 'query')[0]?.trim();
|
|
394
|
-
params.engine = TagParser.extractContent(content, 'engine')[0]?.trim() || 'google';
|
|
395
|
-
const maxResults = TagParser.extractContent(content, 'max-results')[0]?.trim();
|
|
396
|
-
params.maxResults = maxResults ? parseInt(maxResults, 10) : 10;
|
|
397
|
-
break;
|
|
398
|
-
|
|
399
|
-
case 'fetch':
|
|
400
|
-
params.url = TagParser.extractContent(content, 'url')[0]?.trim();
|
|
401
|
-
const formatStr = TagParser.extractContent(content, 'format')[0]?.trim();
|
|
402
|
-
params.formats = formatStr ? formatStr.split(',').map(f => f.trim()) : ['title', 'text'];
|
|
403
|
-
break;
|
|
404
|
-
|
|
405
|
-
case 'interactive':
|
|
406
|
-
const headlessStr = TagParser.extractContent(content, 'headless')[0]?.trim();
|
|
407
|
-
params.headless = headlessStr !== 'false'; // Default true
|
|
408
|
-
|
|
409
|
-
// Extract actions block
|
|
410
|
-
const actionsContent = TagParser.extractContent(content, 'actions')[0];
|
|
411
|
-
if (actionsContent) {
|
|
412
|
-
params.actions = this.parseActions(actionsContent);
|
|
413
|
-
}
|
|
414
|
-
break;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
params.rawContent = content.trim();
|
|
418
|
-
return params;
|
|
419
|
-
|
|
420
|
-
} catch (error) {
|
|
421
|
-
throw new Error(`Failed to parse web tool parameters: ${error.message}`);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* Parse actions from XML content
|
|
427
|
-
* @param {string} content - Actions XML content
|
|
428
|
-
* @returns {Array} Parsed actions
|
|
429
|
-
* @private
|
|
430
|
-
*/
|
|
431
|
-
parseActions(content) {
|
|
432
|
-
const actions = [];
|
|
433
|
-
|
|
434
|
-
// Parse open-tab actions
|
|
435
|
-
const openTabRegex = /<open-tab[^>]*name="([^"]+)"[^>]*>([\s\S]*?)<\/open-tab>/g;
|
|
436
|
-
let match;
|
|
437
|
-
|
|
438
|
-
while ((match = openTabRegex.exec(content)) !== null) {
|
|
439
|
-
const [, name, nestedContent] = match;
|
|
440
|
-
const url = TagParser.extractContent(nestedContent, 'navigate')[0]?.trim();
|
|
441
|
-
|
|
442
|
-
actions.push({
|
|
443
|
-
type: 'open-tab',
|
|
444
|
-
name,
|
|
445
|
-
url,
|
|
446
|
-
nestedActions: this.parseNestedActions(nestedContent)
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// Parse other actions (close-tab, switch-tab, list-tabs, etc.)
|
|
451
|
-
const simpleActions = [
|
|
452
|
-
'close-tab', 'switch-tab', 'list-tabs', 'navigate',
|
|
453
|
-
'click', 'type', 'press', 'wait-for', 'screenshot',
|
|
454
|
-
'analyze-screenshot', 'extract-text', 'extract-links',
|
|
455
|
-
'get-source', 'get-console', 'scroll', 'hover', 'mouse-move'
|
|
456
|
-
];
|
|
457
|
-
|
|
458
|
-
for (const actionType of simpleActions) {
|
|
459
|
-
const regex = new RegExp(`<${actionType}([^>]*)>([^<]*)<\/${actionType}>`, 'g');
|
|
460
|
-
let actionMatch;
|
|
461
|
-
|
|
462
|
-
while ((actionMatch = regex.exec(content)) !== null) {
|
|
463
|
-
const [, attrs, value] = actionMatch;
|
|
464
|
-
const action = { type: actionType };
|
|
465
|
-
|
|
466
|
-
// Parse attributes
|
|
467
|
-
const attrRegex = /(\w+(?:-\w+)*)="([^"]*)"/g;
|
|
468
|
-
let attrMatch;
|
|
469
|
-
while ((attrMatch = attrRegex.exec(attrs)) !== null) {
|
|
470
|
-
action[attrMatch[1]] = attrMatch[2];
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Add value if present
|
|
474
|
-
if (value && value.trim()) {
|
|
475
|
-
action.value = value.trim();
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
actions.push(action);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
return actions;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* Parse nested actions within a tab
|
|
487
|
-
* @param {string} content - Nested actions content
|
|
488
|
-
* @returns {Array} Parsed nested actions
|
|
489
|
-
* @private
|
|
490
|
-
*/
|
|
491
|
-
parseNestedActions(content) {
|
|
492
|
-
const actions = [];
|
|
493
|
-
|
|
494
|
-
const actionTypes = [
|
|
495
|
-
'navigate', 'click', 'type', 'press', 'wait-for', 'screenshot',
|
|
496
|
-
'analyze-screenshot', 'extract-text', 'extract-links',
|
|
497
|
-
'get-source', 'get-console', 'scroll', 'hover', 'mouse-move'
|
|
498
|
-
];
|
|
499
|
-
|
|
500
|
-
for (const actionType of actionTypes) {
|
|
501
|
-
const regex = new RegExp(`<${actionType}([^>]*)>([^<]*)<\/${actionType}>|<${actionType}([^>]*)\/>`, 'g');
|
|
502
|
-
let match;
|
|
503
|
-
|
|
504
|
-
while ((match = regex.exec(content)) !== null) {
|
|
505
|
-
const [, attrs1, value, attrs2] = match;
|
|
506
|
-
const attrs = attrs1 || attrs2 || '';
|
|
507
|
-
const action = { type: actionType };
|
|
508
|
-
|
|
509
|
-
// Parse attributes
|
|
510
|
-
const attrRegex = /(\w+(?:-\w+)*)="([^"]*)"/g;
|
|
511
|
-
let attrMatch;
|
|
512
|
-
while ((attrMatch = attrRegex.exec(attrs)) !== null) {
|
|
513
|
-
action[attrMatch[1]] = attrMatch[2];
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Add value if present
|
|
517
|
-
if (value && value.trim()) {
|
|
518
|
-
action.value = value.trim();
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
actions.push(action);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
return actions;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Get required parameters based on operation
|
|
530
|
-
* @returns {Array<string>} Array of required parameter names
|
|
531
|
-
*/
|
|
532
|
-
getRequiredParameters() {
|
|
533
|
-
return ['operation'];
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
/**
|
|
537
|
-
* Custom parameter validation
|
|
538
|
-
* @param {Object} params - Parameters to validate
|
|
539
|
-
* @returns {Object} Validation result
|
|
540
|
-
*/
|
|
541
|
-
customValidateParameters(params) {
|
|
542
|
-
const errors = [];
|
|
543
|
-
|
|
544
|
-
if (!['search', 'fetch', 'interactive'].includes(params.operation)) {
|
|
545
|
-
errors.push('operation must be one of: search, fetch, interactive');
|
|
546
|
-
return { valid: false, errors };
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
switch (params.operation) {
|
|
550
|
-
case 'search':
|
|
551
|
-
if (!params.query) {
|
|
552
|
-
errors.push('query is required for search operation');
|
|
553
|
-
}
|
|
554
|
-
break;
|
|
555
|
-
|
|
556
|
-
case 'fetch':
|
|
557
|
-
if (!params.url) {
|
|
558
|
-
errors.push('url is required for fetch operation');
|
|
559
|
-
}
|
|
560
|
-
break;
|
|
561
|
-
|
|
562
|
-
case 'interactive':
|
|
563
|
-
if (!params.actions || !Array.isArray(params.actions) || params.actions.length === 0) {
|
|
564
|
-
errors.push('actions array is required for interactive operation');
|
|
565
|
-
}
|
|
566
|
-
break;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
return {
|
|
570
|
-
valid: errors.length === 0,
|
|
571
|
-
errors
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
/**
|
|
576
|
-
* Execute tool with parsed parameters
|
|
577
|
-
* @param {Object} params - Parsed parameters
|
|
578
|
-
* @param {Object} context - Execution context
|
|
579
|
-
* @returns {Promise<Object>} Execution result
|
|
580
|
-
*/
|
|
581
|
-
async execute(params, context) {
|
|
582
|
-
const { operation } = params;
|
|
583
|
-
const { agentId } = context;
|
|
584
|
-
|
|
585
|
-
try {
|
|
586
|
-
// Ensure browser is initialized
|
|
587
|
-
await this.ensureBrowser();
|
|
588
|
-
|
|
589
|
-
let result;
|
|
590
|
-
|
|
591
|
-
switch (operation) {
|
|
592
|
-
case 'search':
|
|
593
|
-
result = await this.search(params.query, {
|
|
594
|
-
engine: params.engine || 'google',
|
|
595
|
-
maxResults: params.maxResults || 10,
|
|
596
|
-
agentId
|
|
597
|
-
});
|
|
598
|
-
break;
|
|
599
|
-
|
|
600
|
-
case 'fetch':
|
|
601
|
-
result = await this.fetch(params.url, {
|
|
602
|
-
formats: params.formats || ['title', 'text'],
|
|
603
|
-
agentId
|
|
604
|
-
});
|
|
605
|
-
break;
|
|
606
|
-
|
|
607
|
-
case 'interactive':
|
|
608
|
-
result = await this.interactive(params.actions, {
|
|
609
|
-
headless: params.headless !== false, // Default true
|
|
610
|
-
agentId,
|
|
611
|
-
context
|
|
612
|
-
});
|
|
613
|
-
break;
|
|
614
|
-
|
|
615
|
-
default:
|
|
616
|
-
throw new Error(`Unknown operation: ${operation}`);
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
return {
|
|
620
|
-
success: true,
|
|
621
|
-
operation,
|
|
622
|
-
result,
|
|
623
|
-
toolUsed: 'web'
|
|
624
|
-
};
|
|
625
|
-
|
|
626
|
-
} catch (error) {
|
|
627
|
-
this.logger?.error('Web tool execution failed', {
|
|
628
|
-
operation,
|
|
629
|
-
error: error.message,
|
|
630
|
-
agentId
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
return {
|
|
634
|
-
success: false,
|
|
635
|
-
operation,
|
|
636
|
-
error: error.message,
|
|
637
|
-
toolUsed: 'web'
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
/**
|
|
643
|
-
* Ensure browser is initialized
|
|
644
|
-
* @private
|
|
645
|
-
*/
|
|
646
|
-
async ensureBrowser() {
|
|
647
|
-
if (this.browser && this.browser.isConnected()) {
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
if (this.browserInitializing) {
|
|
652
|
-
// Wait for browser to finish initializing
|
|
653
|
-
while (this.browserInitializing) {
|
|
654
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
655
|
-
}
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
this.browserInitializing = true;
|
|
660
|
-
|
|
661
|
-
try {
|
|
662
|
-
this.logger?.info('Initializing Puppeteer browser');
|
|
663
|
-
|
|
664
|
-
this.browser = await puppeteer.launch({
|
|
665
|
-
headless: 'new', // Use new headless mode
|
|
666
|
-
args: [
|
|
667
|
-
'--no-sandbox',
|
|
668
|
-
'--disable-setuid-sandbox',
|
|
669
|
-
'--disable-dev-shm-usage',
|
|
670
|
-
'--disable-accelerated-2d-canvas',
|
|
671
|
-
'--disable-gpu',
|
|
672
|
-
'--disable-blink-features=AutomationControlled',
|
|
673
|
-
'--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
674
|
-
]
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
this.logger?.info('Puppeteer browser initialized successfully');
|
|
678
|
-
|
|
679
|
-
} catch (error) {
|
|
680
|
-
this.logger?.error('Failed to initialize browser', { error: error.message });
|
|
681
|
-
throw new Error(`Browser initialization failed: ${error.message}`);
|
|
682
|
-
} finally {
|
|
683
|
-
this.browserInitializing = false;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
/**
|
|
688
|
-
* Search the web using a known search engine
|
|
689
|
-
* @param {string} query - Search query
|
|
690
|
-
* @param {Object} options - Search options
|
|
691
|
-
* @returns {Promise<Object>} Search results
|
|
692
|
-
*/
|
|
693
|
-
async search(query, options = {}) {
|
|
694
|
-
const { engine = 'google', maxResults = 10, agentId } = options;
|
|
695
|
-
|
|
696
|
-
// Validate query
|
|
697
|
-
if (!query || typeof query !== 'string' || query.trim().length === 0) {
|
|
698
|
-
throw new Error('Search query is required and must be a non-empty string');
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
const searchEngine = this.searchEngines.find(e => e.name === engine);
|
|
702
|
-
if (!searchEngine) {
|
|
703
|
-
throw new Error(`Unknown search engine: ${engine}. Available: ${this.searchEngines.map(e => e.name).join(', ')}`);
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// Ensure browser is initialized
|
|
707
|
-
await this.ensureBrowser();
|
|
708
|
-
|
|
709
|
-
this.logger?.info('Performing web search', { query, engine, agentId });
|
|
710
|
-
|
|
711
|
-
// Create temporary page for search
|
|
712
|
-
const page = await this.browser.newPage();
|
|
713
|
-
|
|
714
|
-
try {
|
|
715
|
-
// Navigate to search engine
|
|
716
|
-
const searchUrl = `${searchEngine.url}${encodeURIComponent(query)}`;
|
|
717
|
-
await page.goto(searchUrl, { waitUntil: 'networkidle2', timeout: this.DEFAULT_TIMEOUT });
|
|
718
|
-
|
|
719
|
-
// Wait for results
|
|
720
|
-
await page.waitForSelector(searchEngine.waitSelector, { timeout: this.DEFAULT_TIMEOUT });
|
|
721
|
-
|
|
722
|
-
// Extract results
|
|
723
|
-
const results = await page.evaluate((selector, max) => {
|
|
724
|
-
const links = Array.from(document.querySelectorAll(selector));
|
|
725
|
-
return links.slice(0, max).map(link => ({
|
|
726
|
-
url: link.href,
|
|
727
|
-
title: link.textContent.trim(),
|
|
728
|
-
description: link.closest('.g, .b_algo, .result')?.textContent.trim() || ''
|
|
729
|
-
})).filter(result => result.url && result.url.startsWith('http'));
|
|
730
|
-
}, searchEngine.resultsSelector, maxResults);
|
|
731
|
-
|
|
732
|
-
this.logger?.info('Search completed', { resultsCount: results.length, agentId });
|
|
733
|
-
|
|
734
|
-
return {
|
|
735
|
-
success: true,
|
|
736
|
-
query,
|
|
737
|
-
engine,
|
|
738
|
-
resultsCount: results.length,
|
|
739
|
-
results
|
|
740
|
-
};
|
|
741
|
-
|
|
742
|
-
} finally {
|
|
743
|
-
await page.close();
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
/**
|
|
748
|
-
* Fetch web content in various formats
|
|
749
|
-
* @param {string} url - URL to fetch
|
|
750
|
-
* @param {Object} options - Fetch options
|
|
751
|
-
* @returns {Promise<Object>} Fetched content
|
|
752
|
-
*/
|
|
753
|
-
async fetch(url, options = {}) {
|
|
754
|
-
const { formats = ['title', 'text'], agentId } = options;
|
|
755
|
-
|
|
756
|
-
// Ensure browser is initialized
|
|
757
|
-
await this.ensureBrowser();
|
|
758
|
-
|
|
759
|
-
this.logger?.info('Fetching web content', { url, formats, agentId });
|
|
760
|
-
|
|
761
|
-
// Create temporary page
|
|
762
|
-
const page = await this.browser.newPage();
|
|
763
|
-
|
|
764
|
-
try {
|
|
765
|
-
// Listen for console messages if requested
|
|
766
|
-
const consoleMessages = [];
|
|
767
|
-
if (formats.includes('console')) {
|
|
768
|
-
page.on('console', msg => {
|
|
769
|
-
consoleMessages.push({
|
|
770
|
-
type: msg.type(),
|
|
771
|
-
text: msg.text()
|
|
772
|
-
});
|
|
773
|
-
});
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
// Navigate to URL
|
|
777
|
-
await page.goto(url, { waitUntil: 'networkidle2', timeout: this.DEFAULT_TIMEOUT });
|
|
778
|
-
|
|
779
|
-
const result = { url };
|
|
780
|
-
|
|
781
|
-
// Extract requested formats
|
|
782
|
-
for (const format of formats) {
|
|
783
|
-
switch (format) {
|
|
784
|
-
case 'title':
|
|
785
|
-
result.title = await page.title();
|
|
786
|
-
break;
|
|
787
|
-
|
|
788
|
-
case 'text':
|
|
789
|
-
result.text = await page.evaluate(() => document.body.innerText);
|
|
790
|
-
break;
|
|
791
|
-
|
|
792
|
-
case 'links':
|
|
793
|
-
result.links = await page.evaluate(() => {
|
|
794
|
-
return Array.from(document.querySelectorAll('a[href]')).map(a => ({
|
|
795
|
-
href: a.href,
|
|
796
|
-
text: a.textContent.trim()
|
|
797
|
-
}));
|
|
798
|
-
});
|
|
799
|
-
break;
|
|
800
|
-
|
|
801
|
-
case 'html':
|
|
802
|
-
result.html = await page.content();
|
|
803
|
-
break;
|
|
804
|
-
|
|
805
|
-
case 'console':
|
|
806
|
-
result.consoleMessages = consoleMessages;
|
|
807
|
-
break;
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
this.logger?.info('Fetch completed', { url, formats, agentId });
|
|
812
|
-
|
|
813
|
-
return {
|
|
814
|
-
success: true,
|
|
815
|
-
...result
|
|
816
|
-
};
|
|
817
|
-
|
|
818
|
-
} finally {
|
|
819
|
-
await page.close();
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
/**
|
|
824
|
-
* Interactive browser automation with command chaining
|
|
825
|
-
* @param {Array} actions - Array of actions to execute
|
|
826
|
-
* @param {Object} options - Options
|
|
827
|
-
* @returns {Promise<Object>} Results of all actions
|
|
828
|
-
*/
|
|
829
|
-
async interactive(actions, options = {}) {
|
|
830
|
-
const { headless = true, agentId, context } = options;
|
|
831
|
-
|
|
832
|
-
// Ensure browser is initialized
|
|
833
|
-
await this.ensureBrowser();
|
|
834
|
-
|
|
835
|
-
this.logger?.info('Starting interactive session', {
|
|
836
|
-
actionsCount: actions.length,
|
|
837
|
-
headless,
|
|
838
|
-
agentId
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
const results = [];
|
|
842
|
-
|
|
843
|
-
// Initialize agent tabs if not exists
|
|
844
|
-
if (!this.agentTabs.has(agentId)) {
|
|
845
|
-
this.agentTabs.set(agentId, new Map());
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
const agentTabsMap = this.agentTabs.get(agentId);
|
|
849
|
-
|
|
850
|
-
for (const action of actions) {
|
|
851
|
-
try {
|
|
852
|
-
let actionResult;
|
|
853
|
-
|
|
854
|
-
switch (action.type) {
|
|
855
|
-
case 'open-tab':
|
|
856
|
-
actionResult = await this.openTab(agentId, action.name, action.url, headless, action.nestedActions, context);
|
|
857
|
-
break;
|
|
858
|
-
|
|
859
|
-
case 'close-tab':
|
|
860
|
-
actionResult = await this.closeTab(agentId, action.name);
|
|
861
|
-
break;
|
|
862
|
-
|
|
863
|
-
case 'switch-tab':
|
|
864
|
-
actionResult = await this.switchTab(agentId, action.name);
|
|
865
|
-
break;
|
|
866
|
-
|
|
867
|
-
case 'list-tabs':
|
|
868
|
-
actionResult = await this.listTabs(agentId);
|
|
869
|
-
break;
|
|
870
|
-
|
|
871
|
-
default:
|
|
872
|
-
// For actions that need a tab context, we need to specify which tab
|
|
873
|
-
// For now, we'll skip these at the top level
|
|
874
|
-
actionResult = {
|
|
875
|
-
success: false,
|
|
876
|
-
error: `Action ${action.type} must be executed within a tab context (use open-tab with nestedActions)`
|
|
877
|
-
};
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
results.push({
|
|
881
|
-
action: action.type,
|
|
882
|
-
...actionResult
|
|
883
|
-
});
|
|
884
|
-
|
|
885
|
-
} catch (error) {
|
|
886
|
-
this.logger?.error('Action failed', {
|
|
887
|
-
action: action.type,
|
|
888
|
-
error: error.message,
|
|
889
|
-
agentId
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
results.push({
|
|
893
|
-
action: action.type,
|
|
894
|
-
success: false,
|
|
895
|
-
error: error.message
|
|
896
|
-
});
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
return {
|
|
901
|
-
success: results.every(r => r.success !== false),
|
|
902
|
-
actionsExecuted: results.length,
|
|
903
|
-
results
|
|
904
|
-
};
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
/**
|
|
908
|
-
* Open a new tab with nested actions
|
|
909
|
-
* @param {string} agentId - Agent identifier
|
|
910
|
-
* @param {string} tabName - Unique tab name
|
|
911
|
-
* @param {string} url - Initial URL
|
|
912
|
-
* @param {boolean} headless - Headless mode
|
|
913
|
-
* @param {Array} nestedActions - Actions to execute in this tab
|
|
914
|
-
* @param {Object} context - Execution context
|
|
915
|
-
* @returns {Promise<Object>} Result
|
|
916
|
-
*/
|
|
917
|
-
async openTab(agentId, tabName, url, headless, nestedActions = [], context = {}) {
|
|
918
|
-
// Initialize agent tabs if not exists
|
|
919
|
-
if (!this.agentTabs.has(agentId)) {
|
|
920
|
-
this.agentTabs.set(agentId, new Map());
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
const agentTabsMap = this.agentTabs.get(agentId);
|
|
924
|
-
|
|
925
|
-
// Check if tab already exists
|
|
926
|
-
if (agentTabsMap.has(tabName)) {
|
|
927
|
-
throw new Error(`Tab '${tabName}' already exists for agent ${agentId}`);
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
this.logger?.info('Opening tab', { agentId, tabName, url, headless });
|
|
931
|
-
|
|
932
|
-
// Create new page
|
|
933
|
-
const page = await this.browser.newPage();
|
|
934
|
-
|
|
935
|
-
// Set viewport
|
|
936
|
-
await page.setViewport({ width: 1280, height: 720 });
|
|
937
|
-
|
|
938
|
-
// Track console messages
|
|
939
|
-
const consoleMessages = [];
|
|
940
|
-
page.on('console', msg => {
|
|
941
|
-
consoleMessages.push({
|
|
942
|
-
type: msg.type(),
|
|
943
|
-
text: msg.text(),
|
|
944
|
-
timestamp: Date.now()
|
|
945
|
-
});
|
|
946
|
-
});
|
|
947
|
-
|
|
948
|
-
// Store tab info
|
|
949
|
-
const tabInfo = {
|
|
950
|
-
page,
|
|
951
|
-
url,
|
|
952
|
-
lastActivity: Date.now(),
|
|
953
|
-
headless,
|
|
954
|
-
consoleMessages,
|
|
955
|
-
name: tabName
|
|
956
|
-
};
|
|
957
|
-
|
|
958
|
-
agentTabsMap.set(tabName, tabInfo);
|
|
959
|
-
|
|
960
|
-
const results = [];
|
|
961
|
-
|
|
962
|
-
try {
|
|
963
|
-
// Navigate to initial URL if provided
|
|
964
|
-
if (url) {
|
|
965
|
-
await page.goto(url, { waitUntil: 'networkidle2', timeout: this.DEFAULT_TIMEOUT });
|
|
966
|
-
tabInfo.url = url;
|
|
967
|
-
tabInfo.lastActivity = Date.now();
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
// Execute nested actions
|
|
971
|
-
for (const action of nestedActions) {
|
|
972
|
-
const actionResult = await this.executeTabAction(page, action, tabInfo, context);
|
|
973
|
-
results.push({
|
|
974
|
-
action: action.type,
|
|
975
|
-
...actionResult
|
|
976
|
-
});
|
|
977
|
-
tabInfo.lastActivity = Date.now();
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
return {
|
|
981
|
-
success: true,
|
|
982
|
-
tabName,
|
|
983
|
-
url: tabInfo.url,
|
|
984
|
-
actionsExecuted: results.length,
|
|
985
|
-
results
|
|
986
|
-
};
|
|
987
|
-
|
|
988
|
-
} catch (error) {
|
|
989
|
-
this.logger?.error('Failed to open tab', {
|
|
990
|
-
agentId,
|
|
991
|
-
tabName,
|
|
992
|
-
error: error.message
|
|
993
|
-
});
|
|
994
|
-
|
|
995
|
-
// Clean up on error
|
|
996
|
-
await page.close();
|
|
997
|
-
agentTabsMap.delete(tabName);
|
|
998
|
-
|
|
999
|
-
throw error;
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
/**
|
|
1004
|
-
* Execute an action in a tab context
|
|
1005
|
-
* @param {Page} page - Puppeteer page
|
|
1006
|
-
* @param {Object} action - Action to execute
|
|
1007
|
-
* @param {Object} tabInfo - Tab information
|
|
1008
|
-
* @param {Object} context - Execution context
|
|
1009
|
-
* @returns {Promise<Object>} Action result
|
|
1010
|
-
* @private
|
|
1011
|
-
*/
|
|
1012
|
-
async executeTabAction(page, action, tabInfo, context) {
|
|
1013
|
-
switch (action.type) {
|
|
1014
|
-
case 'navigate':
|
|
1015
|
-
await page.goto(action.value || action.url, {
|
|
1016
|
-
waitUntil: 'networkidle2',
|
|
1017
|
-
timeout: this.DEFAULT_TIMEOUT
|
|
1018
|
-
});
|
|
1019
|
-
tabInfo.url = page.url();
|
|
1020
|
-
return { success: true, url: tabInfo.url };
|
|
1021
|
-
|
|
1022
|
-
case 'click':
|
|
1023
|
-
await page.click(action.selector, {
|
|
1024
|
-
button: action.button || 'left'
|
|
1025
|
-
});
|
|
1026
|
-
return { success: true, selector: action.selector };
|
|
1027
|
-
|
|
1028
|
-
case 'type':
|
|
1029
|
-
await page.type(action.selector, action.text || action.value);
|
|
1030
|
-
return { success: true, selector: action.selector, text: action.text };
|
|
1031
|
-
|
|
1032
|
-
case 'press':
|
|
1033
|
-
await page.keyboard.press(action.key || action.value);
|
|
1034
|
-
return { success: true, key: action.key };
|
|
1035
|
-
|
|
1036
|
-
case 'wait-for':
|
|
1037
|
-
const timeout = action.timeout ? parseInt(action.timeout, 10) : this.DEFAULT_TIMEOUT;
|
|
1038
|
-
await page.waitForSelector(action.selector, { timeout });
|
|
1039
|
-
return { success: true, selector: action.selector };
|
|
1040
|
-
|
|
1041
|
-
case 'screenshot':
|
|
1042
|
-
return await this.takeScreenshot(page, action, context);
|
|
1043
|
-
|
|
1044
|
-
case 'analyze-screenshot':
|
|
1045
|
-
return await this.analyzeScreenshot(page, action.value, context);
|
|
1046
|
-
|
|
1047
|
-
case 'extract-text':
|
|
1048
|
-
const text = await page.evaluate((sel) => {
|
|
1049
|
-
const element = document.querySelector(sel);
|
|
1050
|
-
return element ? element.innerText : null;
|
|
1051
|
-
}, action.selector);
|
|
1052
|
-
return { success: true, selector: action.selector, text };
|
|
1053
|
-
|
|
1054
|
-
case 'extract-links':
|
|
1055
|
-
const links = await page.evaluate((sel) => {
|
|
1056
|
-
const elements = document.querySelectorAll(sel);
|
|
1057
|
-
return Array.from(elements).map(a => ({
|
|
1058
|
-
href: a.href,
|
|
1059
|
-
text: a.textContent.trim()
|
|
1060
|
-
}));
|
|
1061
|
-
}, action.selector);
|
|
1062
|
-
return { success: true, selector: action.selector, links };
|
|
1063
|
-
|
|
1064
|
-
case 'get-source':
|
|
1065
|
-
const html = await page.content();
|
|
1066
|
-
return { success: true, html };
|
|
1067
|
-
|
|
1068
|
-
case 'get-console':
|
|
1069
|
-
return {
|
|
1070
|
-
success: true,
|
|
1071
|
-
consoleMessages: [...tabInfo.consoleMessages]
|
|
1072
|
-
};
|
|
1073
|
-
|
|
1074
|
-
case 'scroll':
|
|
1075
|
-
await page.evaluate((sel) => {
|
|
1076
|
-
if (sel) {
|
|
1077
|
-
document.querySelector(sel)?.scrollIntoView();
|
|
1078
|
-
} else {
|
|
1079
|
-
window.scrollTo(0, document.body.scrollHeight);
|
|
1080
|
-
}
|
|
1081
|
-
}, action.selector);
|
|
1082
|
-
return { success: true };
|
|
1083
|
-
|
|
1084
|
-
case 'hover':
|
|
1085
|
-
await page.hover(action.selector);
|
|
1086
|
-
return { success: true, selector: action.selector };
|
|
1087
|
-
|
|
1088
|
-
case 'mouse-move':
|
|
1089
|
-
await page.hover(action.selector);
|
|
1090
|
-
return { success: true, selector: action.selector };
|
|
1091
|
-
|
|
1092
|
-
default:
|
|
1093
|
-
throw new Error(`Unknown action type: ${action.type}`);
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
/**
|
|
1098
|
-
* Take screenshot of page
|
|
1099
|
-
* @param {Page} page - Puppeteer page
|
|
1100
|
-
* @param {Object} options - Screenshot options
|
|
1101
|
-
* @param {Object} context - Execution context
|
|
1102
|
-
* @returns {Promise<Object>} Screenshot result
|
|
1103
|
-
* @private
|
|
1104
|
-
*/
|
|
1105
|
-
async takeScreenshot(page, options, context) {
|
|
1106
|
-
const format = options.format || 'file';
|
|
1107
|
-
const screenshotPath = options.path;
|
|
1108
|
-
|
|
1109
|
-
if (format === 'base64') {
|
|
1110
|
-
const screenshot = await page.screenshot({ encoding: 'base64' });
|
|
1111
|
-
return {
|
|
1112
|
-
success: true,
|
|
1113
|
-
format: 'base64',
|
|
1114
|
-
screenshot
|
|
1115
|
-
};
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
// File format
|
|
1119
|
-
let filePath;
|
|
1120
|
-
|
|
1121
|
-
if (screenshotPath) {
|
|
1122
|
-
// Save to project directory if path is provided
|
|
1123
|
-
const projectDir = context.directoryAccess?.workingDirectory || context.projectDir || process.cwd();
|
|
1124
|
-
filePath = path.isAbsolute(screenshotPath)
|
|
1125
|
-
? screenshotPath
|
|
1126
|
-
: path.join(projectDir, screenshotPath);
|
|
1127
|
-
} else {
|
|
1128
|
-
// Save to temp directory
|
|
1129
|
-
const filename = `screenshot-${Date.now()}.png`;
|
|
1130
|
-
filePath = path.join(this.TEMP_DIR, filename);
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
await page.screenshot({ path: filePath });
|
|
1134
|
-
|
|
1135
|
-
return {
|
|
1136
|
-
success: true,
|
|
1137
|
-
format: 'file',
|
|
1138
|
-
path: filePath
|
|
1139
|
-
};
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
/**
|
|
1143
|
-
* Analyze screenshot using AI vision model
|
|
1144
|
-
* @param {Page} page - Puppeteer page
|
|
1145
|
-
* @param {string} question - Question for AI
|
|
1146
|
-
* @param {Object} context - Execution context
|
|
1147
|
-
* @returns {Promise<Object>} Analysis result
|
|
1148
|
-
* @private
|
|
1149
|
-
*/
|
|
1150
|
-
async analyzeScreenshot(page, question, context) {
|
|
1151
|
-
// Take screenshot as base64
|
|
1152
|
-
const screenshot = await page.screenshot({ encoding: 'base64' });
|
|
1153
|
-
|
|
1154
|
-
// Get AI service from context
|
|
1155
|
-
const aiService = context.aiService;
|
|
1156
|
-
if (!aiService) {
|
|
1157
|
-
throw new Error('AI service not available for screenshot analysis');
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
this.logger?.info('Analyzing screenshot with AI', {
|
|
1161
|
-
question: question.substring(0, 100),
|
|
1162
|
-
agentId: context.agentId
|
|
1163
|
-
});
|
|
1164
|
-
|
|
1165
|
-
try {
|
|
1166
|
-
// Use vision model (prefer o3 if available, fallback to gpt-4-vision)
|
|
1167
|
-
const model = 'o3'; // Will be mapped by AI service
|
|
1168
|
-
|
|
1169
|
-
// Create message with image
|
|
1170
|
-
const response = await aiService.sendMessage(
|
|
1171
|
-
model,
|
|
1172
|
-
question,
|
|
1173
|
-
{
|
|
1174
|
-
agentId: context.agentId,
|
|
1175
|
-
images: [`data:image/png;base64,${screenshot}`],
|
|
1176
|
-
apiKey: context.apiKey,
|
|
1177
|
-
customApiKeys: context.customApiKeys,
|
|
1178
|
-
platformProvided: context.platformProvided
|
|
1179
|
-
}
|
|
1180
|
-
);
|
|
1181
|
-
|
|
1182
|
-
return {
|
|
1183
|
-
success: true,
|
|
1184
|
-
question,
|
|
1185
|
-
analysis: response.content,
|
|
1186
|
-
model: response.model || model
|
|
1187
|
-
};
|
|
1188
|
-
|
|
1189
|
-
} catch (error) {
|
|
1190
|
-
this.logger?.error('Screenshot analysis failed', {
|
|
1191
|
-
error: error.message,
|
|
1192
|
-
agentId: context.agentId
|
|
1193
|
-
});
|
|
1194
|
-
|
|
1195
|
-
throw new Error(`Screenshot analysis failed: ${error.message}`);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
/**
|
|
1200
|
-
* Close a tab
|
|
1201
|
-
* @param {string} agentId - Agent identifier
|
|
1202
|
-
* @param {string} tabName - Tab name to close
|
|
1203
|
-
* @returns {Promise<Object>} Result
|
|
1204
|
-
*/
|
|
1205
|
-
async closeTab(agentId, tabName) {
|
|
1206
|
-
const agentTabsMap = this.agentTabs.get(agentId);
|
|
1207
|
-
if (!agentTabsMap || !agentTabsMap.has(tabName)) {
|
|
1208
|
-
throw new Error(`Tab '${tabName}' not found for agent ${agentId}`);
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
const tabInfo = agentTabsMap.get(tabName);
|
|
1212
|
-
|
|
1213
|
-
this.logger?.info('Closing tab', { agentId, tabName });
|
|
1214
|
-
|
|
1215
|
-
await tabInfo.page.close();
|
|
1216
|
-
agentTabsMap.delete(tabName);
|
|
1217
|
-
|
|
1218
|
-
return {
|
|
1219
|
-
success: true,
|
|
1220
|
-
tabName,
|
|
1221
|
-
message: `Tab '${tabName}' closed`
|
|
1222
|
-
};
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
/**
|
|
1226
|
-
* Switch to an existing tab
|
|
1227
|
-
* @param {string} agentId - Agent identifier
|
|
1228
|
-
* @param {string} tabName - Tab name to switch to
|
|
1229
|
-
* @returns {Promise<Object>} Result
|
|
1230
|
-
*/
|
|
1231
|
-
async switchTab(agentId, tabName) {
|
|
1232
|
-
const agentTabsMap = this.agentTabs.get(agentId);
|
|
1233
|
-
if (!agentTabsMap || !agentTabsMap.has(tabName)) {
|
|
1234
|
-
throw new Error(`Tab '${tabName}' not found for agent ${agentId}`);
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
const tabInfo = agentTabsMap.get(tabName);
|
|
1238
|
-
tabInfo.lastActivity = Date.now();
|
|
1239
|
-
|
|
1240
|
-
return {
|
|
1241
|
-
success: true,
|
|
1242
|
-
tabName,
|
|
1243
|
-
url: tabInfo.url,
|
|
1244
|
-
message: `Switched to tab '${tabName}'`
|
|
1245
|
-
};
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
/**
|
|
1249
|
-
* List all active tabs for an agent
|
|
1250
|
-
* @param {string} agentId - Agent identifier
|
|
1251
|
-
* @returns {Promise<Object>} List of tabs
|
|
1252
|
-
*/
|
|
1253
|
-
async listTabs(agentId) {
|
|
1254
|
-
const agentTabsMap = this.agentTabs.get(agentId);
|
|
1255
|
-
|
|
1256
|
-
if (!agentTabsMap || agentTabsMap.size === 0) {
|
|
1257
|
-
return {
|
|
1258
|
-
success: true,
|
|
1259
|
-
tabCount: 0,
|
|
1260
|
-
tabs: [],
|
|
1261
|
-
message: 'No active tabs'
|
|
1262
|
-
};
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
const tabs = [];
|
|
1266
|
-
for (const [name, info] of agentTabsMap.entries()) {
|
|
1267
|
-
tabs.push({
|
|
1268
|
-
name,
|
|
1269
|
-
url: info.url,
|
|
1270
|
-
idleTime: Date.now() - info.lastActivity,
|
|
1271
|
-
headless: info.headless
|
|
1272
|
-
});
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
return {
|
|
1276
|
-
success: true,
|
|
1277
|
-
tabCount: tabs.length,
|
|
1278
|
-
tabs
|
|
1279
|
-
};
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
/**
|
|
1283
|
-
* Start cleanup timer for idle tabs
|
|
1284
|
-
* @private
|
|
1285
|
-
*/
|
|
1286
|
-
startCleanupTimer() {
|
|
1287
|
-
if (this.cleanupTimer) {
|
|
1288
|
-
clearInterval(this.cleanupTimer);
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
this.cleanupTimer = setInterval(() => {
|
|
1292
|
-
this.cleanupIdleTabs();
|
|
1293
|
-
}, this.CLEANUP_INTERVAL);
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
/**
|
|
1297
|
-
* Cleanup idle tabs (1-hour timeout)
|
|
1298
|
-
* @private
|
|
1299
|
-
*/
|
|
1300
|
-
async cleanupIdleTabs() {
|
|
1301
|
-
const now = Date.now();
|
|
1302
|
-
const tabsToClose = [];
|
|
1303
|
-
|
|
1304
|
-
for (const [agentId, agentTabsMap] of this.agentTabs.entries()) {
|
|
1305
|
-
for (const [tabName, tabInfo] of agentTabsMap.entries()) {
|
|
1306
|
-
const idleTime = now - tabInfo.lastActivity;
|
|
1307
|
-
|
|
1308
|
-
if (idleTime > this.TAB_IDLE_TIMEOUT) {
|
|
1309
|
-
tabsToClose.push({ agentId, tabName, tabInfo });
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
if (tabsToClose.length > 0) {
|
|
1315
|
-
this.logger?.info('Cleaning up idle tabs', {
|
|
1316
|
-
count: tabsToClose.length
|
|
1317
|
-
});
|
|
1318
|
-
|
|
1319
|
-
for (const { agentId, tabName, tabInfo } of tabsToClose) {
|
|
1320
|
-
try {
|
|
1321
|
-
await tabInfo.page.close();
|
|
1322
|
-
this.agentTabs.get(agentId).delete(tabName);
|
|
1323
|
-
this.logger?.debug('Closed idle tab', { agentId, tabName });
|
|
1324
|
-
} catch (error) {
|
|
1325
|
-
this.logger?.error('Failed to close idle tab', {
|
|
1326
|
-
agentId,
|
|
1327
|
-
tabName,
|
|
1328
|
-
error: error.message
|
|
1329
|
-
});
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
/**
|
|
1336
|
-
* Cleanup all tabs for an agent (called when agent is deleted)
|
|
1337
|
-
* @param {string} agentId - Agent identifier
|
|
1338
|
-
* @returns {Promise<Object>} Cleanup result
|
|
1339
|
-
*/
|
|
1340
|
-
async cleanupAgent(agentId) {
|
|
1341
|
-
const agentTabsMap = this.agentTabs.get(agentId);
|
|
1342
|
-
|
|
1343
|
-
if (!agentTabsMap) {
|
|
1344
|
-
return {
|
|
1345
|
-
success: true,
|
|
1346
|
-
agentId,
|
|
1347
|
-
closedTabs: 0,
|
|
1348
|
-
message: 'No tabs to clean up'
|
|
1349
|
-
};
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
this.logger?.info('Cleaning up agent tabs', {
|
|
1353
|
-
agentId,
|
|
1354
|
-
tabCount: agentTabsMap.size
|
|
1355
|
-
});
|
|
1356
|
-
|
|
1357
|
-
let closedCount = 0;
|
|
1358
|
-
|
|
1359
|
-
for (const [tabName, tabInfo] of agentTabsMap.entries()) {
|
|
1360
|
-
try {
|
|
1361
|
-
await tabInfo.page.close();
|
|
1362
|
-
closedCount++;
|
|
1363
|
-
} catch (error) {
|
|
1364
|
-
this.logger?.error('Failed to close tab during cleanup', {
|
|
1365
|
-
agentId,
|
|
1366
|
-
tabName,
|
|
1367
|
-
error: error.message
|
|
1368
|
-
});
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
this.agentTabs.delete(agentId);
|
|
1373
|
-
|
|
1374
|
-
return {
|
|
1375
|
-
success: true,
|
|
1376
|
-
agentId,
|
|
1377
|
-
closedTabs: closedCount,
|
|
1378
|
-
message: `Closed ${closedCount} tabs for agent ${agentId}`
|
|
1379
|
-
};
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
/**
|
|
1383
|
-
* Ensure temp directory exists
|
|
1384
|
-
* @private
|
|
1385
|
-
*/
|
|
1386
|
-
async ensureTempDir() {
|
|
1387
|
-
try {
|
|
1388
|
-
await fs.mkdir(this.TEMP_DIR, { recursive: true });
|
|
1389
|
-
} catch (error) {
|
|
1390
|
-
this.logger?.warn('Failed to create temp directory', {
|
|
1391
|
-
path: this.TEMP_DIR,
|
|
1392
|
-
error: error.message
|
|
1393
|
-
});
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
/**
|
|
1398
|
-
* Cleanup resources
|
|
1399
|
-
*/
|
|
1400
|
-
async cleanup() {
|
|
1401
|
-
// Stop cleanup timer
|
|
1402
|
-
if (this.cleanupTimer) {
|
|
1403
|
-
clearInterval(this.cleanupTimer);
|
|
1404
|
-
this.cleanupTimer = null;
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
// Close all tabs
|
|
1408
|
-
for (const [agentId] of this.agentTabs.entries()) {
|
|
1409
|
-
await this.cleanupAgent(agentId);
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
// Close browser
|
|
1413
|
-
if (this.browser) {
|
|
1414
|
-
await this.browser.close();
|
|
1415
|
-
this.browser = null;
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
// Clean temp directory
|
|
1419
|
-
try {
|
|
1420
|
-
await fs.rm(this.TEMP_DIR, { recursive: true, force: true });
|
|
1421
|
-
} catch (error) {
|
|
1422
|
-
this.logger?.warn('Failed to clean temp directory', {
|
|
1423
|
-
path: this.TEMP_DIR,
|
|
1424
|
-
error: error.message
|
|
1425
|
-
});
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
export default WebTool;
|
|
1
|
+
const a0_0x409fc8=a0_0x44f7;(function(_0x4bedc9,_0x101b89){const _0x21200c=a0_0x44f7,_0x1c1390=_0x4bedc9();while(!![]){try{const _0x3dd826=parseInt(_0x21200c(0x144))/0x1+-parseInt(_0x21200c(0x151))/0x2+parseInt(_0x21200c(0x11e))/0x3*(-parseInt(_0x21200c(0x16c))/0x4)+parseInt(_0x21200c(0x12e))/0x5*(-parseInt(_0x21200c(0x152))/0x6)+parseInt(_0x21200c(0x119))/0x7*(-parseInt(_0x21200c(0x18d))/0x8)+parseInt(_0x21200c(0x150))/0x9*(-parseInt(_0x21200c(0x1a2))/0xa)+-parseInt(_0x21200c(0x1ad))/0xb*(-parseInt(_0x21200c(0x120))/0xc);if(_0x3dd826===_0x101b89)break;else _0x1c1390['push'](_0x1c1390['shift']());}catch(_0x30ff92){_0x1c1390['push'](_0x1c1390['shift']());}}}(a0_0x5e70,0x229be));import{BaseTool}from'./baseTool.js';import a0_0x41a119 from'../utilities/tagParser.js';import a0_0x1adca6 from'puppeteer';function a0_0x5e70(){const _0x12249a=['mZeZodnZC3bLwuK','ChjLC3m','mtq0t3PqtfbS','zMLSzq','zMv0y2G','uhvWCgv0zwvYigjYB3DZzxiGAw5PDgLHBgL6zwqGC3vJy2vZC2z1BgX5','Bw9KzwW','y3DK','z290BW','DgvTCerPCG','BMv3','tM8GDgfICYb0BYbJBgvHBIb1Ca','AgfZ','B3bLCMf0Aw9Uig11C3qGyMuGB25Lig9MoIbZzwfYy2GSigzLDgnOlcbPBNrLCMfJDgL2zq','Dgv4Da','lMCSic5Ix2fSz28Sic5Yzxn1Bhq','mJbAtwvxqxa','B3bLBLrHyG','zM9YBwf0CW','ChvZAa','DgLTzw91Da','rMfPBgvKihrVignYzwf0zsb0zw1WigrPCMvJDg9YEq','BgLZDfrHyNm','BMf2AwDHDgu','zgvSzxrL','B3bLBI10ywi','y29UDgvUDa','u3rHCNrPBMCGAw50zxjHy3rPDMuGC2vZC2LVBG','C2vSzwn0B3i','BMvZDgvKqwn0Aw9UCW','C2v0','Bw91C2uTBw92zq','DgL0Bgu','yw5HBhL6zvnJCMvLBNnOB3q','zw5ZDxjLqNjVD3nLCG','A2v5yM9HCMq','BMv3ugfNzq','i3nLyxjJAa','nZG0mJLhswfysvy','C3bSAxq','A2v5','zM9YBwf0','BgfZDefJDgL2Axr5','qw5HBhL6Aw5NihnJCMvLBNnOB3qGD2L0Acbbsq','Dg1WzgLY','y2XLyw51CfrPBwvY','DhjPBq','yMfZzty0','CxvLCNLtzwXLy3rVCKfSBa','Ahr0Chm6lY9KDwnRzhvJA2DVlMnVBs8/Ct0','mti2yM13vKPZ','mtuYotq4DMLMr3D0','mtG3ntG0rxD0A2De','BMfTzq','DxjSigLZihjLCxvPCMvKigzVCIbMzxrJAcbVCgvYyxrPB24','C2nYB2XS','u2vHCMnOihf1zxj5igLZihjLCxvPCMvKigfUzcbTDxn0igjLigeGBM9UlwvTChr5ihn0CMLUzW','DxjS','C2nYB2XSvg8','lNjLC3vSDf9FysWGyvTKyxrHlxrLC3rPzd0ICMvZDwX0lxrPDgXLlweIxq','vefcx0LetevFveLnru9vva','C3rHCNrdBgvHBNvWvgLTzxi','CMf3q29UDgvUDa','DhLWzq','AhjLzG','u2vHCMnOignVBxbSzxrLza','q2XVC2vKigLKBguGDgfI','zgvIDwC','yNjVD3nLCKLUAxrPywXPEMLUzW','Aw5WDxrBBMfTzt0ICsjD','lIbbDMfPBgfIBgu6ia','zMLSDgvY','C2XPy2u','Aw5UzxjuzxH0','Aw50zxjHy3rPDMu','zw50CMLLCW','ywn0Aw9UCW','rMv0y2HPBMCGD2vIignVBNrLBNq','nhzvBMX0DG','ywn0Aw9UCYbHCNjHEsbPCYbYzxf1AxjLzcbMB3iGAw50zxjHy3rPDMuGB3bLCMf0Aw9U','i2jFCMvZDwX0CW','yw5HBhL6zs1Zy3jLzw5ZAg90','BwfW','DgfIswrSzvrPBwvVDxq','BgvUz3rO','zxHLy3v0zvrHyKfJDgLVBG','C2nYB2XSsw50B1zPzxC','CgfNzq','ywDLBNrjza','D29YA2LUz0rPCMvJDg9YEq','C2vHCMnO','zxjYB3i','AgvHzgXLC3m','y2XPy2S','rMfPBgvKihrVignSzwfUihrLBxaGzgLYzwn0B3j5','kfTEpL0Qkt4Ow148xsOPpc8','DgfRzvnJCMvLBNnOB3q','CgXHDgzVCM1qCM92AwrLza','CgfYC2vqyxjHBwv0zxjZ','z2v0','Ag92zxi','CMvXDwLYzxnqCM9Qzwn0','zMLUza','CxvLCNK','B3bLCMf0Aw9U','CxvLCNLtzwXLy3rVCG','C2nYzwvUC2HVDc0','y2XLyw51CefNzw50','y29UC29Szq','BwTKAxi','Aw5JBhvKzxm','mtG1oteYwKn4A0fi','y29UC29Szu1LC3nHz2vZ','AhrTBa','yMLUzW','ywDLBNruywjZ','zxH0CMfJDenVBNrLBNq','zxzHBhvHDgu','Aw5MBW','Dgv4DenVBNrLBNq','CgfYC2vby3rPB25Z','AM9PBG','vw5RBM93BIbHy3rPB24GDhLWztOG','Bwf4uMvZDwX0CW','DMfSDwu','C2nYzwvUC2HVDa','z2v0lxnVDxjJzq','AxnbCNjHEq','ls1KAxnHyMXLlwrLDI1ZAg0TDxnHz2u','zw5NAw5L','y2XLyw51CeLKBgvuywjZ','zNjVBq','mtyYntuWug9irKzY','vevnuf9esvi','ls1KAxnHyMXLlwDWDq','D2fYBG','C2L6zq','Ahr0Chm6lY93D3CUyMLUzY5JB20VC2vHCMnOp3e9','zgf0ytPPBwfNzs9WBMC7yMfZzty0la','yxbPs2v5','qwn0Aw9UigzHAwXLza','pNW8','vw5RBM93BIbVCgvYyxrPB246ia','ntG4nZK3D2zfqwLt','zxHLyW','BgLZDc10ywjZ','D2fPDc1MB3i','vgfIicC','jYbHBhjLywr5igv4Axn0CYbMB3iGywDLBNqG','yNjVD3nLCG','zxzLCNK','Aw5WDxrBDhLWzt0IC3vIBwL0iL0Sigj1DhrVBLT0ExbLpsjZDwjTAxqIxq','u3DPDgnOzwqGDg8GDgfIicC','C2vHCMnOrw5NAw5LCW','z29Vz2XL','zxHLy3v0zq','zxH0CMfJDc10zxH0','revgqvvmvf9usu1ft1vu','yM9KEq','i3nLyxjJAcaUzYbHlcaJC2vHCMnOigfBAhjLzL0','BM93','ls1UBY1Zyw5KyM94','zMfSC2u','C3DPDgnOvgfI','y2XVC2u','BMv0D29YA2LKBguY','D2fPDezVCLnLBgvJDg9Y','ndjuALLmwgm','Bg9Nz2vY','y2XVC2vuywi','BwvZC2fNzq','rMfPBgvKihrVignSB3nLigLKBguGDgfI'];a0_0x5e70=function(){return _0x12249a;};return a0_0x5e70();}import a0_0xeef28c from'path';import a0_0x450aef from'fs/promises';import a0_0x261e4f from'os';import{TOOL_STATUS,SYSTEM_DEFAULTS}from'../utilities/constants.js';class WebTool extends BaseTool{constructor(_0xcef85a={},_0x54fda7=null){const _0x2504a2=a0_0x44f7;super(_0xcef85a,_0x54fda7),this[_0x2504a2(0x183)]=![],this['isAsync']=!![],this[_0x2504a2(0x1b3)]=null,this['browserInitializing']=![],this['agentTabs']=new Map(),this[_0x2504a2(0x1b7)]=[{'name':'google','url':'https://www.google.com/search?q=','searchSelector':'input[name=\x22q\x22]','submitSelector':_0x2504a2(0x1b5),'resultsSelector':_0x2504a2(0x1bd),'waitSelector':_0x2504a2(0x143)},{'name':_0x2504a2(0x190),'url':_0x2504a2(0x1a7),'searchSelector':_0x2504a2(0x163),'submitSelector':'input[type=\x22submit\x22]','resultsSelector':'.b_algo\x20a','waitSelector':_0x2504a2(0x16e)},{'name':'duckduckgo','url':_0x2504a2(0x14f),'searchSelector':'input[name=\x22q\x22]','submitSelector':'button[type=\x22submit\x22]','resultsSelector':_0x2504a2(0x159),'waitSelector':'#links,\x20[data-testid=\x22mainline\x22]'}],this[_0x2504a2(0x15a)]=_0xcef85a[_0x2504a2(0x171)]||0x3c*0x3c*0x3e8,this['CLEANUP_INTERVAL']=_0xcef85a['cleanupInterval']||0x5*0x3c*0x3e8,this[_0x2504a2(0x1bb)]=_0xcef85a['defaultTimeout']||0xea60,this['TEMP_DIR']=_0xcef85a[_0x2504a2(0x127)]||a0_0xeef28c['join'](a0_0x261e4f[_0x2504a2(0x14a)](),'webtool-screenshots'),this['cleanupTimer']=null,this['startCleanupTimer'](),this['ensureTempDir']();}['getDescription'](){return'\x0aWeb\x20Tool:\x20Browse,\x20search,\x20and\x20automate\x20web\x20interactions\x20using\x20a\x20real\x20browser\x20(Puppeteer).\x0a\x0aIMPORTANT:\x20This\x20tool\x20supports\x20command\x20chaining\x20-\x20nest\x20multiple\x20actions\x20together\x20to\x20execute\x20them\x20sequentially\x20without\x20waiting\x20for\x20responses\x20between\x20each\x20action.\x0a\x0aUSAGE:\x0a[tool\x20id=\x22web\x22]\x0a<operation>search|fetch|interactive</operation>\x0a<!--\x20Operation-specific\x20content\x20-->\x0a[/tool]\x0a\x0aALTERNATIVE\x20JSON\x20FORMAT:\x0a```json\x0a{\x0a\x20\x20\x22toolId\x22:\x20\x22web\x22,\x0a\x20\x20\x22operation\x22:\x20\x22search|fetch|interactive\x22,\x0a\x20\x20\x22parameters\x22:\x20{\x20...\x20}\x0a}\x0a```\x0a\x0a═══════════════════════════════════════════════════════════════\x0aOPERATION\x201:\x20SEARCH\x20THE\x20WEB\x0a═══════════════════════════════════════════════════════════════\x0a\x0aSearch\x20using\x20real\x20browsers\x20on\x20known\x20search\x20engines\x20(Google,\x20Bing,\x20DuckDuckGo).\x0a\x0aXML\x20SYNTAX:\x0a[tool\x20id=\x22web\x22]\x0a<operation>search</operation>\x0a<query>puppeteer\x20web\x20scraping\x20tutorial</query>\x0a<engine>google</engine>\x20<!--\x20Optional:\x20google|bing|duckduckgo,\x20default:\x20google\x20-->\x0a<max-results>10</max-results>\x20<!--\x20Optional,\x20default:\x2010\x20-->\x0a[/tool]\x0a\x0aJSON\x20SYNTAX:\x0a```json\x0a{\x0a\x20\x20\x22toolId\x22:\x20\x22web\x22,\x0a\x20\x20\x22operation\x22:\x20\x22search\x22,\x0a\x20\x20\x22query\x22:\x20\x22puppeteer\x20web\x20scraping\x20tutorial\x22,\x0a\x20\x20\x22engine\x22:\x20\x22google\x22,\x0a\x20\x20\x22maxResults\x22:\x2010\x0a}\x0a```\x0a\x0aOUTPUT:\x20List\x20of\x20URLs\x20with\x20titles\x20and\x20descriptions\x0a\x0a═══════════════════════════════════════════════════════════════\x0aOPERATION\x202:\x20FETCH\x20WEB\x20CONTENT\x0a═══════════════════════════════════════════════════════════════\x0a\x0aFetch\x20content\x20from\x20a\x20URL\x20in\x20various\x20formats.\x0a\x0aXML\x20SYNTAX:\x0a[tool\x20id=\x22web\x22]\x0a<operation>fetch</operation>\x0a<url>https://example.com</url>\x0a<format>title,text,links</format>\x20<!--\x20Options:\x20title|text|links|html|console\x20-->\x0a[/tool]\x0a\x0aJSON\x20SYNTAX:\x0a```json\x0a{\x0a\x20\x20\x22toolId\x22:\x20\x22web\x22,\x0a\x20\x20\x22operation\x22:\x20\x22fetch\x22,\x0a\x20\x20\x22url\x22:\x20\x22https://example.com\x22,\x0a\x20\x20\x22formats\x22:\x20[\x22title\x22,\x20\x22text\x22,\x20\x22links\x22,\x20\x22html\x22,\x20\x22console\x22]\x0a}\x0a```\x0a\x0aFORMAT\x20OPTIONS:\x0a-\x20title:\x20Page\x20title\x0a-\x20text:\x20Plain\x20text\x20content\x20(no\x20HTML)\x0a-\x20links:\x20All\x20links\x20on\x20page\x0a-\x20html:\x20Full\x20HTML\x20source\x0a-\x20console:\x20Browser\x20console\x20messages\x0a\x0aOUTPUT:\x20Object\x20with\x20requested\x20content\x20formats\x0a\x0a═══════════════════════════════════════════════════════════════\x0aOPERATION\x203:\x20INTERACTIVE\x20BROWSER\x20AUTOMATION\x0a═══════════════════════════════════════════════════════════════\x0a\x0aControl\x20a\x20real\x20browser\x20with\x20command\x20chaining\x20for\x20complex\x20workflows.\x0a\x0aXML\x20SYNTAX\x20(RECOMMENDED\x20FOR\x20CHAINING):\x0a[tool\x20id=\x22web\x22]\x0a<operation>interactive</operation>\x0a<headless>true</headless>\x20<!--\x20true|false,\x20default:\x20true\x20-->\x0a<actions>\x0a\x20\x20<open-tab\x20name=\x22search\x22>\x0a\x20\x20\x20\x20<navigate>https://github.com/trending</navigate>\x0a\x20\x20\x20\x20<wait-for\x20selector=\x22.Box-row\x22\x20timeout=\x225000\x22\x20/>\x0a\x20\x20\x20\x20<click\x20selector=\x22.Box-row:first-child\x20a\x22\x20/>\x0a\x20\x20\x20\x20<wait-for\x20selector=\x22#readme\x22\x20/>\x0a\x20\x20\x20\x20<screenshot\x20format=\x22file\x22\x20path=\x22readme.png\x22\x20/>\x0a\x20\x20\x20\x20<analyze-screenshot>What\x20is\x20the\x20main\x20topic\x20of\x20this\x20README?</analyze-screenshot>\x0a\x20\x20\x20\x20<extract-text\x20selector=\x22#readme\x22\x20/>\x0a\x20\x20\x20\x20<get-source\x20/>\x0a\x20\x20</open-tab>\x0a\x20\x20<open-tab\x20name=\x22docs\x22>\x0a\x20\x20\x20\x20<navigate>https://docs.example.com</navigate>\x0a\x20\x20\x20\x20<type\x20selector=\x22input.search\x22\x20text=\x22installation\x22\x20/>\x0a\x20\x20\x20\x20<press\x20key=\x22Enter\x22\x20/>\x0a\x20\x20\x20\x20<extract-links\x20selector=\x22a.doc-link\x22\x20/>\x0a\x20\x20</open-tab>\x0a\x20\x20<list-tabs\x20/>\x0a\x20\x20<close-tab\x20name=\x22search\x22\x20/>\x0a</actions>\x0a[/tool]\x0a\x0aJSON\x20SYNTAX\x20(ALTERNATIVE):\x0a```json\x0a{\x0a\x20\x20\x22toolId\x22:\x20\x22web\x22,\x0a\x20\x20\x22operation\x22:\x20\x22interactive\x22,\x0a\x20\x20\x22headless\x22:\x20true,\x0a\x20\x20\x22actions\x22:\x20[\x0a\x20\x20\x20\x20{\x0a\x20\x20\x20\x20\x20\x20\x22type\x22:\x20\x22open-tab\x22,\x0a\x20\x20\x20\x20\x20\x20\x22name\x22:\x20\x22search\x22,\x0a\x20\x20\x20\x20\x20\x20\x22url\x22:\x20\x22https://github.com/trending\x22,\x0a\x20\x20\x20\x20\x20\x20\x22nestedActions\x22:\x20[\x0a\x20\x20\x20\x20\x20\x20\x20\x20{\x22type\x22:\x20\x22wait-for\x22,\x20\x22selector\x22:\x20\x22.Box-row\x22,\x20\x22timeout\x22:\x205000},\x0a\x20\x20\x20\x20\x20\x20\x20\x20{\x22type\x22:\x20\x22click\x22,\x20\x22selector\x22:\x20\x22.Box-row:first-child\x20a\x22},\x0a\x20\x20\x20\x20\x20\x20\x20\x20{\x22type\x22:\x20\x22screenshot\x22,\x20\x22format\x22:\x20\x22base64\x22},\x0a\x20\x20\x20\x20\x20\x20\x20\x20{\x22type\x22:\x20\x22extract-text\x22,\x20\x22selector\x22:\x20\x22#readme\x22}\x0a\x20\x20\x20\x20\x20\x20]\x0a\x20\x20\x20\x20},\x0a\x20\x20\x20\x20{\x22type\x22:\x20\x22list-tabs\x22},\x0a\x20\x20\x20\x20{\x22type\x22:\x20\x22close-tab\x22,\x20\x22name\x22:\x20\x22search\x22}\x0a\x20\x20]\x0a}\x0a```\x0a\x0aSUPPORTED\x20ACTIONS:\x0a-\x20open-tab:\x20Open\x20new\x20tab\x20with\x20nested\x20actions\x0a-\x20close-tab:\x20Close\x20specific\x20tab\x0a-\x20switch-tab:\x20Switch\x20to\x20existing\x20tab\x0a-\x20list-tabs:\x20List\x20all\x20active\x20tabs\x20for\x20this\x20agent\x0a-\x20navigate:\x20Go\x20to\x20URL\x0a-\x20click:\x20Click\x20element\x20(left|right|middle)\x0a-\x20type:\x20Type\x20text\x20into\x20element\x0a-\x20press:\x20Press\x20keyboard\x20key\x0a-\x20wait-for:\x20Wait\x20for\x20element\x20to\x20appear\x0a-\x20screenshot:\x20Capture\x20screenshot\x20(file|base64)\x0a-\x20analyze-screenshot:\x20AI\x20analysis\x20of\x20current\x20page\x0a-\x20extract-text:\x20Extract\x20text\x20from\x20selector\x0a-\x20extract-links:\x20Extract\x20all\x20links\x0a-\x20get-source:\x20Get\x20HTML\x20source\x0a-\x20get-console:\x20Get\x20console\x20messages\x0a-\x20scroll:\x20Scroll\x20page\x0a-\x20hover:\x20Hover\x20over\x20element\x0a\x0aMOUSE\x20EVENTS:\x0a<click\x20selector=\x22.button\x22\x20button=\x22left\x22\x20/>\x20<!--\x20left|right|middle\x20-->\x0a<hover\x20selector=\x22.menu\x22\x20/>\x0a<mouse-move\x20selector=\x22.element\x22\x20/>\x0a\x0aKEYBOARD\x20EVENTS:\x0a<type\x20selector=\x22input\x22\x20text=\x22Hello\x20World\x22\x20/>\x0a<press\x20key=\x22Enter\x22\x20/>\x0a<press\x20key=\x22Control+C\x22\x20/>\x20<!--\x20Supports\x20modifier\x20keys\x20-->\x0a\x0aSCREENSHOT\x20OPTIONS:\x0a<screenshot\x20format=\x22file\x22\x20path=\x22screenshot.png\x22\x20/>\x20<!--\x20Save\x20to\x20project\x20dir\x20-->\x0a<screenshot\x20format=\x22file\x22\x20/>\x20<!--\x20Save\x20to\x20temp\x20dir\x20-->\x0a<screenshot\x20format=\x22base64\x22\x20/>\x20<!--\x20Return\x20as\x20base64\x20string\x20-->\x0a\x0aAI\x20SCREENSHOT\x20ANALYSIS:\x0a<analyze-screenshot>What\x20products\x20are\x20visible\x20on\x20this\x20page?</analyze-screenshot>\x0a<analyze-screenshot\x20model=\x22gpt-4-vision\x22>Describe\x20the\x20layout</analyze-screenshot>\x0a\x0aTAB\x20MANAGEMENT:\x0a-\x20Tabs\x20are\x20agent-isolated\x20(each\x20agent\x20has\x20its\x20own\x20tabs)\x0a-\x20Tabs\x20auto-close\x20after\x201\x20hour\x20of\x20inactivity\x0a-\x20Tab\x20names\x20must\x20be\x20unique\x20per\x20agent\x0a-\x20Use\x20descriptive\x20names\x20for\x20easy\x20identification\x0a\x0a═══════════════════════════════════════════════════════════════\x0aCOMMAND\x20CHAINING\x20BENEFITS\x0a═══════════════════════════════════════════════════════════════\x0a\x0aInstead\x20of:\x0a1.\x20Open\x20tab\x20→\x20wait\x20for\x20response\x0a2.\x20Navigate\x20→\x20wait\x20for\x20response\x0a3.\x20Click\x20→\x20wait\x20for\x20response\x0a4.\x20Screenshot\x20→\x20wait\x20for\x20response\x0a\x0aDo\x20this\x20(ONE\x20REQUEST):\x0a<open-tab\x20name=\x22task\x22>\x0a\x20\x20<navigate>URL</navigate>\x0a\x20\x20<click\x20selector=\x22.button\x22\x20/>\x0a\x20\x20<screenshot\x20/>\x0a</open-tab>\x0a\x0aAll\x20actions\x20execute\x20sequentially\x20in\x20one\x20operation!\x0a\x0a═══════════════════════════════════════════════════════════════\x0aEXAMPLES\x0a═══════════════════════════════════════════════════════════════\x0a\x0aEXAMPLE\x201:\x20Search\x20and\x20analyze\x20results\x0a[tool\x20id=\x22web\x22]\x0a<operation>interactive</operation>\x0a<headless>true</headless>\x0a<actions>\x0a\x20\x20<open-tab\x20name=\x22search\x22>\x0a\x20\x20\x20\x20<navigate>https://google.com</navigate>\x0a\x20\x20\x20\x20<type\x20selector=\x22input[name=q]\x22>best\x20web\x20scraping\x20tools\x202025</type>\x0a\x20\x20\x20\x20<press\x20key=\x22Enter\x22\x20/>\x0a\x20\x20\x20\x20<wait-for\x20selector=\x22#search\x22\x20/>\x0a\x20\x20\x20\x20<screenshot\x20format=\x22file\x22\x20path=\x22search-results.png\x22\x20/>\x0a\x20\x20\x20\x20<analyze-screenshot>List\x20the\x20top\x203\x20tools\x20mentioned</analyze-screenshot>\x0a\x20\x20\x20\x20<extract-links\x20selector=\x22#search\x20.g\x20a\x22\x20/>\x0a\x20\x20</open-tab>\x0a</actions>\x0a[/tool]\x0a\x0aEXAMPLE\x202:\x20Multi-tab\x20workflow\x0a[tool\x20id=\x22web\x22]\x0a<operation>interactive</operation>\x0a<actions>\x0a\x20\x20<open-tab\x20name=\x22github\x22>\x0a\x20\x20\x20\x20<navigate>https://github.com/trending</navigate>\x0a\x20\x20\x20\x20<extract-links\x20selector=\x22.Box-row\x20a\x22\x20/>\x0a\x20\x20</open-tab>\x0a\x20\x20<open-tab\x20name=\x22npm\x22>\x0a\x20\x20\x20\x20<navigate>https://npmjs.com/package/puppeteer</navigate>\x0a\x20\x20\x20\x20<extract-text\x20selector=\x22.package-description\x22\x20/>\x0a\x20\x20</open-tab>\x0a\x20\x20<list-tabs\x20/>\x0a</actions>\x0a[/tool]\x0a\x0aEXAMPLE\x203:\x20Form\x20interaction\x0a[tool\x20id=\x22web\x22]\x0a<operation>interactive</operation>\x0a<headless>false</headless>\x20<!--\x20Visible\x20browser\x20for\x20debugging\x20-->\x0a<actions>\x0a\x20\x20<open-tab\x20name=\x22form\x22>\x0a\x20\x20\x20\x20<navigate>https://example.com/contact</navigate>\x0a\x20\x20\x20\x20<type\x20selector=\x22#name\x22\x20text=\x22John\x20Doe\x22\x20/>\x0a\x20\x20\x20\x20<type\x20selector=\x22#email\x22\x20text=\x22john@example.com\x22\x20/>\x0a\x20\x20\x20\x20<type\x20selector=\x22#message\x22\x20text=\x22Hello!\x22\x20/>\x0a\x20\x20\x20\x20<click\x20selector=\x22#submit\x22\x20/>\x0a\x20\x20\x20\x20<wait-for\x20selector=\x22.success-message\x22\x20/>\x0a\x20\x20\x20\x20<screenshot\x20format=\x22base64\x22\x20/>\x0a\x20\x20</open-tab>\x0a</actions>\x0a[/tool]\x0a\x0aEXAMPLE\x204:\x20Simple\x20fetch\x0a[tool\x20id=\x22web\x22]\x0a<operation>fetch</operation>\x0a<url>https://example.com</url>\x0a<format>title,text,links</format>\x0a[/tool]\x0a\x0aEXAMPLE\x205:\x20Quick\x20search\x0a[tool\x20id=\x22web\x22]\x0a<operation>search</operation>\x0a<query>openai\x20gpt-4\x20api\x20documentation</query>\x0a<engine>google</engine>\x0a<max-results>5</max-results>\x0a[/tool]\x0a\x0aSECURITY\x20NOTES:\x0a-\x20Browser\x20runs\x20in\x20isolated\x20context\x20per\x20agent\x0a-\x20Tabs\x20auto-close\x20after\x201\x20hour\x20of\x20inactivity\x0a-\x20Screenshots\x20stored\x20in\x20temp\x20directory\x20with\x20auto-cleanup\x0a-\x20No\x20access\x20to\x20local\x20file\x20system\x20beyond\x20allowed\x20directories\x0a\x0aBEST\x20PRACTICES:\x0a-\x20Use\x20command\x20chaining\x20to\x20minimize\x20round-trips\x0a-\x20Use\x20descriptive\x20tab\x20names\x0a-\x20Close\x20tabs\x20when\x20done\x20to\x20free\x20resources\x0a-\x20Use\x20headless\x20mode\x20for\x20better\x20performance\x0a-\x20Use\x20visible\x20mode\x20only\x20for\x20debugging\x0a\x20\x20\x20\x20';}[a0_0x409fc8(0x180)](_0x1f81fd){const _0x45b4b1=a0_0x409fc8;try{if(_0x1f81fd[_0x45b4b1(0x14c)]()['startsWith']('{'))return JSON['parse'](_0x1f81fd);const _0x106574={},_0x4f26a5=a0_0x41a119['extractContent'](_0x1f81fd,'operation');_0x4f26a5['length']>0x0&&(_0x106574['operation']=_0x4f26a5[0x0][_0x45b4b1(0x14c)]());switch(_0x106574['operation']){case'search':_0x106574[_0x45b4b1(0x185)]=a0_0x41a119[_0x45b4b1(0x192)](_0x1f81fd,'query')[0x0]?.['trim'](),_0x106574['engine']=a0_0x41a119[_0x45b4b1(0x192)](_0x1f81fd,_0x45b4b1(0x19f))[0x0]?.['trim']()||_0x45b4b1(0x1b8);const _0x3414e7=a0_0x41a119['extractContent'](_0x1f81fd,'max-results')[0x0]?.[_0x45b4b1(0x14c)]();_0x106574[_0x45b4b1(0x199)]=_0x3414e7?parseInt(_0x3414e7,0xa):0xa;break;case _0x45b4b1(0x122):_0x106574[_0x45b4b1(0x157)]=a0_0x41a119[_0x45b4b1(0x192)](_0x1f81fd,'url')[0x0]?.['trim']();const _0x41de61=a0_0x41a119['extractContent'](_0x1f81fd,'format')[0x0]?.[_0x45b4b1(0x14c)]();_0x106574[_0x45b4b1(0x130)]=_0x41de61?_0x41de61[_0x45b4b1(0x145)](',')[_0x45b4b1(0x170)](_0x1e37dd=>_0x1e37dd[_0x45b4b1(0x14c)]()):[_0x45b4b1(0x13e),'text'];break;case'interactive':const _0xf9af86=a0_0x41a119[_0x45b4b1(0x192)](_0x1f81fd,'headless')[0x0]?.['trim']();_0x106574[_0x45b4b1(0x17a)]=_0xf9af86!==_0x45b4b1(0x114);const _0x5d8911=a0_0x41a119[_0x45b4b1(0x192)](_0x1f81fd,_0x45b4b1(0x16a))[0x0];_0x5d8911&&(_0x106574['actions']=this[_0x45b4b1(0x196)](_0x5d8911));break;}return _0x106574[_0x45b4b1(0x15c)]=_0x1f81fd[_0x45b4b1(0x14c)](),_0x106574;}catch(_0x296c50){throw new Error('Failed\x20to\x20parse\x20web\x20tool\x20parameters:\x20'+_0x296c50['message']);}}['parseActions'](_0x1eff21){const _0x265995=a0_0x409fc8,_0x38fe7d=[],_0x1a7a94=/<open-tab[^>]*name="([^"]+)"[^>]*>([\s\S]*?)<\/open-tab>/g;let _0x2b8679;while((_0x2b8679=_0x1a7a94[_0x265995(0x1ae)](_0x1eff21))!==null){const [,_0xa45168,_0x1e758a]=_0x2b8679,_0x59344c=a0_0x41a119['extractContent'](_0x1e758a,_0x265995(0x135))[0x0]?.[_0x265995(0x14c)]();_0x38fe7d['push']({'type':'open-tab','name':_0xa45168,'url':_0x59344c,'nestedActions':this['parseNestedActions'](_0x1e758a)});}const _0x27a786=['close-tab','switch-tab',_0x265995(0x1af),_0x265995(0x135),_0x265995(0x17b),'type','press',_0x265995(0x1b0),_0x265995(0x19b),'analyze-screenshot',_0x265995(0x1ba),'extract-links',_0x265995(0x19c),'get-console',_0x265995(0x155),'hover',_0x265995(0x13d)];for(const _0x49b7aa of _0x27a786){const _0x41e561=new RegExp('<'+_0x49b7aa+_0x265995(0x17d)+_0x49b7aa+'>','g');let _0x2b02c0;while((_0x2b02c0=_0x41e561['exec'](_0x1eff21))!==null){const [,_0x19b470,_0x42a4b8]=_0x2b02c0,_0x86f1ce={'type':_0x49b7aa},_0x57310a=/(\w+(?:-\w+)*)="([^"]*)"/g;let _0x44448f;while((_0x44448f=_0x57310a['exec'](_0x19b470))!==null){_0x86f1ce[_0x44448f[0x1]]=_0x44448f[0x2];}_0x42a4b8&&_0x42a4b8[_0x265995(0x14c)]()&&(_0x86f1ce[_0x265995(0x19a)]=_0x42a4b8[_0x265995(0x14c)]()),_0x38fe7d['push'](_0x86f1ce);}}return _0x38fe7d;}['parseNestedActions'](_0x111a47){const _0xef32df=a0_0x409fc8,_0x2b8442=[],_0x2ee8e4=['navigate','click','type','press','wait-for',_0xef32df(0x19b),'analyze-screenshot',_0xef32df(0x1ba),'extract-links',_0xef32df(0x19c),'get-console',_0xef32df(0x155),'hover',_0xef32df(0x13d)];for(const _0xb04f4c of _0x2ee8e4){const _0x515902=new RegExp('<'+_0xb04f4c+_0xef32df(0x17d)+_0xb04f4c+_0xef32df(0x1ab)+_0xb04f4c+'([^>]*)/>','g');let _0x32ec20;while((_0x32ec20=_0x515902[_0xef32df(0x1ae)](_0x111a47))!==null){const [,_0x208caf,_0x1c9086,_0xe3445b]=_0x32ec20,_0x55bf09=_0x208caf||_0xe3445b||'',_0x400cd9={'type':_0xb04f4c},_0x44a434=/(\w+(?:-\w+)*)="([^"]*)"/g;let _0xa9fef2;while((_0xa9fef2=_0x44a434[_0xef32df(0x1ae)](_0x55bf09))!==null){_0x400cd9[_0xa9fef2[0x1]]=_0xa9fef2[0x2];}_0x1c9086&&_0x1c9086['trim']()&&(_0x400cd9[_0xef32df(0x19a)]=_0x1c9086['trim']()),_0x2b8442[_0xef32df(0x131)](_0x400cd9);}}return _0x2b8442;}['getRequiredParameters'](){const _0xbcc8d9=a0_0x409fc8;return[_0xbcc8d9(0x186)];}['customValidateParameters'](_0x17c2b2){const _0x15e009=a0_0x409fc8,_0x5b849a=[];if(!['search',_0x15e009(0x122),_0x15e009(0x168)][_0x15e009(0x18c)](_0x17c2b2[_0x15e009(0x186)]))return _0x5b849a['push'](_0x15e009(0x12b)),{'valid':![],'errors':_0x5b849a};switch(_0x17c2b2[_0x15e009(0x186)]){case _0x15e009(0x178):!_0x17c2b2['query']&&_0x5b849a['push']('query\x20is\x20required\x20for\x20search\x20operation');break;case'fetch':!_0x17c2b2['url']&&_0x5b849a[_0x15e009(0x131)](_0x15e009(0x154));break;case _0x15e009(0x168):(!_0x17c2b2[_0x15e009(0x16a)]||!Array[_0x15e009(0x19d)](_0x17c2b2['actions'])||_0x17c2b2['actions'][_0x15e009(0x172)]===0x0)&&_0x5b849a[_0x15e009(0x131)](_0x15e009(0x16d));break;}return{'valid':_0x5b849a['length']===0x0,'errors':_0x5b849a};}async[a0_0x409fc8(0x1b9)](_0x456047,_0x4d66fc){const _0x317922=a0_0x409fc8,{operation:_0x7bc59d}=_0x456047,{agentId:_0x44b3f4}=_0x4d66fc;try{await this['ensureBrowser']();let _0x4cd747;switch(_0x7bc59d){case'search':_0x4cd747=await this[_0x317922(0x178)](_0x456047[_0x317922(0x185)],{'engine':_0x456047[_0x317922(0x19f)]||_0x317922(0x1b8),'maxResults':_0x456047[_0x317922(0x199)]||0xa,'agentId':_0x44b3f4});break;case _0x317922(0x122):_0x4cd747=await this[_0x317922(0x122)](_0x456047[_0x317922(0x157)],{'formats':_0x456047['formats']||[_0x317922(0x13e),_0x317922(0x12c)],'agentId':_0x44b3f4});break;case'interactive':_0x4cd747=await this['interactive'](_0x456047[_0x317922(0x16a)],{'headless':_0x456047['headless']!==![],'agentId':_0x44b3f4,'context':_0x4d66fc});break;default:throw new Error(_0x317922(0x1ac)+_0x7bc59d);}return{'success':!![],'operation':_0x7bc59d,'result':_0x4cd747,'toolUsed':'web'};}catch(_0x2ecd28){return this[_0x317922(0x11a)]?.[_0x317922(0x179)]('Web\x20tool\x20execution\x20failed',{'operation':_0x7bc59d,'error':_0x2ecd28[_0x317922(0x11c)],'agentId':_0x44b3f4}),{'success':![],'operation':_0x7bc59d,'error':_0x2ecd28[_0x317922(0x11c)],'toolUsed':'web'};}}async[a0_0x409fc8(0x140)](){const _0x4159b1=a0_0x409fc8;if(this['browser']&&this['browser']['isConnected']())return;if(this[_0x4159b1(0x162)]){while(this['browserInitializing']){await new Promise(_0x4441d4=>setTimeout(_0x4441d4,0x64));}return;}this[_0x4159b1(0x162)]=!![];try{this[_0x4159b1(0x11a)]?.[_0x4159b1(0x194)]('Initializing\x20Puppeteer\x20browser'),this[_0x4159b1(0x1b3)]=await a0_0x1adca6['launch']({'headless':_0x4159b1(0x128),'args':[_0x4159b1(0x113),'--disable-setuid-sandbox',_0x4159b1(0x19e),'--disable-accelerated-2d-canvas',_0x4159b1(0x1a4),'--disable-blink-features=AutomationControlled','--user-agent=Mozilla/5.0\x20(Windows\x20NT\x2010.0;\x20Win64;\x20x64)\x20AppleWebKit/537.36\x20(KHTML,\x20like\x20Gecko)\x20Chrome/120.0.0.0\x20Safari/537.36']}),this[_0x4159b1(0x11a)]?.['info'](_0x4159b1(0x123));}catch(_0xeb8426){this[_0x4159b1(0x11a)]?.[_0x4159b1(0x179)]('Failed\x20to\x20initialize\x20browser',{'error':_0xeb8426[_0x4159b1(0x11c)]});throw new Error('Browser\x20initialization\x20failed:\x20'+_0xeb8426['message']);}finally{this['browserInitializing']=![];}}async[a0_0x409fc8(0x178)](_0x6a1d20,_0x264f3a={}){const _0x1b3323=a0_0x409fc8,{engine:engine='google',maxResults:maxResults=0xa,agentId:_0x1e11f5}=_0x264f3a;if(!_0x6a1d20||typeof _0x6a1d20!=='string'||_0x6a1d20[_0x1b3323(0x14c)]()['length']===0x0)throw new Error(_0x1b3323(0x156));const _0x9adb31=this[_0x1b3323(0x1b7)][_0x1b3323(0x184)](_0xeb21fe=>_0xeb21fe[_0x1b3323(0x153)]===engine);if(!_0x9adb31)throw new Error('Unknown\x20search\x20engine:\x20'+engine+_0x1b3323(0x164)+this['searchEngines'][_0x1b3323(0x170)](_0x533874=>_0x533874['name'])[_0x1b3323(0x197)](',\x20'));await this['ensureBrowser'](),this['logger']?.['info']('Performing\x20web\x20search',{'query':_0x6a1d20,'engine':engine,'agentId':_0x1e11f5});const _0x582043=await this['browser'][_0x1b3323(0x142)]();try{const _0x2742bc=''+_0x9adb31['url']+encodeURIComponent(_0x6a1d20);await _0x582043[_0x1b3323(0x126)](_0x2742bc,{'waitUntil':'networkidle2','timeout':this[_0x1b3323(0x1bb)]}),await _0x582043[_0x1b3323(0x118)](_0x9adb31['waitSelector'],{'timeout':this['DEFAULT_TIMEOUT']});const _0x18d986=await _0x582043[_0x1b3323(0x193)]((_0x2aef7e,_0x123fdb)=>{const _0x340d6a=_0x1b3323,_0x3314b0=Array[_0x340d6a(0x1a1)](document['querySelectorAll'](_0x2aef7e));return _0x3314b0[_0x340d6a(0x166)](0x0,_0x123fdb)[_0x340d6a(0x170)](_0x5e7aec=>({'url':_0x5e7aec['href'],'title':_0x5e7aec['textContent']['trim'](),'description':_0x5e7aec['closest'](_0x340d6a(0x12d))?.[_0x340d6a(0x195)][_0x340d6a(0x14c)]()||''}))[_0x340d6a(0x165)](_0x519e20=>_0x519e20[_0x340d6a(0x157)]&&_0x519e20[_0x340d6a(0x157)]['startsWith']('http'));},_0x9adb31['resultsSelector'],maxResults);return this[_0x1b3323(0x11a)]?.['info'](_0x1b3323(0x15f),{'resultsCount':_0x18d986[_0x1b3323(0x172)],'agentId':_0x1e11f5}),{'success':!![],'query':_0x6a1d20,'engine':engine,'resultsCount':_0x18d986[_0x1b3323(0x172)],'results':_0x18d986};}finally{await _0x582043[_0x1b3323(0x116)]();}}async[a0_0x409fc8(0x122)](_0x47f115,_0x46cc80={}){const _0x3cd829=a0_0x409fc8,{formats:formats=[_0x3cd829(0x13e),'text'],agentId:_0x4c085e}=_0x46cc80;await this[_0x3cd829(0x140)](),this[_0x3cd829(0x11a)]?.[_0x3cd829(0x194)](_0x3cd829(0x16b),{'url':_0x47f115,'formats':formats,'agentId':_0x4c085e});const _0x1c1b8e=await this['browser']['newPage']();try{const _0x599aee=[];formats['includes']('console')&&_0x1c1b8e['on'](_0x3cd829(0x18a),_0x28cb26=>{const _0x4f072b=_0x3cd829;_0x599aee[_0x4f072b(0x131)]({'type':_0x28cb26[_0x4f072b(0x15d)](),'text':_0x28cb26[_0x4f072b(0x12c)]()});});await _0x1c1b8e[_0x3cd829(0x126)](_0x47f115,{'waitUntil':'networkidle2','timeout':this['DEFAULT_TIMEOUT']});const _0x4ff901={'url':_0x47f115};for(const _0x5ab41c of formats){switch(_0x5ab41c){case _0x3cd829(0x13e):_0x4ff901[_0x3cd829(0x13e)]=await _0x1c1b8e['title']();break;case _0x3cd829(0x12c):_0x4ff901['text']=await _0x1c1b8e[_0x3cd829(0x193)](()=>document[_0x3cd829(0x1bc)]['innerText']);break;case'links':_0x4ff901['links']=await _0x1c1b8e['evaluate'](()=>{const _0x442718=_0x3cd829;return Array['from'](document['querySelectorAll']('a[href]'))['map'](_0x3f4d78=>({'href':_0x3f4d78[_0x442718(0x15e)],'text':_0x3f4d78['textContent'][_0x442718(0x14c)]()}));});break;case _0x3cd829(0x18f):_0x4ff901[_0x3cd829(0x18f)]=await _0x1c1b8e[_0x3cd829(0x138)]();break;case _0x3cd829(0x18a):_0x4ff901[_0x3cd829(0x18e)]=_0x599aee;break;}}return this[_0x3cd829(0x11a)]?.['info']('Fetch\x20completed',{'url':_0x47f115,'formats':formats,'agentId':_0x4c085e}),{'success':!![],..._0x4ff901};}finally{await _0x1c1b8e['close']();}}async['interactive'](_0x4f1fed,_0x47f3c4={}){const _0x51acff=a0_0x409fc8,{headless:headless=!![],agentId:_0x22c6d9,context:_0x5eb35f}=_0x47f3c4;await this['ensureBrowser'](),this['logger']?.[_0x51acff(0x194)](_0x51acff(0x139),{'actionsCount':_0x4f1fed[_0x51acff(0x172)],'headless':headless,'agentId':_0x22c6d9});const _0x11f030=[];!this[_0x51acff(0x191)]['has'](_0x22c6d9)&&this[_0x51acff(0x191)]['set'](_0x22c6d9,new Map());const _0x5bba75=this[_0x51acff(0x191)][_0x51acff(0x181)](_0x22c6d9);for(const _0x3d0aea of _0x4f1fed){try{let _0x5405ee;switch(_0x3d0aea['type']){case _0x51acff(0x137):_0x5405ee=await this['openTab'](_0x22c6d9,_0x3d0aea['name'],_0x3d0aea[_0x51acff(0x157)],headless,_0x3d0aea[_0x51acff(0x13b)],_0x5eb35f);break;case'close-tab':_0x5405ee=await this['closeTab'](_0x22c6d9,_0x3d0aea['name']);break;case'switch-tab':_0x5405ee=await this['switchTab'](_0x22c6d9,_0x3d0aea['name']);break;case _0x51acff(0x1af):_0x5405ee=await this[_0x51acff(0x134)](_0x22c6d9);break;default:_0x5405ee={'success':![],'error':'Action\x20'+_0x3d0aea['type']+'\x20must\x20be\x20executed\x20within\x20a\x20tab\x20context\x20(use\x20open-tab\x20with\x20nestedActions)'};}_0x11f030['push']({'action':_0x3d0aea['type'],..._0x5405ee});}catch(_0x2461b6){this[_0x51acff(0x11a)]?.['error'](_0x51acff(0x1aa),{'action':_0x3d0aea['type'],'error':_0x2461b6['message'],'agentId':_0x22c6d9}),_0x11f030['push']({'action':_0x3d0aea['type'],'success':![],'error':_0x2461b6['message']});}}return{'success':_0x11f030[_0x51acff(0x1b4)](_0x4c7838=>_0x4c7838['success']!==![]),'actionsExecuted':_0x11f030['length'],'results':_0x11f030};}async[a0_0x409fc8(0x12f)](_0x214188,_0x581415,_0x12af71,_0x3e20a1,_0x3dc139=[],_0x32f47={}){const _0x59ec3d=a0_0x409fc8;!this['agentTabs'][_0x59ec3d(0x12a)](_0x214188)&&this['agentTabs'][_0x59ec3d(0x13c)](_0x214188,new Map());const _0x59c5ca=this[_0x59ec3d(0x191)][_0x59ec3d(0x181)](_0x214188);if(_0x59c5ca['has'](_0x581415))throw new Error(_0x59ec3d(0x1b1)+_0x581415+_0x59ec3d(0x1b2)+_0x214188);this['logger']?.[_0x59ec3d(0x194)]('Opening\x20tab',{'agentId':_0x214188,'tabName':_0x581415,'url':_0x12af71,'headless':_0x3e20a1});const _0x29afaf=await this[_0x59ec3d(0x1b3)]['newPage']();await _0x29afaf['setViewport']({'width':0x500,'height':0x2d0});const _0x24331a=[];_0x29afaf['on']('console',_0x3acb4a=>{const _0x59382c=_0x59ec3d;_0x24331a[_0x59382c(0x131)]({'type':_0x3acb4a['type'](),'text':_0x3acb4a[_0x59382c(0x12c)](),'timestamp':Date['now']()});});const _0x5c8b45={'page':_0x29afaf,'url':_0x12af71,'lastActivity':Date['now'](),'headless':_0x3e20a1,'consoleMessages':_0x24331a,'name':_0x581415};_0x59c5ca['set'](_0x581415,_0x5c8b45);const _0x532f29=[];try{_0x12af71&&(await _0x29afaf['goto'](_0x12af71,{'waitUntil':_0x59ec3d(0x117),'timeout':this[_0x59ec3d(0x1bb)]}),_0x5c8b45['url']=_0x12af71,_0x5c8b45['lastActivity']=Date['now']());for(const _0x32dd1e of _0x3dc139){const _0xf99672=await this[_0x59ec3d(0x173)](_0x29afaf,_0x32dd1e,_0x5c8b45,_0x32f47);_0x532f29['push']({'action':_0x32dd1e['type'],..._0xf99672}),_0x5c8b45[_0x59ec3d(0x148)]=Date[_0x59ec3d(0x1be)]();}return{'success':!![],'tabName':_0x581415,'url':_0x5c8b45['url'],'actionsExecuted':_0x532f29[_0x59ec3d(0x172)],'results':_0x532f29};}catch(_0x2cb2bd){this[_0x59ec3d(0x11a)]?.['error']('Failed\x20to\x20open\x20tab',{'agentId':_0x214188,'tabName':_0x581415,'error':_0x2cb2bd['message']}),await _0x29afaf[_0x59ec3d(0x116)](),_0x59c5ca['delete'](_0x581415);throw _0x2cb2bd;}}async[a0_0x409fc8(0x173)](_0x4fb083,_0x37c02a,_0x11146b,_0x359214){const _0x499c19=a0_0x409fc8;switch(_0x37c02a[_0x499c19(0x15d)]){case'navigate':await _0x4fb083['goto'](_0x37c02a[_0x499c19(0x19a)]||_0x37c02a[_0x499c19(0x157)],{'waitUntil':'networkidle2','timeout':this[_0x499c19(0x1bb)]}),_0x11146b['url']=_0x4fb083['url']();return{'success':!![],'url':_0x11146b[_0x499c19(0x157)]};case'click':await _0x4fb083['click'](_0x37c02a[_0x499c19(0x13a)],{'button':_0x37c02a['button']||'left'});return{'success':!![],'selector':_0x37c02a[_0x499c19(0x13a)]};case'type':await _0x4fb083[_0x499c19(0x15d)](_0x37c02a['selector'],_0x37c02a['text']||_0x37c02a[_0x499c19(0x19a)]);return{'success':!![],'selector':_0x37c02a[_0x499c19(0x13a)],'text':_0x37c02a[_0x499c19(0x12c)]};case'press':await _0x4fb083[_0x499c19(0x141)][_0x499c19(0x11f)](_0x37c02a['key']||_0x37c02a['value']);return{'success':!![],'key':_0x37c02a[_0x499c19(0x146)]};case _0x499c19(0x1b0):const _0x3bbbc2=_0x37c02a[_0x499c19(0x132)]?parseInt(_0x37c02a[_0x499c19(0x132)],0xa):this[_0x499c19(0x1bb)];await _0x4fb083['waitForSelector'](_0x37c02a[_0x499c19(0x13a)],{'timeout':_0x3bbbc2});return{'success':!![],'selector':_0x37c02a['selector']};case _0x499c19(0x19b):return await this[_0x499c19(0x17e)](_0x4fb083,_0x37c02a,_0x359214);case _0x499c19(0x16f):return await this[_0x499c19(0x13f)](_0x4fb083,_0x37c02a[_0x499c19(0x19a)],_0x359214);case'extract-text':const _0x2f0ea1=await _0x4fb083['evaluate'](_0x1da18b=>{const _0x2f8a7c=_0x499c19,_0x2878b5=document['querySelector'](_0x1da18b);return _0x2878b5?_0x2878b5[_0x2f8a7c(0x167)]:null;},_0x37c02a[_0x499c19(0x13a)]);return{'success':!![],'selector':_0x37c02a[_0x499c19(0x13a)],'text':_0x2f0ea1};case'extract-links':const _0x43a237=await _0x4fb083[_0x499c19(0x193)](_0x129abf=>{const _0x3aefcc=_0x499c19,_0x482e2a=document[_0x3aefcc(0x14e)](_0x129abf);return Array['from'](_0x482e2a)['map'](_0x582f2a=>({'href':_0x582f2a[_0x3aefcc(0x15e)],'text':_0x582f2a['textContent']['trim']()}));},_0x37c02a['selector']);return{'success':!![],'selector':_0x37c02a['selector'],'links':_0x43a237};case _0x499c19(0x19c):const _0x267b35=await _0x4fb083['content']();return{'success':!![],'html':_0x267b35};case'get-console':return{'success':!![],'consoleMessages':[..._0x11146b['consoleMessages']]};case'scroll':await _0x4fb083['evaluate'](_0x17f7bc=>{const _0x4e504e=_0x499c19;_0x17f7bc?document[_0x4e504e(0x187)](_0x17f7bc)?.[_0x4e504e(0x174)]():window[_0x4e504e(0x158)](0x0,document['body']['scrollHeight']);},_0x37c02a[_0x499c19(0x13a)]);return{'success':!![]};case'hover':await _0x4fb083[_0x499c19(0x182)](_0x37c02a['selector']);return{'success':!![],'selector':_0x37c02a['selector']};case'mouse-move':await _0x4fb083[_0x499c19(0x182)](_0x37c02a['selector']);return{'success':!![],'selector':_0x37c02a[_0x499c19(0x13a)]};default:throw new Error(_0x499c19(0x198)+_0x37c02a[_0x499c19(0x15d)]);}}async['takeScreenshot'](_0xb7e772,_0x1e9c6d,_0x14de72){const _0x475365=a0_0x409fc8,_0x440419=_0x1e9c6d[_0x475365(0x147)]||'file',_0x366443=_0x1e9c6d['path'];if(_0x440419==='base64'){const _0x52774b=await _0xb7e772[_0x475365(0x19b)]({'encoding':'base64'});return{'success':!![],'format':_0x475365(0x14d),'screenshot':_0x52774b};}let _0x35eee9;if(_0x366443){const _0x5cc8f6=_0x14de72['directoryAccess']?.[_0x475365(0x177)]||_0x14de72['projectDir']||process[_0x475365(0x125)]();_0x35eee9=a0_0xeef28c['isAbsolute'](_0x366443)?_0x366443:a0_0xeef28c['join'](_0x5cc8f6,_0x366443);}else{const _0x34dc13=_0x475365(0x188)+Date['now']()+'.png';_0x35eee9=a0_0xeef28c[_0x475365(0x197)](this['TEMP_DIR'],_0x34dc13);}return await _0xb7e772['screenshot']({'path':_0x35eee9}),{'success':!![],'format':_0x475365(0x121),'path':_0x35eee9};}async[a0_0x409fc8(0x13f)](_0x496a7a,_0x4402e0,_0x3555d3){const _0x491792=a0_0x409fc8,_0x19c726=await _0x496a7a['screenshot']({'encoding':_0x491792(0x14d)}),_0x46443b=_0x3555d3['aiService'];if(!_0x46443b)throw new Error('AI\x20service\x20not\x20available\x20for\x20screenshot\x20analysis');this['logger']?.[_0x491792(0x194)](_0x491792(0x149),{'question':_0x4402e0['substring'](0x0,0x64),'agentId':_0x3555d3[_0x491792(0x176)]});try{const _0x43232c='o3',_0x1be0f0=await _0x46443b['sendMessage'](_0x43232c,_0x4402e0,{'agentId':_0x3555d3[_0x491792(0x176)],'images':[_0x491792(0x1a8)+_0x19c726],'apiKey':_0x3555d3[_0x491792(0x1a9)],'customApiKeys':_0x3555d3['customApiKeys'],'platformProvided':_0x3555d3[_0x491792(0x17f)]});return{'success':!![],'question':_0x4402e0,'analysis':_0x1be0f0['content'],'model':_0x1be0f0[_0x491792(0x124)]||_0x43232c};}catch(_0x18c3a6){this['logger']?.[_0x491792(0x179)]('Screenshot\x20analysis\x20failed',{'error':_0x18c3a6[_0x491792(0x11c)],'agentId':_0x3555d3['agentId']});throw new Error('Screenshot\x20analysis\x20failed:\x20'+_0x18c3a6[_0x491792(0x11c)]);}}async[a0_0x409fc8(0x11b)](_0x4f2fa6,_0x1ebf95){const _0x380262=a0_0x409fc8,_0x1fa482=this[_0x380262(0x191)]['get'](_0x4f2fa6);if(!_0x1fa482||!_0x1fa482[_0x380262(0x12a)](_0x1ebf95))throw new Error('Tab\x20\x27'+_0x1ebf95+'\x27\x20not\x20found\x20for\x20agent\x20'+_0x4f2fa6);const _0x4c8241=_0x1fa482[_0x380262(0x181)](_0x1ebf95);return this['logger']?.['info']('Closing\x20tab',{'agentId':_0x4f2fa6,'tabName':_0x1ebf95}),await _0x4c8241['page'][_0x380262(0x116)](),_0x1fa482[_0x380262(0x136)](_0x1ebf95),{'success':!![],'tabName':_0x1ebf95,'message':_0x380262(0x1b1)+_0x1ebf95+'\x27\x20closed'};}async[a0_0x409fc8(0x115)](_0x3bfdaf,_0x313da0){const _0x233136=a0_0x409fc8,_0x5c290e=this['agentTabs'][_0x233136(0x181)](_0x3bfdaf);if(!_0x5c290e||!_0x5c290e['has'](_0x313da0))throw new Error('Tab\x20\x27'+_0x313da0+'\x27\x20not\x20found\x20for\x20agent\x20'+_0x3bfdaf);const _0x18d755=_0x5c290e['get'](_0x313da0);return _0x18d755['lastActivity']=Date[_0x233136(0x1be)](),{'success':!![],'tabName':_0x313da0,'url':_0x18d755['url'],'message':_0x233136(0x1b6)+_0x313da0+'\x27'};}async[a0_0x409fc8(0x134)](_0x4b0a6f){const _0x33578b=a0_0x409fc8,_0x343e9b=this['agentTabs']['get'](_0x4b0a6f);if(!_0x343e9b||_0x343e9b[_0x33578b(0x1a6)]===0x0)return{'success':!![],'tabCount':0x0,'tabs':[],'message':'No\x20active\x20tabs'};const _0x45be7c=[];for(const [_0x1e1583,_0x1ab9a2]of _0x343e9b['entries']()){_0x45be7c[_0x33578b(0x131)]({'name':_0x1e1583,'url':_0x1ab9a2['url'],'idleTime':Date['now']()-_0x1ab9a2[_0x33578b(0x148)],'headless':_0x1ab9a2['headless']});}return{'success':!![],'tabCount':_0x45be7c[_0x33578b(0x172)],'tabs':_0x45be7c};}[a0_0x409fc8(0x15b)](){const _0x497f44=a0_0x409fc8;this[_0x497f44(0x14b)]&&clearInterval(this['cleanupTimer']),this[_0x497f44(0x14b)]=setInterval(()=>{const _0x32f6d6=_0x497f44;this[_0x32f6d6(0x1a0)]();},this['CLEANUP_INTERVAL']);}async['cleanupIdleTabs'](){const _0x5b526f=a0_0x409fc8,_0x8af9a2=Date[_0x5b526f(0x1be)](),_0x468233=[];for(const [_0x3d2e07,_0x271c0a]of this['agentTabs'][_0x5b526f(0x169)]()){for(const [_0x31f81d,_0x272a04]of _0x271c0a['entries']()){const _0xa16ad4=_0x8af9a2-_0x272a04['lastActivity'];_0xa16ad4>this['TAB_IDLE_TIMEOUT']&&_0x468233['push']({'agentId':_0x3d2e07,'tabName':_0x31f81d,'tabInfo':_0x272a04});}}if(_0x468233['length']>0x0){this['logger']?.['info']('Cleaning\x20up\x20idle\x20tabs',{'count':_0x468233['length']});for(const {agentId:_0x185bdc,tabName:_0x49c03c,tabInfo:_0x234ddf}of _0x468233){try{await _0x234ddf[_0x5b526f(0x175)][_0x5b526f(0x116)](),this[_0x5b526f(0x191)]['get'](_0x185bdc)[_0x5b526f(0x136)](_0x49c03c),this['logger']?.[_0x5b526f(0x161)](_0x5b526f(0x160),{'agentId':_0x185bdc,'tabName':_0x49c03c});}catch(_0x166d1b){this['logger']?.['error'](_0x5b526f(0x11d),{'agentId':_0x185bdc,'tabName':_0x49c03c,'error':_0x166d1b['message']});}}}}async[a0_0x409fc8(0x189)](_0x10b04f){const _0x28e36c=a0_0x409fc8,_0x4d4dab=this[_0x28e36c(0x191)]['get'](_0x10b04f);if(!_0x4d4dab)return{'success':!![],'agentId':_0x10b04f,'closedTabs':0x0,'message':_0x28e36c(0x129)};this[_0x28e36c(0x11a)]?.[_0x28e36c(0x194)]('Cleaning\x20up\x20agent\x20tabs',{'agentId':_0x10b04f,'tabCount':_0x4d4dab[_0x28e36c(0x1a6)]});let _0x36821c=0x0;for(const [_0x4dedc3,_0x58607e]of _0x4d4dab[_0x28e36c(0x169)]()){try{await _0x58607e['page'][_0x28e36c(0x116)](),_0x36821c++;}catch(_0x241bde){this[_0x28e36c(0x11a)]?.[_0x28e36c(0x179)]('Failed\x20to\x20close\x20tab\x20during\x20cleanup',{'agentId':_0x10b04f,'tabName':_0x4dedc3,'error':_0x241bde['message']});}}return this['agentTabs'][_0x28e36c(0x136)](_0x10b04f),{'success':!![],'agentId':_0x10b04f,'closedTabs':_0x36821c,'message':'Closed\x20'+_0x36821c+'\x20tabs\x20for\x20agent\x20'+_0x10b04f};}async['ensureTempDir'](){const _0x263fd4=a0_0x409fc8;try{await a0_0x450aef[_0x263fd4(0x18b)](this['TEMP_DIR'],{'recursive':!![]});}catch(_0x26262b){this[_0x263fd4(0x11a)]?.['warn'](_0x263fd4(0x133),{'path':this[_0x263fd4(0x1a3)],'error':_0x26262b[_0x263fd4(0x11c)]});}}async['cleanup'](){const _0x218e11=a0_0x409fc8;this[_0x218e11(0x14b)]&&(clearInterval(this[_0x218e11(0x14b)]),this['cleanupTimer']=null);for(const [_0x1cb819]of this[_0x218e11(0x191)][_0x218e11(0x169)]()){await this[_0x218e11(0x189)](_0x1cb819);}this['browser']&&(await this['browser']['close'](),this['browser']=null);try{await a0_0x450aef['rm'](this['TEMP_DIR'],{'recursive':!![],'force':!![]});}catch(_0x3fb471){this['logger']?.[_0x218e11(0x1a5)](_0x218e11(0x17c),{'path':this['TEMP_DIR'],'error':_0x3fb471['message']});}}}function a0_0x44f7(_0x3a4a50,_0x3ba84a){_0x3a4a50=_0x3a4a50-0x113;const _0x5e70bf=a0_0x5e70();let _0x44f71d=_0x5e70bf[_0x3a4a50];if(a0_0x44f7['yFtHPA']===undefined){var _0x47e279=function(_0x21753f){const _0xb54f0f='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x41a119='',_0x1adca6='';for(let _0xeef28c=0x0,_0x450aef,_0x261e4f,_0xcef85a=0x0;_0x261e4f=_0x21753f['charAt'](_0xcef85a++);~_0x261e4f&&(_0x450aef=_0xeef28c%0x4?_0x450aef*0x40+_0x261e4f:_0x261e4f,_0xeef28c++%0x4)?_0x41a119+=String['fromCharCode'](0xff&_0x450aef>>(-0x2*_0xeef28c&0x6)):0x0){_0x261e4f=_0xb54f0f['indexOf'](_0x261e4f);}for(let _0x54fda7=0x0,_0x1f81fd=_0x41a119['length'];_0x54fda7<_0x1f81fd;_0x54fda7++){_0x1adca6+='%'+('00'+_0x41a119['charCodeAt'](_0x54fda7)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x1adca6);};a0_0x44f7['fKDzgx']=_0x47e279,a0_0x44f7['EumYpg']={},a0_0x44f7['yFtHPA']=!![];}const _0x4dd58c=_0x5e70bf[0x0],_0x49c895=_0x3a4a50+_0x4dd58c,_0x4f9805=a0_0x44f7['EumYpg'][_0x49c895];return!_0x4f9805?(_0x44f71d=a0_0x44f7['fKDzgx'](_0x44f71d),a0_0x44f7['EumYpg'][_0x49c895]=_0x44f71d):_0x44f71d=_0x4f9805,_0x44f71d;}export default WebTool;
|