@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
package/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Copyright (c) Microsoft Corporation.
3
+ * Copyright (c) DarbotLabs.
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
package/config.d.ts CHANGED
@@ -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.
@@ -80,6 +80,82 @@ export type Config = {
80
80
  * The host to bind the server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.
81
81
  */
82
82
  host?: string;
83
+
84
+ /**
85
+ * Base URL for the server, used in OpenAPI specification generation.
86
+ */
87
+ baseUrl?: string;
88
+
89
+ /**
90
+ * Enable HTTPS with SSL certificates.
91
+ */
92
+ https?: {
93
+ enabled: boolean;
94
+ keyPath?: string;
95
+ certPath?: string;
96
+ };
97
+
98
+ /**
99
+ * Rate limiting configuration for enterprise use.
100
+ */
101
+ rateLimit?: {
102
+ enabled: boolean;
103
+ windowMs?: number;
104
+ maxRequests?: number;
105
+ };
106
+ },
107
+
108
+ /**
109
+ * Microsoft Copilot Studio integration settings.
110
+ */
111
+ copilotStudio?: {
112
+ /**
113
+ * Enable Copilot Studio specific features.
114
+ */
115
+ enabled?: boolean;
116
+
117
+ /**
118
+ * Callback URL for OAuth authentication flow.
119
+ */
120
+ callbackUrl?: string;
121
+
122
+ /**
123
+ * Maximum number of concurrent browser sessions for enterprise workloads.
124
+ */
125
+ maxConcurrentSessions?: number;
126
+
127
+ /**
128
+ * Session timeout in milliseconds.
129
+ */
130
+ sessionTimeoutMs?: number;
131
+
132
+ /**
133
+ * Enable audit logging for compliance.
134
+ */
135
+ auditLogging?: boolean;
136
+ },
137
+
138
+ /**
139
+ * Authentication configuration.
140
+ */
141
+ auth?: {
142
+ /**
143
+ * Microsoft Entra ID authentication settings.
144
+ */
145
+ entraId?: {
146
+ enabled?: boolean;
147
+ tenantId?: string;
148
+ clientId?: string;
149
+ clientSecret?: string;
150
+ };
151
+
152
+ /**
153
+ * API key authentication settings.
154
+ */
155
+ apiKey?: {
156
+ enabled?: boolean;
157
+ keys?: string[];
158
+ };
83
159
  },
84
160
 
