@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
@@ -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.
package/lib/cdpRelay.js 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.
@@ -161,7 +161,7 @@ export class CDPRelayServer extends EventEmitter {
161
161
  result: {
162
162
  protocolVersion: '1.3',
163
163
  product: 'Browser/Extension-Bridge',
164
- userAgent: 'CDP-Bridge-Server/1.0.0',
164
+ userAgent: 'CDP-Bridge-Server/1.3.0',
165
165
  }
166
166
  });
167
167
  break;
package/lib/common.js ADDED
@@ -0,0 +1,68 @@
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 './tools/tool.js';
18
+ const close = defineTool({
19
+ capability: 'core',
20
+ schema: {
21
+ name: 'browser_close',
22
+ title: 'Autonomous browser closure',
23
+ description: 'Autonomously close the browser session and terminate all operations',
24
+ inputSchema: z.object({}),
25
+ type: 'readOnly',
26
+ },
27
+ handle: async (context) => {
28
+ await context.close();
29
+ return {
30
+ code: [`await page.close()`],
31
+ captureSnapshot: false,
32
+ waitForNetwork: false,
33
+ };
34
+ },
35
+ });
36
+ const resize = captureSnapshot => defineTool({
37
+ capability: 'core',
38
+ schema: {
39
+ name: 'browser_resize',
40
+ title: 'Autonomous window resizing',
41
+ description: 'Autonomously resize the browser window to specific dimensions for optimal viewing',
42
+ inputSchema: z.object({
43
+ width: z.number().describe('Width of the browser window'),
44
+ height: z.number().describe('Height of the browser window'),
45
+ }),
46
+ type: 'readOnly',
47
+ },
48
+ handle: async (context, params) => {
49
+ const tab = context.currentTabOrDie();
50
+ const code = [
51
+ `// Resize browser window to ${params.width}x${params.height}`,
52
+ `await page.setViewportSize({ width: ${params.width}, height: ${params.height} });`
53
+ ];
54
+ const action = async () => {
55
+ await tab.page.setViewportSize({ width: params.width, height: params.height });
56
+ };
57
+ return {
58
+ code,
59
+ action,
60
+ captureSnapshot,
61
+ waitForNetwork: true
62
+ };
63
+ },
64
+ });
65
+ export default (captureSnapshot) => [
66
+ close,
67
+ resize(captureSnapshot)
68
+ ];
package/lib/config.js 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.
@@ -39,7 +39,36 @@ const defaultConfig = {
39
39
  allowedOrigins: undefined,
40
40
  blockedOrigins: undefined,
41
41
  },
42
- server: {},
42
+ server: {
43
+ baseUrl: process.env.SERVER_BASE_URL,
44
+ https: {
45
+ enabled: false
46
+ },
47
+ rateLimit: {
48
+ enabled: false,
49
+ windowMs: 15 * 60 * 1000, // 15 minutes
50
+ maxRequests: 100
51
+ }
52
+ },
53
+ copilotStudio: {
54
+ enabled: process.env.COPILOT_STUDIO_ENABLED === 'true',
55
+ callbackUrl: process.env.COPILOT_STUDIO_CALLBACK_URL,
56
+ maxConcurrentSessions: parseInt(process.env.MAX_CONCURRENT_SESSIONS || '10', 10),
57
+ sessionTimeoutMs: parseInt(process.env.SESSION_TIMEOUT_MS || '1800000', 10), // 30 minutes
58
+ auditLogging: process.env.AUDIT_LOGGING_ENABLED === 'true'
59
+ },
60
+ auth: {
61
+ entraId: {
62
+ enabled: process.env.ENTRA_AUTH_ENABLED === 'true',
63
+ tenantId: process.env.AZURE_TENANT_ID,
64
+ clientId: process.env.AZURE_CLIENT_ID,
65
+ clientSecret: process.env.AZURE_CLIENT_SECRET
66
+ },
67
+ apiKey: {
68
+ enabled: process.env.API_KEY_AUTH_ENABLED === 'true',
69
+ keys: process.env.API_KEYS?.split(',') || []
70
+ }
71
+ },
43
72
  outputDir: path.join(os.tmpdir(), 'darbot-browser-mcp-output', sanitizeForFilePath(new Date().toISOString())),
44
73
  };
45
74
  export async function resolveConfig(config) {
@@ -124,6 +153,11 @@ export async function configFromCLIOptions(cliOptions) {
124
153
  contextOptions.ignoreHTTPSErrors = true;
125
154
  if (cliOptions.blockServiceWorkers)
126
155
  contextOptions.serviceWorkers = 'block';
156
+ // Set Edge profile environment variables for session state tracking
157
+ if (cliOptions.edgeProfile)
158
+ process.env.DARBOT_EDGE_PROFILE = cliOptions.edgeProfile;
159
+ if (cliOptions.edgeProfileEmail)
160
+ process.env.DARBOT_EDGE_PROFILE_EMAIL = cliOptions.edgeProfileEmail;
127
161
  const result = {
128
162
  browser: {
129
163
  browserAgent: cliOptions.browserAgent ?? process.env.PW_BROWSER_AGENT,
@@ -135,7 +169,8 @@ export async function configFromCLIOptions(cliOptions) {
135
169
  cdpEndpoint: cliOptions.cdpEndpoint,
136
170
  },
137
171
  server: {
138
- port: cliOptions.port,
172
+ // Support PORT environment variable (common in cloud deployments like Azure)
173
+ port: cliOptions.port ?? (process.env.PORT ? parseInt(process.env.PORT, 10) : undefined),
139
174
  host: cliOptions.host,
140
175
  },
141
176
  capabilities: cliOptions.caps?.split(',').map((c) => c.trim()),
@@ -198,6 +233,30 @@ function mergeConfig(base, overrides) {
198
233
  server: {
199
234
  ...pickDefined(base.server),
200
235
  ...pickDefined(overrides.server),
236
+ https: {
237
+ ...pickDefined(base.server?.https),
238
+ ...pickDefined(overrides.server?.https),
239
+ },
240
+ rateLimit: {
241
+ ...pickDefined(base.server?.rateLimit),
242
+ ...pickDefined(overrides.server?.rateLimit),
243
+ }
201
244
  },
245
+ copilotStudio: {
246
+ ...pickDefined(base.copilotStudio),
247
+ ...pickDefined(overrides.copilotStudio),
248
+ },
249
+ auth: {
250
+ ...pickDefined(base.auth),
251
+ ...pickDefined(overrides.auth),
252
+ entraId: {
253
+ ...pickDefined(base.auth?.entraId),
254
+ ...pickDefined(overrides.auth?.entraId),
255
+ },
256
+ apiKey: {
257
+ ...pickDefined(base.auth?.apiKey),
258
+ ...pickDefined(overrides.auth?.apiKey),
259
+ }
260
+ }
202
261
  };
203
262
  }
package/lib/connection.js 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.
package/lib/context.js 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.
package/lib/fileUtils.js 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.
@@ -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
+ }