@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.
Files changed (80) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +249 -158
  3. package/cli.js +1 -1
  4. package/config.d.ts +77 -1
  5. package/index.d.ts +1 -1
  6. package/index.js +1 -1
  7. package/lib/ai/context.js +150 -0
  8. package/lib/ai/guardrails.js +382 -0
  9. package/lib/ai/integration.js +397 -0
  10. package/lib/ai/intent.js +237 -0
  11. package/lib/ai/manualPromise.js +111 -0
  12. package/lib/ai/memory.js +273 -0
  13. package/lib/ai/ml-scorer.js +265 -0
  14. package/lib/ai/orchestrator-tools.js +292 -0
  15. package/lib/ai/orchestrator.js +473 -0
  16. package/lib/ai/planner.js +300 -0
  17. package/lib/ai/reporter.js +493 -0
  18. package/lib/ai/workflow.js +407 -0
  19. package/lib/auth/apiKeyAuth.js +46 -0
  20. package/lib/auth/entraAuth.js +110 -0
  21. package/lib/auth/entraJwtVerifier.js +117 -0
  22. package/lib/auth/index.js +210 -0
  23. package/lib/auth/managedIdentityAuth.js +175 -0
  24. package/lib/auth/mcpOAuthProvider.js +186 -0
  25. package/lib/auth/tunnelAuth.js +120 -0
  26. package/lib/browserContextFactory.js +1 -1
  27. package/lib/browserServer.js +1 -1
  28. package/lib/cdpRelay.js +2 -2
  29. package/lib/common.js +68 -0
  30. package/lib/config.js +62 -3
  31. package/lib/connection.js +1 -1
  32. package/lib/context.js +1 -1
  33. package/lib/fileUtils.js +1 -1
  34. package/lib/guardrails.js +382 -0
  35. package/lib/health.js +178 -0
  36. package/lib/httpServer.js +1 -1
  37. package/lib/index.js +1 -1
  38. package/lib/javascript.js +1 -1
  39. package/lib/manualPromise.js +1 -1
  40. package/lib/memory.js +273 -0
  41. package/lib/openapi.js +373 -0
  42. package/lib/orchestrator.js +473 -0
  43. package/lib/package.js +1 -1
  44. package/lib/pageSnapshot.js +17 -2
  45. package/lib/planner.js +302 -0
  46. package/lib/program.js +17 -5
  47. package/lib/reporter.js +493 -0
  48. package/lib/resources/resource.js +1 -1
  49. package/lib/server.js +5 -3
  50. package/lib/tab.js +1 -1
  51. package/lib/tools/ai-native.js +298 -0
  52. package/lib/tools/autonomous.js +147 -0
  53. package/lib/tools/clock.js +183 -0
  54. package/lib/tools/common.js +1 -1
  55. package/lib/tools/console.js +1 -1
  56. package/lib/tools/diagnostics.js +132 -0
  57. package/lib/tools/dialogs.js +1 -1
  58. package/lib/tools/emulation.js +155 -0
  59. package/lib/tools/files.js +1 -1
  60. package/lib/tools/install.js +1 -1
  61. package/lib/tools/keyboard.js +1 -1
  62. package/lib/tools/navigate.js +1 -1
  63. package/lib/tools/network.js +1 -1
  64. package/lib/tools/pageSnapshot.js +58 -0
  65. package/lib/tools/pdf.js +1 -1
  66. package/lib/tools/profiles.js +76 -25
  67. package/lib/tools/screenshot.js +1 -1
  68. package/lib/tools/scroll.js +93 -0
  69. package/lib/tools/snapshot.js +1 -1
  70. package/lib/tools/storage.js +328 -0
  71. package/lib/tools/tab.js +16 -0
  72. package/lib/tools/tabs.js +1 -1
  73. package/lib/tools/testing.js +1 -1
  74. package/lib/tools/tool.js +1 -1
  75. package/lib/tools/utils.js +1 -1
  76. package/lib/tools/vision.js +1 -1
  77. package/lib/tools/wait.js +1 -1
  78. package/lib/tools.js +22 -1
  79. package/lib/transport.js +251 -31
  80. 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
+ ];
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright (c) Microsoft Corporation.
2
+ * Copyright (c) DarbotLabs.
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
5
  * you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright (c) Microsoft Corporation.
2
+ * Copyright (c) DarbotLabs.
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
5
  * you may not use this file except in compliance with the License.