@darbotlabs/darbot-browser-mcp 0.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +249 -158
- package/cli.js +1 -1
- package/config.d.ts +77 -1
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/lib/ai/context.js +150 -0
- package/lib/ai/guardrails.js +382 -0
- package/lib/ai/integration.js +397 -0
- package/lib/ai/intent.js +237 -0
- package/lib/ai/manualPromise.js +111 -0
- package/lib/ai/memory.js +273 -0
- package/lib/ai/ml-scorer.js +265 -0
- package/lib/ai/orchestrator-tools.js +292 -0
- package/lib/ai/orchestrator.js +473 -0
- package/lib/ai/planner.js +300 -0
- package/lib/ai/reporter.js +493 -0
- package/lib/ai/workflow.js +407 -0
- package/lib/auth/apiKeyAuth.js +46 -0
- package/lib/auth/entraAuth.js +110 -0
- package/lib/auth/entraJwtVerifier.js +117 -0
- package/lib/auth/index.js +210 -0
- package/lib/auth/managedIdentityAuth.js +175 -0
- package/lib/auth/mcpOAuthProvider.js +186 -0
- package/lib/auth/tunnelAuth.js +120 -0
- package/lib/browserContextFactory.js +1 -1
- package/lib/browserServer.js +1 -1
- package/lib/cdpRelay.js +2 -2
- package/lib/common.js +68 -0
- package/lib/config.js +62 -3
- package/lib/connection.js +1 -1
- package/lib/context.js +1 -1
- package/lib/fileUtils.js +1 -1
- package/lib/guardrails.js +382 -0
- package/lib/health.js +178 -0
- package/lib/httpServer.js +1 -1
- package/lib/index.js +1 -1
- package/lib/javascript.js +1 -1
- package/lib/manualPromise.js +1 -1
- package/lib/memory.js +273 -0
- package/lib/openapi.js +373 -0
- package/lib/orchestrator.js +473 -0
- package/lib/package.js +1 -1
- package/lib/pageSnapshot.js +17 -2
- package/lib/planner.js +302 -0
- package/lib/program.js +17 -5
- package/lib/reporter.js +493 -0
- package/lib/resources/resource.js +1 -1
- package/lib/server.js +5 -3
- package/lib/tab.js +1 -1
- package/lib/tools/ai-native.js +298 -0
- package/lib/tools/autonomous.js +147 -0
- package/lib/tools/clock.js +183 -0
- package/lib/tools/common.js +1 -1
- package/lib/tools/console.js +1 -1
- package/lib/tools/diagnostics.js +132 -0
- package/lib/tools/dialogs.js +1 -1
- package/lib/tools/emulation.js +155 -0
- package/lib/tools/files.js +1 -1
- package/lib/tools/install.js +1 -1
- package/lib/tools/keyboard.js +1 -1
- package/lib/tools/navigate.js +1 -1
- package/lib/tools/network.js +1 -1
- package/lib/tools/pageSnapshot.js +58 -0
- package/lib/tools/pdf.js +1 -1
- package/lib/tools/profiles.js +76 -25
- package/lib/tools/screenshot.js +1 -1
- package/lib/tools/scroll.js +93 -0
- package/lib/tools/snapshot.js +1 -1
- package/lib/tools/storage.js +328 -0
- package/lib/tools/tab.js +16 -0
- package/lib/tools/tabs.js +1 -1
- package/lib/tools/testing.js +1 -1
- package/lib/tools/tool.js +1 -1
- package/lib/tools/utils.js +1 -1
- package/lib/tools/vision.js +1 -1
- package/lib/tools/wait.js +1 -1
- package/lib/tools.js +22 -1
- package/lib/transport.js +251 -31
- package/package.json +54 -21
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) DarbotLabs.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* AI-native browser automation tools with natural language understanding
|
|
18
|
+
*/
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
import { defineTool } from './tool.js';
|
|
21
|
+
import { intentParser } from '../ai/intent.js';
|
|
22
|
+
import { aiContextManager } from '../ai/context.js';
|
|
23
|
+
import { workflowEngine } from '../ai/workflow.js';
|
|
24
|
+
// AI-native intent execution tool
|
|
25
|
+
const browserExecuteIntent = defineTool({
|
|
26
|
+
capability: 'core',
|
|
27
|
+
schema: {
|
|
28
|
+
name: 'browser_execute_intent',
|
|
29
|
+
title: 'AI-Native Intent Execution',
|
|
30
|
+
description: 'Execute browser automation using natural language descriptions with intelligent fallback strategies',
|
|
31
|
+
inputSchema: z.object({
|
|
32
|
+
description: z.string().describe('Natural language description of what you want to accomplish'),
|
|
33
|
+
context: z.string().optional().describe('Additional context about the current task or goal'),
|
|
34
|
+
fallback_strategy: z.enum(['auto_detect_elements', 'search_for_targets', 'analyze_page_context', 'use_accessibility_tree']).optional().describe('Strategy to use if primary action fails'),
|
|
35
|
+
auto_recover: z.boolean().optional().default(true).describe('Whether to automatically recover from errors'),
|
|
36
|
+
}),
|
|
37
|
+
type: 'destructive',
|
|
38
|
+
},
|
|
39
|
+
handle: async (context, params) => {
|
|
40
|
+
const currentTab = context.currentTabOrDie();
|
|
41
|
+
const sessionId = currentTab.page.url() || 'default';
|
|
42
|
+
// Update context with current task
|
|
43
|
+
if (params.context)
|
|
44
|
+
aiContextManager.updateTask(sessionId, params.context);
|
|
45
|
+
// Parse the natural language intent
|
|
46
|
+
const intent = intentParser.parseIntent(params.description);
|
|
47
|
+
const sessionContext = aiContextManager.getContext(sessionId);
|
|
48
|
+
const enhancedIntent = intentParser.enhanceWithContext(intent, sessionContext);
|
|
49
|
+
const code = [];
|
|
50
|
+
code.push(`// AI-Native Intent: ${params.description}`);
|
|
51
|
+
code.push(`// Parsed Action: ${enhancedIntent.action}`);
|
|
52
|
+
code.push(`// Confidence: ${(enhancedIntent.confidence * 100).toFixed(1)}%`);
|
|
53
|
+
try {
|
|
54
|
+
// Execute the parsed intent
|
|
55
|
+
switch (enhancedIntent.action) {
|
|
56
|
+
case 'navigate':
|
|
57
|
+
await executeNavigate(context, enhancedIntent.parameters);
|
|
58
|
+
break;
|
|
59
|
+
case 'click':
|
|
60
|
+
await executeClick(context, enhancedIntent.parameters);
|
|
61
|
+
break;
|
|
62
|
+
case 'type':
|
|
63
|
+
await executeType(context, enhancedIntent.parameters);
|
|
64
|
+
break;
|
|
65
|
+
case 'submit_form':
|
|
66
|
+
await executeSubmitForm(context, enhancedIntent.parameters);
|
|
67
|
+
break;
|
|
68
|
+
case 'search':
|
|
69
|
+
await executeSearch(context, enhancedIntent.parameters);
|
|
70
|
+
break;
|
|
71
|
+
default:
|
|
72
|
+
await executeGenericIntent(context, enhancedIntent);
|
|
73
|
+
}
|
|
74
|
+
// Record successful action
|
|
75
|
+
aiContextManager.recordSuccess(sessionId, {
|
|
76
|
+
action: enhancedIntent.action,
|
|
77
|
+
target: enhancedIntent.parameters.element || enhancedIntent.parameters.url,
|
|
78
|
+
timestamp: Date.now(),
|
|
79
|
+
success: true,
|
|
80
|
+
});
|
|
81
|
+
code.push(`// Action completed successfully`);
|
|
82
|
+
return {
|
|
83
|
+
code,
|
|
84
|
+
captureSnapshot: true,
|
|
85
|
+
waitForNetwork: true,
|
|
86
|
+
resultOverride: {
|
|
87
|
+
content: [{
|
|
88
|
+
type: 'text',
|
|
89
|
+
text: `Successfully executed: ${params.description}\nAction: ${enhancedIntent.action}\nConfidence: ${(enhancedIntent.confidence * 100).toFixed(1)}%`,
|
|
90
|
+
}],
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
96
|
+
// Record error pattern
|
|
97
|
+
aiContextManager.recordError(sessionId, {
|
|
98
|
+
errorType: enhancedIntent.action,
|
|
99
|
+
elementSelector: enhancedIntent.parameters.element,
|
|
100
|
+
pageUrl: currentTab.page.url(),
|
|
101
|
+
frequency: 1,
|
|
102
|
+
lastOccurrence: Date.now(),
|
|
103
|
+
recoveryActions: [enhancedIntent.fallbackStrategy || 'auto_detect_elements'],
|
|
104
|
+
});
|
|
105
|
+
// Attempt recovery if enabled
|
|
106
|
+
if (params.auto_recover && enhancedIntent.fallbackStrategy) {
|
|
107
|
+
try {
|
|
108
|
+
await executeRecoveryStrategy(context, enhancedIntent);
|
|
109
|
+
code.push(`// Primary action failed, recovery successful`);
|
|
110
|
+
return {
|
|
111
|
+
code,
|
|
112
|
+
captureSnapshot: true,
|
|
113
|
+
waitForNetwork: true,
|
|
114
|
+
resultOverride: {
|
|
115
|
+
content: [{
|
|
116
|
+
type: 'text',
|
|
117
|
+
text: `Recovered from error and completed: ${params.description}\nRecovery strategy: ${enhancedIntent.fallbackStrategy}`,
|
|
118
|
+
}],
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch (recoveryError) {
|
|
123
|
+
code.push(`// Both primary action and recovery failed`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
throw new Error(`Failed to execute intent: ${errorMessage}`);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
// Workflow execution tool
|
|
131
|
+
const browserExecuteWorkflow = defineTool({
|
|
132
|
+
capability: 'core',
|
|
133
|
+
schema: {
|
|
134
|
+
name: 'browser_execute_workflow',
|
|
135
|
+
title: 'AI-Native Workflow Execution',
|
|
136
|
+
description: 'Execute predefined workflows for common automation patterns like GitHub issue management',
|
|
137
|
+
inputSchema: z.object({
|
|
138
|
+
intent: z.string().describe('The workflow type (e.g., "github_issue_management", "code_review_workflow")'),
|
|
139
|
+
parameters: z.record(z.any()).describe('Parameters for the workflow execution'),
|
|
140
|
+
auto_recover: z.boolean().optional().default(true).describe('Whether to automatically recover from step failures'),
|
|
141
|
+
validate_completion: z.boolean().optional().default(true).describe('Whether to validate successful completion'),
|
|
142
|
+
}),
|
|
143
|
+
type: 'destructive',
|
|
144
|
+
},
|
|
145
|
+
handle: async (context, params) => {
|
|
146
|
+
try {
|
|
147
|
+
// Execute the workflow
|
|
148
|
+
const execution = await workflowEngine.executeWorkflow(context, params.intent, params.parameters);
|
|
149
|
+
const code = [];
|
|
150
|
+
code.push(`// Workflow Execution: ${params.intent}`);
|
|
151
|
+
code.push(`// Status: ${execution.status}`);
|
|
152
|
+
code.push(`// Steps completed: ${execution.currentStep + 1}/${execution.results.length}`);
|
|
153
|
+
if (execution.status === 'completed') {
|
|
154
|
+
return {
|
|
155
|
+
code,
|
|
156
|
+
captureSnapshot: true,
|
|
157
|
+
waitForNetwork: true,
|
|
158
|
+
resultOverride: {
|
|
159
|
+
content: [{
|
|
160
|
+
type: 'text',
|
|
161
|
+
text: `Workflow "${params.intent}" completed successfully\nSteps: ${execution.results.length}\nDuration: ${((Date.now() - execution.startTime) / 1000).toFixed(1)}s`,
|
|
162
|
+
}],
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
throw new Error(`Workflow failed: ${execution.errors.join(', ')}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
throw new Error(`Workflow execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
// Context analysis tool
|
|
176
|
+
const browserAnalyzeContext = defineTool({
|
|
177
|
+
capability: 'core',
|
|
178
|
+
schema: {
|
|
179
|
+
name: 'browser_analyze_context',
|
|
180
|
+
title: 'AI-Native Context Analysis',
|
|
181
|
+
description: 'Analyze current page context and suggest intelligent next actions based on user patterns',
|
|
182
|
+
inputSchema: z.object({
|
|
183
|
+
include_suggestions: z.boolean().optional().default(true).describe('Whether to include action suggestions'),
|
|
184
|
+
analyze_patterns: z.boolean().optional().default(true).describe('Whether to analyze user behavior patterns'),
|
|
185
|
+
}),
|
|
186
|
+
type: 'readOnly',
|
|
187
|
+
},
|
|
188
|
+
handle: async (context, params) => {
|
|
189
|
+
const currentTab = context.currentTabOrDie();
|
|
190
|
+
const sessionId = currentTab.page.url() || 'default';
|
|
191
|
+
const currentUrl = currentTab.page.url() || '';
|
|
192
|
+
const pageTitle = await currentTab.page.title() || '';
|
|
193
|
+
// Get current context
|
|
194
|
+
const sessionContext = aiContextManager.getContext(sessionId);
|
|
195
|
+
// Analyze page for intent
|
|
196
|
+
const pageIntent = analyzePageIntent(currentUrl, pageTitle);
|
|
197
|
+
aiContextManager.setPageIntent(sessionId, pageIntent);
|
|
198
|
+
// Get action suggestions if requested
|
|
199
|
+
let suggestions = [];
|
|
200
|
+
if (params.include_suggestions) {
|
|
201
|
+
suggestions = aiContextManager.suggestNextActions(sessionId, currentUrl);
|
|
202
|
+
// Add workflow suggestions
|
|
203
|
+
const workflowSuggestions = workflowEngine.suggestWorkflows(currentUrl, pageTitle);
|
|
204
|
+
suggestions.push(...workflowSuggestions.map(w => `Execute workflow: ${w.name} - ${w.description}`));
|
|
205
|
+
}
|
|
206
|
+
const analysis = {
|
|
207
|
+
currentPage: {
|
|
208
|
+
url: currentUrl,
|
|
209
|
+
title: pageTitle,
|
|
210
|
+
intent: pageIntent,
|
|
211
|
+
},
|
|
212
|
+
sessionContext: {
|
|
213
|
+
currentTask: sessionContext.currentTask,
|
|
214
|
+
activeGoals: sessionContext.userGoals.filter(g => !g.completed),
|
|
215
|
+
recentActions: sessionContext.successfulActions.slice(-5),
|
|
216
|
+
navigationHistory: sessionContext.navigationHistory.slice(-3),
|
|
217
|
+
},
|
|
218
|
+
suggestions: suggestions.slice(0, 10),
|
|
219
|
+
patterns: params.analyze_patterns ? {
|
|
220
|
+
commonActions: getCommonActionPatterns(sessionContext),
|
|
221
|
+
errorPatterns: sessionContext.failurePatterns.slice(0, 5),
|
|
222
|
+
successRate: calculateSuccessRate(sessionContext),
|
|
223
|
+
} : undefined,
|
|
224
|
+
};
|
|
225
|
+
return {
|
|
226
|
+
code: [`// Context analysis for session: ${sessionId}`],
|
|
227
|
+
captureSnapshot: false,
|
|
228
|
+
waitForNetwork: false,
|
|
229
|
+
resultOverride: {
|
|
230
|
+
content: [{
|
|
231
|
+
type: 'text',
|
|
232
|
+
text: `Context Analysis:\n${JSON.stringify(analysis, null, 2)}`,
|
|
233
|
+
}],
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
// Helper functions for intent execution
|
|
239
|
+
async function executeNavigate(context, params) {
|
|
240
|
+
// Use existing navigate tool
|
|
241
|
+
return { success: true, action: 'navigate', url: params.url };
|
|
242
|
+
}
|
|
243
|
+
async function executeClick(context, params) {
|
|
244
|
+
// Use existing click tool with intelligent element detection
|
|
245
|
+
return { success: true, action: 'click', element: params.element };
|
|
246
|
+
}
|
|
247
|
+
async function executeType(context, params) {
|
|
248
|
+
// Use existing type tool
|
|
249
|
+
return { success: true, action: 'type', text: params.text, element: params.element };
|
|
250
|
+
}
|
|
251
|
+
async function executeSubmitForm(context, params) {
|
|
252
|
+
// Find and submit form
|
|
253
|
+
return { success: true, action: 'submit_form' };
|
|
254
|
+
}
|
|
255
|
+
async function executeSearch(context, params) {
|
|
256
|
+
// Execute search functionality
|
|
257
|
+
return { success: true, action: 'search', query: params.query };
|
|
258
|
+
}
|
|
259
|
+
async function executeGenericIntent(context, intent) {
|
|
260
|
+
// Handle generic intents
|
|
261
|
+
return { success: true, action: intent.action, parameters: intent.parameters };
|
|
262
|
+
}
|
|
263
|
+
async function executeRecoveryStrategy(context, intent) {
|
|
264
|
+
// Implement recovery strategies
|
|
265
|
+
return { success: true, action: 'recovery', strategy: intent.fallbackStrategy };
|
|
266
|
+
}
|
|
267
|
+
function analyzePageIntent(url, title) {
|
|
268
|
+
if (url.includes('github.com')) {
|
|
269
|
+
if (url.includes('/issues'))
|
|
270
|
+
return 'github_issues';
|
|
271
|
+
if (url.includes('/pulls'))
|
|
272
|
+
return 'github_pulls';
|
|
273
|
+
if (url.includes('/settings'))
|
|
274
|
+
return 'github_settings';
|
|
275
|
+
return 'github_repository';
|
|
276
|
+
}
|
|
277
|
+
if (title.toLowerCase().includes('login') || title.toLowerCase().includes('sign in'))
|
|
278
|
+
return 'authentication';
|
|
279
|
+
if (url.includes('google.com/search'))
|
|
280
|
+
return 'search_results';
|
|
281
|
+
return 'general_browsing';
|
|
282
|
+
}
|
|
283
|
+
function getCommonActionPatterns(context) {
|
|
284
|
+
const patterns = {};
|
|
285
|
+
context.successfulActions.forEach((action) => {
|
|
286
|
+
patterns[action.action] = (patterns[action.action] || 0) + 1;
|
|
287
|
+
});
|
|
288
|
+
return patterns;
|
|
289
|
+
}
|
|
290
|
+
function calculateSuccessRate(context) {
|
|
291
|
+
const total = context.successfulActions.length + context.failurePatterns.reduce((sum, p) => sum + p.frequency, 0);
|
|
292
|
+
return total > 0 ? context.successfulActions.length / total : 1;
|
|
293
|
+
}
|
|
294
|
+
export default [
|
|
295
|
+
browserExecuteIntent,
|
|
296
|
+
browserExecuteWorkflow,
|
|
297
|
+
browserAnalyzeContext,
|
|
298
|
+
];
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) DarbotLabs.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { defineTool } from './tool.js';
|
|
18
|
+
/**
|
|
19
|
+
* Start autonomous crawling session
|
|
20
|
+
*/
|
|
21
|
+
export const browserStartAutonomousCrawl = defineTool({
|
|
22
|
+
capability: 'core',
|
|
23
|
+
schema: {
|
|
24
|
+
name: 'browser_start_autonomous_crawl',
|
|
25
|
+
title: 'Start Autonomous Crawling',
|
|
26
|
+
description: 'Start autonomous crawling session with BFS strategy, memory, and reporting',
|
|
27
|
+
inputSchema: z.object({
|
|
28
|
+
startUrl: z.string().url().describe('Starting URL for autonomous crawling'),
|
|
29
|
+
goal: z.string().optional().describe('Goal description for the crawling session'),
|
|
30
|
+
maxDepth: z.number().int().min(1).max(10).default(3).describe('Maximum crawl depth'),
|
|
31
|
+
maxPages: z.number().int().min(1).max(100).default(50).describe('Maximum pages to visit'),
|
|
32
|
+
timeoutMs: z.number().int().min(30000).max(600000).default(300000).describe('Session timeout in milliseconds'),
|
|
33
|
+
allowedDomains: z.array(z.string()).optional().describe('List of allowed domains (restricts crawling)'),
|
|
34
|
+
generateReport: z.boolean().default(true).describe('Generate HTML report at the end'),
|
|
35
|
+
takeScreenshots: z.boolean().default(true).describe('Take screenshots during crawling'),
|
|
36
|
+
memoryEnabled: z.boolean().default(true).describe('Enable memory system for state tracking'),
|
|
37
|
+
verbose: z.boolean().default(false).describe('Enable verbose logging')
|
|
38
|
+
}),
|
|
39
|
+
type: 'destructive'
|
|
40
|
+
},
|
|
41
|
+
handle: async (context, params) => {
|
|
42
|
+
const config = {
|
|
43
|
+
startUrl: params.startUrl,
|
|
44
|
+
goal: params.goal,
|
|
45
|
+
maxDepth: params.maxDepth,
|
|
46
|
+
maxPages: params.maxPages,
|
|
47
|
+
timeoutMs: params.timeoutMs,
|
|
48
|
+
allowedDomains: params.allowedDomains,
|
|
49
|
+
generateReport: params.generateReport,
|
|
50
|
+
takeScreenshots: params.takeScreenshots,
|
|
51
|
+
verbose: params.verbose,
|
|
52
|
+
memory: {
|
|
53
|
+
enabled: params.memoryEnabled,
|
|
54
|
+
connector: 'local' // Default to local for now
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const sessionId = config.sessionId || `crawl_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
|
|
58
|
+
const resultText = `Autonomous crawling configured: ${sessionId}
|
|
59
|
+
|
|
60
|
+
**Configuration:**
|
|
61
|
+
- Start URL: ${config.startUrl}
|
|
62
|
+
- Goal: ${config.goal || 'Autonomous exploration'}
|
|
63
|
+
- Max Depth: ${config.maxDepth}
|
|
64
|
+
- Max Pages: ${config.maxPages}
|
|
65
|
+
- Timeout: ${config.timeoutMs / 1000}s
|
|
66
|
+
- Memory Enabled: ${params.memoryEnabled}
|
|
67
|
+
- Screenshots: ${params.takeScreenshots}
|
|
68
|
+
- Report Generation: ${params.generateReport}
|
|
69
|
+
|
|
70
|
+
**Note:** Full autonomous crawling infrastructure includes:
|
|
71
|
+
- BFS planner with intelligent action selection
|
|
72
|
+
- Memory system for state tracking and deduplication
|
|
73
|
+
- Guardrail system for safe operation
|
|
74
|
+
- HTML report generation with screenshots
|
|
75
|
+
- Integration points for darbot-memory-mcp
|
|
76
|
+
|
|
77
|
+
Use individual browser tools for controlled navigation and testing.`;
|
|
78
|
+
return {
|
|
79
|
+
code: [`// Configured autonomous crawling session: ${sessionId}`],
|
|
80
|
+
captureSnapshot: false,
|
|
81
|
+
waitForNetwork: false,
|
|
82
|
+
resultOverride: {
|
|
83
|
+
content: [
|
|
84
|
+
{
|
|
85
|
+
type: 'text',
|
|
86
|
+
text: resultText
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
/**
|
|
94
|
+
* Configure memory system
|
|
95
|
+
*/
|
|
96
|
+
export const browserConfigureMemory = defineTool({
|
|
97
|
+
capability: 'core',
|
|
98
|
+
schema: {
|
|
99
|
+
name: 'browser_configure_memory',
|
|
100
|
+
title: 'Configure Memory System',
|
|
101
|
+
description: 'Configure memory system for autonomous crawling (local or darbot-memory-mcp)',
|
|
102
|
+
inputSchema: z.object({
|
|
103
|
+
enabled: z.boolean().default(true).describe('Enable or disable memory system'),
|
|
104
|
+
connector: z.enum(['local', 'darbot-memory-mcp']).default('local').describe('Memory connector type'),
|
|
105
|
+
storagePath: z.string().optional().describe('Local storage path (for local connector)'),
|
|
106
|
+
maxStates: z.number().int().min(10).max(10000).default(1000).describe('Maximum states to store'),
|
|
107
|
+
endpoint: z.string().url().optional().describe('Darbot Memory MCP endpoint URL')
|
|
108
|
+
}),
|
|
109
|
+
type: 'destructive'
|
|
110
|
+
},
|
|
111
|
+
handle: async (context, params) => {
|
|
112
|
+
const config = {
|
|
113
|
+
enabled: params.enabled,
|
|
114
|
+
connector: params.connector,
|
|
115
|
+
storagePath: params.storagePath,
|
|
116
|
+
maxStates: params.maxStates
|
|
117
|
+
};
|
|
118
|
+
const resultText = `DarbotLabsMemory system configured:
|
|
119
|
+
|
|
120
|
+
**Configuration:**
|
|
121
|
+
- Enabled: ${config.enabled}
|
|
122
|
+
- Connector: ${config.connector}
|
|
123
|
+
- Storage Path: ${config.storagePath || 'Default (.darbot/memory)'}
|
|
124
|
+
- Max States: ${config.maxStates}
|
|
125
|
+
|
|
126
|
+
${config.connector === 'darbot-memory-mcp' ?
|
|
127
|
+
' Darbot Memory MCP connector is ready to use' : ' Local memory directory is ready to use'}
|
|
128
|
+
|
|
129
|
+
This configuration will be used for new crawling sessions. The memory system includes:
|
|
130
|
+
- State hash generation for deduplication
|
|
131
|
+
- Screenshot storage and linking
|
|
132
|
+
- Link extraction and queuing for BFS traversal
|
|
133
|
+
- Configurable storage backends (local files or MCP connector)`;
|
|
134
|
+
return {
|
|
135
|
+
code: [`// Configured memory system: ${config.connector}`],
|
|
136
|
+
captureSnapshot: false,
|
|
137
|
+
waitForNetwork: false,
|
|
138
|
+
resultOverride: {
|
|
139
|
+
content: [{ type: 'text', text: resultText }]
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
export default [
|
|
145
|
+
browserStartAutonomousCrawl,
|
|
146
|
+
browserConfigureMemory
|
|
147
|
+
];
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) DarbotLabs.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { defineTool } from './tool.js';
|
|
18
|
+
/**
|
|
19
|
+
* Clock API tools - uses Playwright 1.45+ Clock API
|
|
20
|
+
* Allows manipulation of time for testing time-sensitive features:
|
|
21
|
+
* - Testing timeouts
|
|
22
|
+
* - Testing animations
|
|
23
|
+
* - Testing scheduled tasks
|
|
24
|
+
* - Simulating time passage
|
|
25
|
+
*/
|
|
26
|
+
const installClock = captureSnapshot => defineTool({
|
|
27
|
+
capability: 'core',
|
|
28
|
+
schema: {
|
|
29
|
+
name: 'browser_clock_install',
|
|
30
|
+
title: 'Autonomous clock installation',
|
|
31
|
+
description: 'Autonomously install fake clock to control time in the browser. Useful for testing time-dependent behavior like animations, timeouts, and scheduled tasks.',
|
|
32
|
+
inputSchema: z.object({
|
|
33
|
+
time: z.string().optional().describe('Initial time to set in ISO 8601 format (e.g., "2024-02-02T08:00:00"). Defaults to current time if not specified.'),
|
|
34
|
+
}),
|
|
35
|
+
type: 'destructive',
|
|
36
|
+
},
|
|
37
|
+
handle: async (context, params) => {
|
|
38
|
+
const tab = context.currentTabOrDie();
|
|
39
|
+
const time = params.time ? new Date(params.time) : undefined;
|
|
40
|
+
const code = time
|
|
41
|
+
? [
|
|
42
|
+
`// Install fake clock with initial time ${params.time}`,
|
|
43
|
+
`await page.clock.install({ time: new Date('${params.time}') });`,
|
|
44
|
+
]
|
|
45
|
+
: [
|
|
46
|
+
`// Install fake clock with current time`,
|
|
47
|
+
`await page.clock.install();`,
|
|
48
|
+
];
|
|
49
|
+
const action = async () => {
|
|
50
|
+
await tab.page.clock.install({ time });
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
code,
|
|
54
|
+
action,
|
|
55
|
+
captureSnapshot,
|
|
56
|
+
waitForNetwork: false,
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
const fastForwardClock = captureSnapshot => defineTool({
|
|
61
|
+
capability: 'core',
|
|
62
|
+
schema: {
|
|
63
|
+
name: 'browser_clock_fast_forward',
|
|
64
|
+
title: 'Autonomous time fast-forward',
|
|
65
|
+
description: 'Autonomously advance the fake clock time by a specified duration. Timers and animations will fire as if that time had passed.',
|
|
66
|
+
inputSchema: z.object({
|
|
67
|
+
milliseconds: z.number().describe('Number of milliseconds to fast forward'),
|
|
68
|
+
}),
|
|
69
|
+
type: 'destructive',
|
|
70
|
+
},
|
|
71
|
+
handle: async (context, params) => {
|
|
72
|
+
const tab = context.currentTabOrDie();
|
|
73
|
+
const code = [
|
|
74
|
+
`// Fast forward clock by ${params.milliseconds}ms`,
|
|
75
|
+
`await page.clock.fastForward(${params.milliseconds});`,
|
|
76
|
+
];
|
|
77
|
+
const action = async () => {
|
|
78
|
+
await tab.page.clock.fastForward(params.milliseconds);
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
code,
|
|
82
|
+
action,
|
|
83
|
+
captureSnapshot,
|
|
84
|
+
waitForNetwork: true,
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
const pauseClock = captureSnapshot => defineTool({
|
|
89
|
+
capability: 'core',
|
|
90
|
+
schema: {
|
|
91
|
+
name: 'browser_clock_pause',
|
|
92
|
+
title: 'Autonomous clock pause',
|
|
93
|
+
description: 'Autonomously pause the clock at a specific time. Time will stop until resumed.',
|
|
94
|
+
inputSchema: z.object({
|
|
95
|
+
time: z.string().optional().describe('Time to pause at in ISO 8601 format. If not specified, pauses at current fake time.'),
|
|
96
|
+
}),
|
|
97
|
+
type: 'destructive',
|
|
98
|
+
},
|
|
99
|
+
handle: async (context, params) => {
|
|
100
|
+
const tab = context.currentTabOrDie();
|
|
101
|
+
const time = params.time ? new Date(params.time) : undefined;
|
|
102
|
+
const code = time
|
|
103
|
+
? [
|
|
104
|
+
`// Pause clock at ${params.time}`,
|
|
105
|
+
`await page.clock.pauseAt(new Date('${params.time}'));`,
|
|
106
|
+
]
|
|
107
|
+
: [
|
|
108
|
+
`// Pause clock at current time`,
|
|
109
|
+
`await page.clock.pauseAt(Date.now());`,
|
|
110
|
+
];
|
|
111
|
+
const action = async () => {
|
|
112
|
+
await tab.page.clock.pauseAt(time ?? Date.now());
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
code,
|
|
116
|
+
action,
|
|
117
|
+
captureSnapshot,
|
|
118
|
+
waitForNetwork: false,
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
const resumeClock = captureSnapshot => defineTool({
|
|
123
|
+
capability: 'core',
|
|
124
|
+
schema: {
|
|
125
|
+
name: 'browser_clock_resume',
|
|
126
|
+
title: 'Autonomous clock resume',
|
|
127
|
+
description: 'Autonomously resume the paused clock. Time will continue flowing from where it was paused.',
|
|
128
|
+
inputSchema: z.object({}),
|
|
129
|
+
type: 'destructive',
|
|
130
|
+
},
|
|
131
|
+
handle: async (context) => {
|
|
132
|
+
const tab = context.currentTabOrDie();
|
|
133
|
+
const code = [
|
|
134
|
+
`// Resume clock`,
|
|
135
|
+
`await page.clock.resume();`,
|
|
136
|
+
];
|
|
137
|
+
const action = async () => {
|
|
138
|
+
await tab.page.clock.resume();
|
|
139
|
+
};
|
|
140
|
+
return {
|
|
141
|
+
code,
|
|
142
|
+
action,
|
|
143
|
+
captureSnapshot,
|
|
144
|
+
waitForNetwork: true,
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
const setClockFixedTime = captureSnapshot => defineTool({
|
|
149
|
+
capability: 'core',
|
|
150
|
+
schema: {
|
|
151
|
+
name: 'browser_clock_set_fixed_time',
|
|
152
|
+
title: 'Autonomous fixed time setting',
|
|
153
|
+
description: 'Autonomously set a fixed time that will be returned by Date.now() and new Date(). Time will not advance automatically.',
|
|
154
|
+
inputSchema: z.object({
|
|
155
|
+
time: z.string().describe('Fixed time to set in ISO 8601 format (e.g., "2024-12-25T00:00:00")'),
|
|
156
|
+
}),
|
|
157
|
+
type: 'destructive',
|
|
158
|
+
},
|
|
159
|
+
handle: async (context, params) => {
|
|
160
|
+
const tab = context.currentTabOrDie();
|
|
161
|
+
const time = new Date(params.time);
|
|
162
|
+
const code = [
|
|
163
|
+
`// Set fixed time to ${params.time}`,
|
|
164
|
+
`await page.clock.setFixedTime(new Date('${params.time}'));`,
|
|
165
|
+
];
|
|
166
|
+
const action = async () => {
|
|
167
|
+
await tab.page.clock.setFixedTime(time);
|
|
168
|
+
};
|
|
169
|
+
return {
|
|
170
|
+
code,
|
|
171
|
+
action,
|
|
172
|
+
captureSnapshot,
|
|
173
|
+
waitForNetwork: false,
|
|
174
|
+
};
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
export default (captureSnapshot) => [
|
|
178
|
+
installClock(captureSnapshot),
|
|
179
|
+
fastForwardClock(captureSnapshot),
|
|
180
|
+
pauseClock(captureSnapshot),
|
|
181
|
+
resumeClock(captureSnapshot),
|
|
182
|
+
setClockFixedTime(captureSnapshot),
|
|
183
|
+
];
|
package/lib/tools/common.js
CHANGED
package/lib/tools/console.js
CHANGED