85
161
  /**
package/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Copyright (c) Microsoft Corporation.
3
+ * Copyright (c) DarbotLabs.
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Copyright (c) Microsoft Corporation.
3
+ * Copyright (c) DarbotLabs
4
4
  *
5
5
  * Licensed under the Apache License, Version 2.0 (the "License");
6
6
  * you may not use this file except in compliance with the License.
@@ -0,0 +1,150 @@
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 context manager for tracking user intent and session state
18
+ */
19
+ export class AIContextManager {
20
+ contexts = new Map();
21
+ maxHistorySize = 50;
22
+ /**
23
+ * Create or get session context for a browser session
24
+ */
25
+ getOrCreateContext(sessionId) {
26
+ if (!this.contexts.has(sessionId)) {
27
+ this.contexts.set(sessionId, {
28
+ currentTask: '',
29
+ pageIntent: '',
30
+ userGoals: [],
31
+ navigationHistory: [],
32
+ failurePatterns: [],
33
+ successfulActions: [],
34
+ contextId: sessionId,
35
+ startTime: Date.now(),
36
+ lastActivity: Date.now(),
37
+ });
38
+ }
39
+ return this.contexts.get(sessionId);
40
+ }
41
+ /**
42
+ * Update current task description
43
+ */
44
+ updateTask(sessionId, task) {
45
+ const context = this.getOrCreateContext(sessionId);
46
+ context.currentTask = task;
47
+ context.lastActivity = Date.now();
48
+ }
49
+ /**
50
+ * Set page intent based on analysis
51
+ */
52
+ setPageIntent(sessionId, intent) {
53
+ const context = this.getOrCreateContext(sessionId);
54
+ context.pageIntent = intent;
55
+ context.lastActivity = Date.now();
56
+ }
57
+ /**
58
+ * Record successful action for learning
59
+ */
60
+ recordSuccess(sessionId, action) {
61
+ const context = this.getOrCreateContext(sessionId);
62
+ context.successfulActions.push(action);
63
+ context.lastActivity = Date.now();
64
+ // Keep only recent successes
65
+ if (context.successfulActions.length > this.maxHistorySize)
66
+ context.successfulActions = context.successfulActions.slice(-this.maxHistorySize);
67
+ }
68
+ /**
69
+ * Record page state for navigation tracking
70
+ */
71
+ recordPageState(sessionId, pageState) {
72
+ const context = this.getOrCreateContext(sessionId);
73
+ context.navigationHistory.push(pageState);
74
+ context.lastActivity = Date.now();
75
+ // Keep only recent navigation history
76
+ if (context.navigationHistory.length > this.maxHistorySize)
77
+ context.navigationHistory = context.navigationHistory.slice(-this.maxHistorySize);
78
+ }
79
+ /**
80
+ * Record error pattern for learning
81
+ */
82
+ recordError(sessionId, error) {
83
+ const context = this.getOrCreateContext(sessionId);
84
+ // Find existing pattern or create new
85
+ const existing = context.failurePatterns.find(p => p.errorType === error.errorType &&
86
+ p.elementSelector === error.elementSelector &&
87
+ p.pageUrl === error.pageUrl);
88
+ if (existing) {
89
+ existing.frequency++;
90
+ existing.lastOccurrence = Date.now();
91
+ }
92
+ else {
93
+ context.failurePatterns.push({
94
+ ...error,
95
+ frequency: 1,
96
+ lastOccurrence: Date.now(),
97
+ });
98
+ }
99
+ context.lastActivity = Date.now();
100
+ }
101
+ /**
102
+ * Get context for AI decision making
103
+ */
104
+ getContext(sessionId) {
105
+ return this.getOrCreateContext(sessionId);
106
+ }
107
+ /**
108
+ * Analyze patterns to suggest next actions
109
+ */
110
+ suggestNextActions(sessionId, currentPage) {
111
+ const context = this.getOrCreateContext(sessionId);
112
+ const suggestions = [];
113
+ // Analyze successful patterns
114
+ const recentSuccesses = context.successfulActions
115
+ .filter(action => Date.now() - action.timestamp < 300000) // Last 5 minutes
116
+ .slice(-10);
117
+ // Look for common action patterns
118
+ const actionCounts = new Map();
119
+ recentSuccesses.forEach(action => {
120
+ actionCounts.set(action.action, (actionCounts.get(action.action) || 0) + 1);
121
+ });
122
+ // Suggest most common recent actions
123
+ const sortedActions = Array.from(actionCounts.entries())
124
+ .sort(([, a], [, b]) => b - a)
125
+ .slice(0, 3);
126
+ sortedActions.forEach(([action]) => {
127
+ suggestions.push(`Continue with ${action} action based on recent pattern`);
128
+ });
129
+ return suggestions;
130
+ }
131
+ /**
132
+ * Clean up old contexts
133
+ */
134
+ cleanup() {
135
+ const now = Date.now();
136
+ const maxAge = 24 * 60 * 60 * 1000; // 24 hours
137
+ for (const [sessionId, context] of this.contexts.entries()) {
138
+ if (now - context.lastActivity > maxAge)
139
+ this.contexts.delete(sessionId);
140
+ }
141
+ }
142
+ /**
143
+ * Clear context for session
144
+ */
145
+ clearContext(sessionId) {
146
+ this.contexts.delete(sessionId);
147
+ }
148
+ }
149
+ // Global instance for session management
150
+ export const aiContextManager = new AIContextManager();
@@ -0,0 +1,382 @@
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 debug from 'debug';
17
+ const log = debug('darbot:guardrails');
18
+ /**
19
+ * Guardrail system for safe autonomous operation
20
+ */
21
+ export class GuardrailSystem {
22
+ config;
23
+ domainCounts = new Map();
24
+ actionHistory = [];
25
+ rateLimiter;
26
+ constructor(config = {}) {
27
+ this.config = {
28
+ maxPagesPerDomain: 50,
29
+ maxDepth: 5,
30
+ timeoutMs: 300000, // 5 minutes
31
+ allowedDomains: [],
32
+ blockedDomains: [
33
+ 'facebook.com', 'twitter.com', 'linkedin.com', 'instagram.com',
34
+ 'tiktok.com', 'youtube.com', 'gmail.com', 'outlook.com'
35
+ ],
36
+ blockedPatterns: [
37
+ /\/login\/?$/i,
38
+ /\/register\/?$/i,
39
+ /\/signup\/?$/i,
40
+ /\/logout\/?$/i,
41
+ /\/admin\/?/i,
42
+ /\/api\//i,
43
+ /\.exe$/i,
44
+ /\.dmg$/i,
45
+ /\.zip$/i,
46
+ /malware/i,
47
+ /virus/i,
48
+ /hack/i,
49
+ /illegal/i
50
+ ],
51
+ rateLimit: {
52
+ requestsPerSecond: 2,
53
+ burstSize: 5
54
+ },
55
+ safetyChecks: {
56
+ preventInfiniteLoops: true,
57
+ preventDestructiveActions: true,
58
+ requireUserConfirmation: false
59
+ },
60
+ ...config
61
+ };
62
+ this.rateLimiter = new RateLimiter(this.config.rateLimit.requestsPerSecond, this.config.rateLimit.burstSize);
63
+ }
64
+ /**
65
+ * Validate a crawl action before execution
66
+ */
67
+ async validateAction(action, context) {
68
+ log('Validating action:', action.type, action.target || action.url);
69
+ // Check rate limiting
70
+ if (!this.rateLimiter.allowRequest()) {
71
+ return {
72
+ allowed: false,
73
+ reason: 'Rate limit exceeded. Please slow down requests.'
74
+ };
75
+ }
76
+ // Check timeout
77
+ if (this.isSessionTimedOut(context.sessionStartTime)) {
78
+ return {
79
+ allowed: false,
80
+ reason: `Session timeout exceeded (${this.config.timeoutMs}ms)`
81
+ };
82
+ }
83
+ // Check depth limit
84
+ if (context.currentDepth > this.config.maxDepth) {
85
+ return {
86
+ allowed: false,
87
+ reason: `Maximum crawl depth exceeded (${this.config.maxDepth})`
88
+ };
89
+ }
90
+ // Validate specific action types
91
+ switch (action.type) {
92
+ case 'navigate':
93
+ return this.validateNavigation(action, context);
94
+ case 'click':
95
+ return this.validateClick(action, context);
96
+ case 'type':
97
+ return this.validateType(action, context);
98
+ case 'finish':
99
+ return { allowed: true };
100
+ default:
101
+ return { allowed: true };
102
+ }
103
+ }
104
+ /**
105
+ * Validate navigation actions
106
+ */
107
+ validateNavigation(action, context) {
108
+ if (!action.url)
109
+ return { allowed: false, reason: 'Navigation action missing URL' };
110
+ // URL safety checks
111
+ const urlCheck = this.validateUrl(action.url);
112
+ if (!urlCheck.allowed)
113
+ return urlCheck;
114
+ // Domain count check
115
+ const domain = this.extractDomain(action.url);
116
+ const domainCount = this.domainCounts.get(domain) || 0;
117
+ if (domainCount >= this.config.maxPagesPerDomain) {
118
+ return {
119
+ allowed: false,
120
+ reason: `Maximum pages per domain exceeded for ${domain} (${this.config.maxPagesPerDomain})`
121
+ };
122
+ }
123
+ // Infinite loop prevention
124
+ if (this.config.safetyChecks.preventInfiniteLoops) {
125
+ const loopCheck = this.detectInfiniteLoop(action, context);
126
+ if (!loopCheck.allowed)
127
+ return loopCheck;
128
+ }
129
+ // Update domain count
130
+ this.domainCounts.set(domain, domainCount + 1);
131
+ return { allowed: true };
132
+ }
133
+ /**
134
+ * Validate click actions
135
+ */
136
+ validateClick(action, context) {
137
+ if (!action.target)
138
+ return { allowed: false, reason: 'Click action missing target selector' };
139
+ // Check for potentially destructive actions
140
+ if (this.config.safetyChecks.preventDestructiveActions) {
141
+ const destructiveCheck = this.checkDestructiveClick(action);
142
+ if (!destructiveCheck.allowed)
143
+ return destructiveCheck;
144
+ }
145
+ return { allowed: true };
146
+ }
147
+ /**
148
+ * Validate type actions
149
+ */
150
+ validateType(action, context) {
151
+ if (!action.text)
152
+ return { allowed: false, reason: 'Type action missing text' };
153
+ // Prevent sensitive data input
154
+ const sensitivePatterns = [
155
+ /password/i,
156
+ /ssn/i,
157
+ /social.security/i,
158
+ /credit.card/i,
159
+ /bank.account/i,
160
+ /api.key/i,
161
+ /secret/i,
162
+ /token/i
163
+ ];
164
+ const containsSensitive = sensitivePatterns.some(pattern => pattern.test(action.text) || pattern.test(action.target || ''));
165
+ if (containsSensitive) {
166
+ return {
167
+ allowed: false,
168
+ reason: 'Type action appears to contain sensitive information'
169
+ };
170
+ }
171
+ return { allowed: true };
172
+ }
173
+ /**
174
+ * Validate URL safety
175
+ */
176
+ validateUrl(url) {
177
+ try {
178
+ const urlObj = new URL(url);
179
+ // Protocol check
180
+ if (!['http:', 'https:'].includes(urlObj.protocol)) {
181
+ return {
182
+ allowed: false,
183
+ reason: `Unsafe protocol: ${urlObj.protocol}`
184
+ };
185
+ }
186
+ // Domain whitelist check
187
+ if (this.config.allowedDomains && this.config.allowedDomains.length > 0) {
188
+ const isAllowed = this.config.allowedDomains.some(domain => urlObj.hostname === domain || urlObj.hostname.endsWith('.' + domain));
189
+ if (!isAllowed) {
190
+ return {
191
+ allowed: false,
192
+ reason: `Domain not in allowlist: ${urlObj.hostname}`
193
+ };
194
+ }
195
+ }
196
+ // Domain blocklist check
197
+ if (this.config.blockedDomains) {
198
+ const isBlocked = this.config.blockedDomains.some(domain => urlObj.hostname === domain || urlObj.hostname.endsWith('.' + domain));
199
+ if (isBlocked) {
200
+ return {
201
+ allowed: false,
202
+ reason: `Domain is blocked: ${urlObj.hostname}`
203
+ };
204
+ }
205
+ }
206
+ // Pattern check
207
+ if (this.config.blockedPatterns) {
208
+ const matchedPattern = this.config.blockedPatterns.find(pattern => pattern.test(url));
209
+ if (matchedPattern) {
210
+ return {
211
+ allowed: false,
212
+ reason: `URL matches blocked pattern: ${matchedPattern.source}`
213
+ };
214
+ }
215
+ }
216
+ return { allowed: true };
217
+ }
218
+ catch (error) {
219
+ return {
220
+ allowed: false,
221
+ reason: `Invalid URL: ${error}`
222
+ };
223
+ }
224
+ }
225
+ /**
226
+ * Detect infinite loop patterns
227
+ */
228
+ detectInfiniteLoop(action, context) {
229
+ if (action.type !== 'navigate' || !action.url)
230
+ return { allowed: true };
231
+ // Check if we've visited this URL recently
232
+ const recentActions = this.actionHistory
233
+ .filter(h => Date.now() - h.timestamp < 60000) // Last minute
234
+ .filter(h => h.action.type === 'navigate' && h.action.url === action.url);
235
+ if (recentActions.length >= 3) {
236
+ return {
237
+ allowed: false,
238
+ reason: `Potential infinite loop detected: visited ${action.url} ${recentActions.length} times in the last minute`
239
+ };
240
+ }
241
+ // Check for back-and-forth patterns
242
+ const lastFewActions = this.actionHistory.slice(-6)
243
+ .filter(h => h.action.type === 'navigate')
244
+ .map(h => h.action.url);
245
+ if (lastFewActions.length >= 4) {
246
+ const pattern = [lastFewActions[0], lastFewActions[1]];
247
+ let patternCount = 0;
248
+ for (let i = 0; i < lastFewActions.length - 1; i += 2) {
249
+ if (lastFewActions[i] === pattern[0] && lastFewActions[i + 1] === pattern[1])
250
+ patternCount++;
251
+ }
252
+ if (patternCount >= 2) {
253
+ return {
254
+ allowed: false,
255
+ reason: 'Infinite navigation loop detected between two URLs'
256
+ };
257
+ }
258
+ }
259
+ return { allowed: true };
260
+ }
261
+ /**
262
+ * Check for potentially destructive click actions
263
+ */
264
+ checkDestructiveClick(action) {
265
+ if (!action.target)
266
+ return { allowed: true };
267
+ const destructivePatterns = [
268
+ /delete/i,
269
+ /remove/i,
270
+ /cancel/i,
271
+ /logout/i,
272
+ /sign.?out/i,
273
+ /unsubscribe/i,
274
+ /deactivate/i,
275
+ /close.account/i,
276
+ /purchase/i,
277
+ /buy.now/i,
278
+ /order.now/i,
279
+ /submit.payment/i
280
+ ];
281
+ const isDestructive = destructivePatterns.some(pattern => pattern.test(action.target) || pattern.test(action.reason || ''));
282
+ if (isDestructive) {
283
+ return {
284
+ allowed: false,
285
+ reason: 'Click action appears to be potentially destructive'
286
+ };
287
+ }
288
+ return { allowed: true };
289
+ }
290
+ /**
291
+ * Record an action in history
292
+ */
293
+ recordAction(action, url) {
294
+ this.actionHistory.push({
295
+ action,
296
+ timestamp: Date.now(),
297
+ url
298
+ });
299
+ // Keep only recent history
300
+ const cutoff = Date.now() - 3600000; // 1 hour
301
+ while (this.actionHistory.length > 0 && this.actionHistory[0].timestamp < cutoff)
302
+ this.actionHistory.shift();
303
+ }
304
+ /**
305
+ * Check if session has timed out
306
+ */
307
+ isSessionTimedOut(sessionStartTime) {
308
+ return Date.now() - sessionStartTime > this.config.timeoutMs;
309
+ }
310
+ /**
311
+ * Extract domain from URL
312
+ */
313
+ extractDomain(url) {
314
+ try {
315
+ return new URL(url).hostname;
316
+ }
317
+ catch {
318
+ return url;
319
+ }
320
+ }
321
+ /**
322
+ * Get guardrail statistics
323
+ */
324
+ getStats() {
325
+ return {
326
+ domainCounts: Object.fromEntries(this.domainCounts),
327
+ actionsRecorded: this.actionHistory.length,
328
+ rateLimiterStatus: this.rateLimiter.getStatus(),
329
+ config: this.config
330
+ };
331
+ }
332
+ /**
333
+ * Reset guardrail state
334
+ */
335
+ reset() {
336
+ this.domainCounts.clear();
337
+ this.actionHistory.length = 0;
338
+ this.rateLimiter.reset();
339
+ }
340
+ }
341
+ /**
342
+ * Simple rate limiter implementation
343
+ */
344
+ class RateLimiter {
345
+ tokens;
346
+ lastRefill;
347
+ maxTokens;
348
+ refillRate; // tokens per second
349
+ constructor(tokensPerSecond, burstSize) {
350
+ this.maxTokens = burstSize;
351
+ this.refillRate = tokensPerSecond;
352
+ this.tokens = burstSize;
353
+ this.lastRefill = Date.now();
354
+ }
355
+ allowRequest() {
356
+ this.refillTokens();
357
+ if (this.tokens >= 1) {
358
+ this.tokens -= 1;
359
+ return true;
360
+ }
361
+ return false;
362
+ }
363
+ refillTokens() {
364
+ const now = Date.now();
365
+ const timePassed = (now - this.lastRefill) / 1000;
366
+ const tokensToAdd = timePassed * this.refillRate;
367
+ this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd);
368
+ this.lastRefill = now;
369
+ }
370
+ getStatus() {
371
+ this.refillTokens();
372
+ return {
373
+ tokens: Math.floor(this.tokens),
374
+ maxTokens: this.maxTokens,
375
+ refillRate: this.refillRate
376
+ };
377
+ }
378
+ reset() {
379
+ this.tokens = this.maxTokens;
380
+ this.lastRefill = Date.now();
381
+ }
382
+ }