mirror of
https://github.com/aaronjmars/opendia.git
synced 2025-12-29 16:16:00 +00:00
bulk tabs (open + query + act)
This commit is contained in:
@@ -17,6 +17,135 @@ chrome.storage.local.get(['safetyMode'], (result) => {
|
||||
safetyModeEnabled = result.safetyMode || false;
|
||||
});
|
||||
|
||||
// Content script management for background tabs
|
||||
async function ensureContentScriptReady(tabId, retries = 3) {
|
||||
for (let attempt = 1; attempt <= retries; attempt++) {
|
||||
try {
|
||||
// Test if content script is responsive
|
||||
const response = await new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('Content script ping timeout'));
|
||||
}, 2000);
|
||||
|
||||
chrome.tabs.sendMessage(tabId, { action: 'ping' }, (response) => {
|
||||
clearTimeout(timeout);
|
||||
if (chrome.runtime.lastError) {
|
||||
reject(new Error(chrome.runtime.lastError.message));
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (response && response.success) {
|
||||
console.log(`✅ Content script ready in tab ${tabId}`);
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`⚠️ Content script not responsive in tab ${tabId}, attempt ${attempt}/${retries}`);
|
||||
|
||||
if (attempt === retries) {
|
||||
// Last attempt - try to inject content script
|
||||
try {
|
||||
const tab = await chrome.tabs.get(tabId);
|
||||
|
||||
// Check if tab URL is injectable (not chrome://, chrome-extension://, etc.)
|
||||
if (!isInjectableUrl(tab.url)) {
|
||||
throw new Error(`Cannot inject content script into ${tab.url} - restricted URL`);
|
||||
}
|
||||
|
||||
console.log(`🔄 Injecting content script into tab ${tabId}`);
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId: tabId },
|
||||
files: ['content.js']
|
||||
});
|
||||
|
||||
// Wait a moment for script to initialize
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Test again
|
||||
const testResponse = await new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => reject(new Error('Timeout after injection')), 3000);
|
||||
chrome.tabs.sendMessage(tabId, { action: 'ping' }, (response) => {
|
||||
clearTimeout(timeout);
|
||||
if (chrome.runtime.lastError) {
|
||||
reject(new Error(chrome.runtime.lastError.message));
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (testResponse && testResponse.success) {
|
||||
console.log(`✅ Content script successfully injected into tab ${tabId}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
} catch (injectionError) {
|
||||
throw new Error(`Failed to inject content script into tab ${tabId}: ${injectionError.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait before retry
|
||||
if (attempt < retries) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Content script not available in tab ${tabId} after ${retries} attempts`);
|
||||
}
|
||||
|
||||
// Check if URL allows content script injection
|
||||
function isInjectableUrl(url) {
|
||||
if (!url) return false;
|
||||
|
||||
const restrictedProtocols = ['chrome:', 'chrome-extension:', 'chrome-devtools:', 'edge:', 'moz-extension:'];
|
||||
const restrictedDomains = ['chrome.google.com'];
|
||||
|
||||
// Check protocol
|
||||
if (restrictedProtocols.some(protocol => url.startsWith(protocol))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check special Chrome pages
|
||||
if (url.startsWith('https://chrome.google.com/webstore') ||
|
||||
url.includes('chrome://') ||
|
||||
restrictedDomains.some(domain => url.includes(domain))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get content script readiness status for a tab
|
||||
async function getTabContentScriptStatus(tabId) {
|
||||
try {
|
||||
const tab = await chrome.tabs.get(tabId);
|
||||
|
||||
if (!isInjectableUrl(tab.url)) {
|
||||
return { ready: false, reason: 'restricted_url', url: tab.url };
|
||||
}
|
||||
|
||||
const response = await new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => resolve(null), 1000);
|
||||
chrome.tabs.sendMessage(tabId, { action: 'ping' }, (response) => {
|
||||
clearTimeout(timeout);
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
|
||||
if (response && response.success) {
|
||||
return { ready: true, reason: 'active', url: tab.url };
|
||||
} else {
|
||||
return { ready: false, reason: 'not_loaded', url: tab.url };
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
return { ready: false, reason: 'tab_error', error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// Port discovery function
|
||||
async function discoverServerPorts() {
|
||||
// Try common HTTP ports to find the server
|
||||
@@ -96,12 +225,33 @@ async function connectToMCPServer() {
|
||||
// Define available browser automation tools for MCP
|
||||
function getAvailableTools() {
|
||||
return [
|
||||
/*
|
||||
🎯 BACKGROUND TAB WORKFLOW GUIDE:
|
||||
|
||||
1. DISCOVER TABS: Use tab_list with check_content_script=true to see all tabs and their IDs
|
||||
2. TARGET SPECIFIC TABS: Add tab_id parameter to any tool to work on background tabs
|
||||
3. MULTI-TAB OPERATIONS: Process multiple tabs without switching between them
|
||||
|
||||
Example Multi-Tab Workflow:
|
||||
- tab_list({check_content_script: true}) → Get tab IDs and readiness status
|
||||
- page_analyze({intent_hint: "article", tab_id: 12345}) → Analyze background research tab
|
||||
- page_extract_content({content_type: "article", tab_id: 12345}) → Extract content without switching
|
||||
- get_selected_text({tab_id: 67890}) → Get quotes from another background tab
|
||||
|
||||
Perfect for: Research workflows, content analysis, form processing, social media management
|
||||
*/
|
||||
|
||||
// Page Analysis Tools
|
||||
{
|
||||
name: "page_analyze",
|
||||
description: "Two-phase intelligent page analysis with token efficiency optimization",
|
||||
description: "🔍 BACKGROUND TAB READY: Analyze any tab without switching to it! Two-phase intelligent page analysis with token efficiency optimization. Use tab_id parameter to analyze background tabs while staying on current page.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
examples: [
|
||||
{ intent_hint: "analyze", phase: "discover" }, // Current tab quick analysis
|
||||
{ intent_hint: "login", tab_id: 12345 }, // Background tab login form analysis
|
||||
{ intent_hint: "post_create", tab_id: 67890, phase: "detailed" } // Background tab detailed analysis
|
||||
],
|
||||
properties: {
|
||||
intent_hint: {
|
||||
type: "string",
|
||||
@@ -128,6 +278,10 @@ function getAvailableTools() {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "Expand specific quick match IDs from discover phase (e.g. ['q1', 'q2'])"
|
||||
},
|
||||
tab_id: {
|
||||
type: "number",
|
||||
description: "🎯 TARGET ANY TAB: Specify tab ID to analyze background tabs without switching! Get tab IDs from tab_list. If omitted, analyzes current active tab."
|
||||
}
|
||||
},
|
||||
required: ["intent_hint"]
|
||||
@@ -135,9 +289,14 @@ function getAvailableTools() {
|
||||
},
|
||||
{
|
||||
name: "page_extract_content",
|
||||
description: "Extract and summarize structured content with token efficiency optimization",
|
||||
description: "📄 BACKGROUND TAB READY: Extract content from any tab without switching! Perfect for analyzing multiple research tabs, articles, or pages simultaneously. Use tab_id to target specific background tabs.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
examples: [
|
||||
{ content_type: "article" }, // Extract from current tab
|
||||
{ content_type: "article", tab_id: 12345 }, // Extract from background research tab
|
||||
{ content_type: "posts", tab_id: 67890, max_items: 10 } // Extract social media posts from background tab
|
||||
],
|
||||
properties: {
|
||||
content_type: {
|
||||
type: "string",
|
||||
@@ -153,6 +312,10 @@ function getAvailableTools() {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
description: "Return summary instead of full content to save tokens"
|
||||
},
|
||||
tab_id: {
|
||||
type: "number",
|
||||
description: "🎯 TARGET ANY TAB: Extract content from specific background tab without switching! Use tab_list to get tab IDs. Perfect for processing multiple research tabs."
|
||||
}
|
||||
},
|
||||
required: ["content_type"]
|
||||
@@ -162,7 +325,7 @@ function getAvailableTools() {
|
||||
// Element Interaction Tools
|
||||
{
|
||||
name: "element_click",
|
||||
description: "Click on a specific page element",
|
||||
description: "🖱️ BACKGROUND TAB READY: Click elements in any tab without switching! Perform actions on background tabs while staying on current page. Use tab_id to target specific tabs.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -179,6 +342,10 @@ function getAvailableTools() {
|
||||
type: "number",
|
||||
description: "Milliseconds to wait after click",
|
||||
default: 500
|
||||
},
|
||||
tab_id: {
|
||||
type: "number",
|
||||
description: "🎯 TARGET ANY TAB: Click elements in background tabs without switching! Get tab IDs from tab_list to interact with multiple tabs efficiently."
|
||||
}
|
||||
},
|
||||
required: ["element_id"]
|
||||
@@ -186,7 +353,7 @@ function getAvailableTools() {
|
||||
},
|
||||
{
|
||||
name: "element_fill",
|
||||
description: "Fill input field with enhanced focus and event simulation for modern web apps",
|
||||
description: "✏️ BACKGROUND TAB READY: Fill forms in any tab without switching! Enhanced focus and event simulation for modern web apps. Use tab_id to fill forms in background tabs.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -207,6 +374,10 @@ function getAvailableTools() {
|
||||
type: "boolean",
|
||||
description: "Use enhanced focus sequence with click simulation for modern apps",
|
||||
default: true
|
||||
},
|
||||
tab_id: {
|
||||
type: "number",
|
||||
description: "🎯 TARGET ANY TAB: Fill forms in background tabs without switching! Perfect for batch form filling across multiple tabs. Get tab IDs from tab_list."
|
||||
}
|
||||
},
|
||||
required: ["element_id", "value"]
|
||||
@@ -216,7 +387,7 @@ function getAvailableTools() {
|
||||
// Navigation Tools
|
||||
{
|
||||
name: "page_navigate",
|
||||
description: "Navigate to specified URL and wait for page load",
|
||||
description: "Navigate CURRENT tab to a new URL. Use tab_create instead if you want to open a NEW tab with a URL.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -269,27 +440,73 @@ function getAvailableTools() {
|
||||
// Tab Management Tools
|
||||
{
|
||||
name: "tab_create",
|
||||
description: "Create a new tab with optional URL and activation",
|
||||
description: "Creates tabs. CRITICAL: For multiple identical tabs, ALWAYS use 'count' parameter! Examples: {url: 'https://x.com', count: 5} creates 5 Twitter tabs. {url: 'https://github.com', count: 10} creates 10 GitHub tabs. Single tab: {url: 'https://example.com'}. Multiple different URLs: {urls: ['url1', 'url2']}.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
examples: [
|
||||
{ url: "https://x.com", count: 5 }, // CORRECT: Creates 5 identical Twitter tabs in one batch
|
||||
{ url: "https://github.com", count: 10 }, // CORRECT: Creates 10 GitHub tabs
|
||||
{ urls: ["https://x.com/post1", "https://x.com/post2", "https://google.com"] }, // CORRECT: Different URLs in batch
|
||||
{ url: "https://example.com" } // Single tab only
|
||||
],
|
||||
properties: {
|
||||
url: {
|
||||
type: "string",
|
||||
description: "URL to open in the new tab (optional)"
|
||||
description: "Single URL to open. Can be used with 'count' to create multiple identical tabs"
|
||||
},
|
||||
urls: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "PREFERRED FOR MULTIPLE URLS: Array of URLs to open ALL AT ONCE in a single batch operation. Pass ALL URLs here instead of making multiple calls! Example: ['https://x.com/post1', 'https://x.com/post2', 'https://google.com']",
|
||||
maxItems: 100
|
||||
},
|
||||
count: {
|
||||
type: "number",
|
||||
default: 1,
|
||||
minimum: 1,
|
||||
maximum: 50,
|
||||
description: "REQUIRED FOR MULTIPLE IDENTICAL TABS: Set this to N to create N copies of the same URL. For '5 Twitter tabs' use count=5 with url='https://x.com'. DO NOT make 5 separate calls!"
|
||||
},
|
||||
active: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
description: "Whether to activate the new tab"
|
||||
description: "Whether to activate the last created tab (single tab only)"
|
||||
},
|
||||
wait_for: {
|
||||
type: "string",
|
||||
description: "CSS selector to wait for after tab creation (if URL provided)"
|
||||
description: "CSS selector to wait for after tab creation (single tab only)"
|
||||
},
|
||||
timeout: {
|
||||
type: "number",
|
||||
default: 10000,
|
||||
description: "Maximum wait time in milliseconds"
|
||||
description: "Maximum wait time per tab in milliseconds"
|
||||
},
|
||||
batch_settings: {
|
||||
type: "object",
|
||||
description: "Performance control settings for batch operations",
|
||||
properties: {
|
||||
chunk_size: {
|
||||
type: "number",
|
||||
default: 5,
|
||||
minimum: 1,
|
||||
maximum: 10,
|
||||
description: "Number of tabs to create per batch"
|
||||
},
|
||||
delay_between_chunks: {
|
||||
type: "number",
|
||||
default: 1000,
|
||||
minimum: 100,
|
||||
maximum: 5000,
|
||||
description: "Delay between batches in milliseconds"
|
||||
},
|
||||
delay_between_tabs: {
|
||||
type: "number",
|
||||
default: 200,
|
||||
minimum: 50,
|
||||
maximum: 1000,
|
||||
description: "Delay between individual tabs in milliseconds"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,9 +531,13 @@ function getAvailableTools() {
|
||||
},
|
||||
{
|
||||
name: "tab_list",
|
||||
description: "Get list of all open tabs with their details",
|
||||
description: "📋 TAB DISCOVERY: Get list of all open tabs with IDs for background tab targeting! Shows content script readiness status and tab details. Essential for multi-tab workflows - use tab IDs with other tools to work on background tabs.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
examples: [
|
||||
{ check_content_script: true }, // RECOMMENDED: Check which tabs are ready for background operations
|
||||
{ current_window_only: false, check_content_script: true } // Check all tabs across windows
|
||||
],
|
||||
properties: {
|
||||
current_window_only: {
|
||||
type: "boolean",
|
||||
@@ -327,6 +548,11 @@ function getAvailableTools() {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
description: "Include additional tab details (title, favicon, etc.)"
|
||||
},
|
||||
check_content_script: {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
description: "🔍 ESSENTIAL FOR BACKGROUND TABS: Check which tabs are ready for background operations! Set to true when planning multi-tab workflows to see which tabs can be targeted."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -450,7 +676,7 @@ function getAvailableTools() {
|
||||
},
|
||||
{
|
||||
name: "get_selected_text",
|
||||
description: "Get the currently selected text on the page",
|
||||
description: "📝 BACKGROUND TAB READY: Get selected text from any tab without switching! Perfect for collecting quotes, citations, or highlighted content from multiple research tabs simultaneously.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -463,13 +689,17 @@ function getAvailableTools() {
|
||||
type: "number",
|
||||
default: 10000,
|
||||
description: "Maximum length of text to return"
|
||||
},
|
||||
tab_id: {
|
||||
type: "number",
|
||||
description: "🎯 TARGET ANY TAB: Get selected text from background tabs without switching! Perfect for collecting quotes or snippets from multiple research tabs."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "page_scroll",
|
||||
description: "Scroll the page in various directions and amounts - critical for long pages",
|
||||
description: "📜 BACKGROUND TAB READY: Scroll any tab without switching! Critical for long pages. Navigate through content in background tabs while staying on current page. Use tab_id to target specific tabs.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -502,6 +732,10 @@ function getAvailableTools() {
|
||||
type: "number",
|
||||
default: 500,
|
||||
description: "Milliseconds to wait after scrolling"
|
||||
},
|
||||
tab_id: {
|
||||
type: "number",
|
||||
description: "🎯 TARGET ANY TAB: Scroll content in background tabs without switching! Perfect for navigating long documents or pages in multiple tabs simultaneously."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -542,30 +776,31 @@ async function handleMCPRequest(message) {
|
||||
try {
|
||||
// Safety Mode check: Block write/edit tools if safety mode is enabled
|
||||
if (safetyModeEnabled && WRITE_EDIT_TOOLS.includes(method)) {
|
||||
throw new Error(`🛡️ Safety Mode is enabled. This tool (${method}) is blocked to prevent page modifications. To disable Safety Mode, open the OpenDia extension popup and toggle off "Safety Mode".`);
|
||||
const targetInfo = params.tab_id ? `tab ${params.tab_id}` : 'the current page';
|
||||
throw new Error(`🛡️ Safety Mode is enabled. This tool (${method}) is blocked to prevent modifications to ${targetInfo}. To disable Safety Mode, open the OpenDia extension popup and toggle off "Safety Mode".`);
|
||||
}
|
||||
|
||||
let result;
|
||||
|
||||
switch (method) {
|
||||
// New automation tools
|
||||
// New automation tools with background tab support
|
||||
case "page_analyze":
|
||||
result = await sendToContentScript('analyze', params);
|
||||
result = await sendToContentScript('analyze', params, params.tab_id);
|
||||
break;
|
||||
case "page_extract_content":
|
||||
result = await sendToContentScript('extract_content', params);
|
||||
result = await sendToContentScript('extract_content', params, params.tab_id);
|
||||
break;
|
||||
case "element_click":
|
||||
result = await sendToContentScript('element_click', params);
|
||||
result = await sendToContentScript('element_click', params, params.tab_id);
|
||||
break;
|
||||
case "element_fill":
|
||||
result = await sendToContentScript('element_fill', params);
|
||||
result = await sendToContentScript('element_fill', params, params.tab_id);
|
||||
break;
|
||||
case "page_navigate":
|
||||
result = await navigateToUrl(params.url, params.wait_for, params.timeout);
|
||||
break;
|
||||
case "page_wait_for":
|
||||
result = await sendToContentScript('wait_for', params);
|
||||
result = await sendToContentScript('wait_for', params, params.tab_id);
|
||||
break;
|
||||
|
||||
// Tab management tools
|
||||
@@ -584,7 +819,7 @@ async function handleMCPRequest(message) {
|
||||
|
||||
// Element state tools
|
||||
case "element_get_state":
|
||||
result = await sendToContentScript('get_element_state', params);
|
||||
result = await sendToContentScript('get_element_state', params, params.tab_id);
|
||||
break;
|
||||
// Workspace and Reference Management Tools
|
||||
case "get_bookmarks":
|
||||
@@ -600,10 +835,10 @@ async function handleMCPRequest(message) {
|
||||
result = await getSelectedText(params);
|
||||
break;
|
||||
case "page_scroll":
|
||||
result = await sendToContentScript('page_scroll', params);
|
||||
result = await sendToContentScript('page_scroll', params, params.tab_id);
|
||||
break;
|
||||
case "get_page_links":
|
||||
result = await sendToContentScript('get_page_links', params);
|
||||
result = await sendToContentScript('get_page_links', params, params.tab_id);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown method: ${method}`);
|
||||
@@ -630,25 +865,41 @@ async function handleMCPRequest(message) {
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced content script communication
|
||||
async function sendToContentScript(action, data) {
|
||||
const [activeTab] = await chrome.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
// Enhanced content script communication with background tab support
|
||||
async function sendToContentScript(action, data, targetTabId = null) {
|
||||
let targetTab;
|
||||
|
||||
if (!activeTab) {
|
||||
throw new Error('No active tab found');
|
||||
if (targetTabId) {
|
||||
// Use specific tab
|
||||
try {
|
||||
targetTab = await chrome.tabs.get(targetTabId);
|
||||
} catch (error) {
|
||||
throw new Error(`Tab ${targetTabId} not found or inaccessible`);
|
||||
}
|
||||
} else {
|
||||
// Fallback to active tab (maintains compatibility)
|
||||
const [activeTab] = await chrome.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
|
||||
if (!activeTab) {
|
||||
throw new Error('No active tab found');
|
||||
}
|
||||
targetTab = activeTab;
|
||||
}
|
||||
|
||||
// Ensure content script is available in the target tab
|
||||
await ensureContentScriptReady(targetTab.id);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.tabs.sendMessage(activeTab.id, { action, data }, (response) => {
|
||||
chrome.tabs.sendMessage(targetTab.id, { action, data }, (response) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
reject(new Error(chrome.runtime.lastError.message));
|
||||
reject(new Error(`Tab ${targetTab.id}: ${chrome.runtime.lastError.message}`));
|
||||
} else if (response && response.success) {
|
||||
resolve(response.data);
|
||||
} else {
|
||||
reject(new Error(response?.error || 'Unknown error'));
|
||||
reject(new Error(`Tab ${targetTab.id}: ${response?.error || 'Unknown error'}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -702,27 +953,150 @@ async function waitForElement(tabId, selector, timeout = 5000) {
|
||||
throw new Error(`Timeout waiting for element: ${selector}`);
|
||||
}
|
||||
|
||||
// Tab Management Functions
|
||||
// Enhanced Tab Management Functions with Batch Support
|
||||
async function createTab(params) {
|
||||
const { url, active = true, wait_for, timeout = 10000 } = params;
|
||||
const {
|
||||
url,
|
||||
urls,
|
||||
count = 1,
|
||||
active = true,
|
||||
wait_for,
|
||||
timeout = 10000,
|
||||
batch_settings = {}
|
||||
} = params;
|
||||
|
||||
// Smart hint: If creating single tab but description suggests multiple, provide guidance
|
||||
if (count === 1 && !urls) {
|
||||
console.log(`💡 Single tab creation. For multiple identical tabs, use count parameter: {"url": "${url}", "count": N}`);
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
const validation = validateTabCreateParams(params);
|
||||
if (!validation.valid) {
|
||||
throw new Error(validation.error);
|
||||
}
|
||||
|
||||
console.log(`🎯 Tab creation request:`, { url, urls, count, batch_settings, hasBatchSettings: !!batch_settings });
|
||||
|
||||
// Determine operation type
|
||||
if (urls && urls.length > 0) {
|
||||
// Batch creation with multiple URLs
|
||||
console.log(`🚀 Using batch mode with ${urls.length} URLs`);
|
||||
return await createTabsBatch(urls, active, wait_for, timeout, batch_settings);
|
||||
} else if (url && count > 1) {
|
||||
// Batch creation with same URL repeated
|
||||
console.log(`🔄 Using repeat mode: ${count} copies of ${url}`);
|
||||
const urlArray = Array(count).fill(url);
|
||||
return await createTabsBatch(urlArray, active, wait_for, timeout, batch_settings);
|
||||
} else {
|
||||
// Single tab creation (legacy behavior)
|
||||
console.log(`📱 Using single tab mode for: ${url || 'about:blank'}`);
|
||||
return await createSingleTab(url, active, wait_for, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
// Parameter validation
|
||||
function validateTabCreateParams(params) {
|
||||
const { url, urls, count = 1 } = params;
|
||||
|
||||
// Check for conflicting parameters
|
||||
if (url && urls) {
|
||||
return { valid: false, error: "Cannot specify both 'url' and 'urls' parameters" };
|
||||
}
|
||||
|
||||
if (urls && count > 1) {
|
||||
return { valid: false, error: "Cannot use 'count' with 'urls' array" };
|
||||
}
|
||||
|
||||
// Allow empty URL for about:blank tabs
|
||||
if (!url && !urls && count > 1) {
|
||||
return { valid: false, error: "Must specify 'url' when using 'count' parameter" };
|
||||
}
|
||||
|
||||
// Validate URLs array
|
||||
if (urls) {
|
||||
if (!Array.isArray(urls) || urls.length === 0) {
|
||||
return { valid: false, error: "'urls' must be a non-empty array" };
|
||||
}
|
||||
|
||||
if (urls.length > 100) {
|
||||
return { valid: false, error: "Maximum 100 URLs allowed in batch operation" };
|
||||
}
|
||||
|
||||
// Validate each URL
|
||||
for (let i = 0; i < urls.length; i++) {
|
||||
if (typeof urls[i] !== 'string' || !urls[i].trim()) {
|
||||
return { valid: false, error: `Invalid URL at index ${i}: must be a non-empty string` };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate count
|
||||
if (count < 1 || count > 50) {
|
||||
return { valid: false, error: "Count must be between 1 and 50" };
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
// Single tab creation (original behavior)
|
||||
async function createSingleTab(url, active, wait_for, timeout) {
|
||||
const createProperties = { active };
|
||||
if (url) {
|
||||
createProperties.url = url;
|
||||
}
|
||||
|
||||
console.log(`🔍 Creating single tab with properties:`, createProperties);
|
||||
const newTab = await chrome.tabs.create(createProperties);
|
||||
console.log(`📝 Tab created:`, { id: newTab.id, url: newTab.url, pendingUrl: newTab.pendingUrl });
|
||||
|
||||
// If URL was provided and wait_for is specified, wait for the element
|
||||
if (url && wait_for) {
|
||||
// Wait a moment for the URL to load
|
||||
if (url) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// Check if tab loaded correctly
|
||||
try {
|
||||
await waitForElement(newTab.id, wait_for, timeout);
|
||||
} catch (error) {
|
||||
const updatedTab = await chrome.tabs.get(newTab.id);
|
||||
console.log(`🔄 Tab after load check:`, { id: updatedTab.id, url: updatedTab.url, status: updatedTab.status });
|
||||
|
||||
// If URL was provided and wait_for is specified, wait for the element
|
||||
if (wait_for) {
|
||||
try {
|
||||
await waitForElement(newTab.id, wait_for, timeout);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: true,
|
||||
tab_id: newTab.id,
|
||||
url: updatedTab.url,
|
||||
actual_url: updatedTab.url,
|
||||
requested_url: url,
|
||||
warning: `Tab created but wait condition failed: ${error.message}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
tab_id: newTab.id,
|
||||
url: newTab.url,
|
||||
warning: `Tab created but wait condition failed: ${error.message}`
|
||||
url: updatedTab.url || updatedTab.pendingUrl || url,
|
||||
actual_url: updatedTab.url || updatedTab.pendingUrl,
|
||||
requested_url: url,
|
||||
active: updatedTab.active,
|
||||
status: updatedTab.status,
|
||||
title: updatedTab.title || 'New Tab',
|
||||
note: updatedTab.url === 'about:blank' && updatedTab.pendingUrl ? 'Tab is still loading' : undefined
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`❌ Error checking tab status:`, error);
|
||||
return {
|
||||
success: true,
|
||||
tab_id: newTab.id,
|
||||
url: newTab.url || 'about:blank',
|
||||
actual_url: newTab.url,
|
||||
requested_url: url,
|
||||
active: newTab.active,
|
||||
title: newTab.title || 'New Tab',
|
||||
warning: `Tab created but status check failed: ${error.message}`
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -736,6 +1110,169 @@ async function createTab(params) {
|
||||
};
|
||||
}
|
||||
|
||||
// Batch tab creation with performance throttling
|
||||
async function createTabsBatch(urls, active, wait_for, timeout, batch_settings = {}) {
|
||||
console.log('🔍 createTabsBatch called with:', { urls: urls.length, batch_settings });
|
||||
|
||||
const {
|
||||
chunk_size = 5,
|
||||
delay_between_chunks = 1000,
|
||||
delay_between_tabs = 200
|
||||
} = batch_settings || {};
|
||||
|
||||
const startTime = Date.now();
|
||||
const totalTabs = urls.length;
|
||||
const createdTabs = [];
|
||||
const errors = [];
|
||||
|
||||
// Performance warnings
|
||||
const warnings = [];
|
||||
if (totalTabs > 20) {
|
||||
warnings.push(`Creating ${totalTabs} tabs may impact browser performance`);
|
||||
}
|
||||
if (totalTabs > 45) {
|
||||
warnings.push(`Large batch (${totalTabs} tabs) may hit Chrome's tab limits or cause memory issues`);
|
||||
}
|
||||
|
||||
console.log(`🚀 Starting batch tab creation: ${totalTabs} tabs in chunks of ${chunk_size}`);
|
||||
|
||||
// Process in chunks
|
||||
for (let chunkStart = 0; chunkStart < urls.length; chunkStart += chunk_size) {
|
||||
const chunkEnd = Math.min(chunkStart + chunk_size, urls.length);
|
||||
const chunk = urls.slice(chunkStart, chunkEnd);
|
||||
const chunkIndex = Math.floor(chunkStart / chunk_size) + 1;
|
||||
const totalChunks = Math.ceil(urls.length / chunk_size);
|
||||
|
||||
// Reduced logging for better performance
|
||||
if (totalTabs > 10) {
|
||||
console.log(`📦 Chunk ${chunkIndex}/${totalChunks}`);
|
||||
}
|
||||
|
||||
// Create tabs in current chunk with delays
|
||||
for (let i = 0; i < chunk.length; i++) {
|
||||
const url = chunk[i];
|
||||
const globalIndex = chunkStart + i;
|
||||
const isLastTab = globalIndex === totalTabs - 1;
|
||||
|
||||
try {
|
||||
// Only activate the very last tab if active=true
|
||||
const shouldActivate = active && isLastTab;
|
||||
|
||||
const tab = await chrome.tabs.create({
|
||||
url: url,
|
||||
active: shouldActivate
|
||||
});
|
||||
|
||||
// Wait a moment and check actual URL
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
const updatedTab = await chrome.tabs.get(tab.id);
|
||||
|
||||
createdTabs.push({
|
||||
tab_id: tab.id,
|
||||
url: updatedTab.url || url,
|
||||
requested_url: url,
|
||||
index: globalIndex,
|
||||
active: updatedTab.active,
|
||||
title: updatedTab.title || `Tab ${globalIndex + 1}`
|
||||
});
|
||||
|
||||
// Only log for small batches to avoid context overflow
|
||||
if (totalTabs <= 5) {
|
||||
console.log(`✅ Created tab ${globalIndex + 1}/${totalTabs}: ${url} (ID: ${tab.id})`);
|
||||
}
|
||||
|
||||
// Wait between individual tabs (except last in chunk)
|
||||
if (i < chunk.length - 1) {
|
||||
await new Promise(resolve => setTimeout(resolve, delay_between_tabs));
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to create tab ${globalIndex + 1}: ${error.message}`);
|
||||
errors.push({
|
||||
index: globalIndex,
|
||||
url: url,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Wait between chunks (except after last chunk)
|
||||
if (chunkEnd < urls.length) {
|
||||
if (totalTabs <= 10) {
|
||||
console.log(`⏳ Waiting ${delay_between_chunks}ms before next chunk...`);
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, delay_between_chunks));
|
||||
}
|
||||
}
|
||||
|
||||
const executionTime = Date.now() - startTime;
|
||||
const successCount = createdTabs.length;
|
||||
const errorCount = errors.length;
|
||||
|
||||
console.log(`🏁 Batch creation complete: ${successCount}/${totalTabs} successful in ${executionTime}ms`);
|
||||
|
||||
// Prepare result - simplified to avoid context overflow
|
||||
const result = {
|
||||
success: errorCount === 0,
|
||||
batch_operation: true,
|
||||
summary: {
|
||||
total_requested: totalTabs,
|
||||
successful: successCount,
|
||||
failed: errorCount,
|
||||
execution_time_ms: executionTime
|
||||
},
|
||||
// Only include full tab details for small batches
|
||||
created_tabs: totalTabs <= 10 ? createdTabs : createdTabs.map(tab => ({
|
||||
tab_id: tab.tab_id,
|
||||
url: tab.url
|
||||
}))
|
||||
};
|
||||
|
||||
// Add warnings if any
|
||||
if (warnings.length > 0) {
|
||||
result.warnings = warnings;
|
||||
}
|
||||
|
||||
// Add errors if any
|
||||
if (errors.length > 0) {
|
||||
result.errors = errors;
|
||||
result.partial_success = successCount > 0;
|
||||
}
|
||||
|
||||
// Add active tab info
|
||||
const activeTabs = createdTabs.filter(tab => tab.active);
|
||||
if (activeTabs.length > 0) {
|
||||
result.active_tab = activeTabs[0];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Utility function to generate URLs for testing/demo purposes
|
||||
function generateTestUrls(baseUrl, count) {
|
||||
const urls = [];
|
||||
for (let i = 1; i <= count; i++) {
|
||||
urls.push(`${baseUrl}?tab=${i}`);
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
// Batch operation helper functions
|
||||
function estimateBatchTime(urlCount, batchSettings = {}) {
|
||||
const {
|
||||
chunk_size = 5,
|
||||
delay_between_chunks = 1000,
|
||||
delay_between_tabs = 200
|
||||
} = batchSettings || {};
|
||||
|
||||
const totalChunks = Math.ceil(urlCount / chunk_size);
|
||||
const timePerChunk = (chunk_size - 1) * delay_between_tabs; // delays within chunk
|
||||
const timeForChunks = totalChunks * timePerChunk;
|
||||
const timeBetweenChunks = (totalChunks - 1) * delay_between_chunks;
|
||||
|
||||
return timeForChunks + timeBetweenChunks; // in milliseconds
|
||||
}
|
||||
|
||||
async function closeTabs(params) {
|
||||
const { tab_id, tab_ids } = params;
|
||||
|
||||
@@ -773,7 +1310,11 @@ async function closeTabs(params) {
|
||||
}
|
||||
|
||||
async function listTabs(params) {
|
||||
const { current_window_only = true, include_details = true } = params;
|
||||
const {
|
||||
current_window_only = true,
|
||||
include_details = true,
|
||||
check_content_script = false
|
||||
} = params;
|
||||
|
||||
const queryOptions = {};
|
||||
if (current_window_only) {
|
||||
@@ -782,6 +1323,24 @@ async function listTabs(params) {
|
||||
|
||||
const tabs = await chrome.tabs.query(queryOptions);
|
||||
|
||||
// Check content script status if requested
|
||||
const contentScriptStatuses = new Map();
|
||||
if (check_content_script) {
|
||||
const statusPromises = tabs.map(async (tab) => {
|
||||
try {
|
||||
const status = await getTabContentScriptStatus(tab.id);
|
||||
return [tab.id, status];
|
||||
} catch (error) {
|
||||
return [tab.id, { ready: false, reason: 'error', error: error.message }];
|
||||
}
|
||||
});
|
||||
|
||||
const results = await Promise.all(statusPromises);
|
||||
results.forEach(([tabId, status]) => {
|
||||
contentScriptStatuses.set(tabId, status);
|
||||
});
|
||||
}
|
||||
|
||||
const tabList = tabs.map(tab => {
|
||||
const basicInfo = {
|
||||
id: tab.id,
|
||||
@@ -790,6 +1349,16 @@ async function listTabs(params) {
|
||||
title: tab.title
|
||||
};
|
||||
|
||||
// Add content script status if checked
|
||||
if (check_content_script) {
|
||||
const scriptStatus = contentScriptStatuses.get(tab.id);
|
||||
basicInfo.content_script = {
|
||||
ready: scriptStatus?.ready || false,
|
||||
reason: scriptStatus?.reason || 'unknown',
|
||||
injectable: isInjectableUrl(tab.url)
|
||||
};
|
||||
}
|
||||
|
||||
if (include_details) {
|
||||
return {
|
||||
...basicInfo,
|
||||
@@ -805,11 +1374,28 @@ async function listTabs(params) {
|
||||
return basicInfo;
|
||||
});
|
||||
|
||||
// Calculate summary statistics
|
||||
const summary = {
|
||||
total_tabs: tabList.length,
|
||||
active_tab: tabs.find(tab => tab.active)?.id || null
|
||||
};
|
||||
|
||||
if (check_content_script) {
|
||||
const readyTabs = tabList.filter(tab => tab.content_script?.ready).length;
|
||||
const injectableTabs = tabList.filter(tab => tab.content_script?.injectable).length;
|
||||
|
||||
summary.content_script_stats = {
|
||||
ready_count: readyTabs,
|
||||
injectable_count: injectableTabs,
|
||||
restricted_count: tabList.length - injectableTabs
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
tabs: tabList,
|
||||
count: tabList.length,
|
||||
active_tab: tabs.find(tab => tab.active)?.id || null
|
||||
summary
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1015,30 +1601,50 @@ async function getHistory(params) {
|
||||
async function getSelectedText(params) {
|
||||
const {
|
||||
include_metadata = true,
|
||||
max_length = 10000
|
||||
max_length = 10000,
|
||||
tab_id
|
||||
} = params;
|
||||
|
||||
try {
|
||||
// Get the active tab
|
||||
const [activeTab] = await chrome.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
let targetTab;
|
||||
|
||||
if (!activeTab) {
|
||||
return {
|
||||
success: false,
|
||||
error: "No active tab found",
|
||||
selected_text: "",
|
||||
metadata: {
|
||||
execution_time: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
if (tab_id) {
|
||||
// Use specific tab
|
||||
try {
|
||||
targetTab = await chrome.tabs.get(tab_id);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Tab ${tab_id} not found or inaccessible`,
|
||||
selected_text: "",
|
||||
metadata: {
|
||||
execution_time: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Get the active tab
|
||||
const [activeTab] = await chrome.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
|
||||
if (!activeTab) {
|
||||
return {
|
||||
success: false,
|
||||
error: "No active tab found",
|
||||
selected_text: "",
|
||||
metadata: {
|
||||
execution_time: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
}
|
||||
targetTab = activeTab;
|
||||
}
|
||||
|
||||
// Execute script to get selected text
|
||||
const results = await chrome.scripting.executeScript({
|
||||
target: { tabId: activeTab.id },
|
||||
target: { tabId: targetTab.id },
|
||||
func: () => {
|
||||
const selection = window.getSelection();
|
||||
const selectedText = selection.toString();
|
||||
@@ -1120,9 +1726,9 @@ async function getSelectedText(params) {
|
||||
metadata: {
|
||||
execution_time: new Date().toISOString(),
|
||||
tab_info: {
|
||||
id: activeTab.id,
|
||||
url: activeTab.url,
|
||||
title: activeTab.title
|
||||
id: targetTab.id,
|
||||
url: targetTab.url,
|
||||
title: targetTab.title
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1145,9 +1751,9 @@ async function getSelectedText(params) {
|
||||
metadata: {
|
||||
execution_time: new Date().toISOString(),
|
||||
tab_info: {
|
||||
id: activeTab.id,
|
||||
url: activeTab.url,
|
||||
title: activeTab.title
|
||||
id: targetTab.id,
|
||||
url: targetTab.url,
|
||||
title: targetTab.title
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -283,6 +283,10 @@ class BrowserAutomation {
|
||||
case "page_scroll":
|
||||
result = await this.scrollPage(data);
|
||||
break;
|
||||
case "ping":
|
||||
// Health check for background tab content script readiness
|
||||
result = { status: "ready", timestamp: Date.now(), url: window.location.href };
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown action: ${action}`);
|
||||
}
|
||||
|
||||
@@ -803,6 +803,54 @@ function formatLinksResult(result, metadata) {
|
||||
}
|
||||
|
||||
function formatTabCreateResult(result, metadata) {
|
||||
// Handle batch operations
|
||||
if (result.batch_operation) {
|
||||
const { summary, created_tabs, settings_used, warnings, errors } = result;
|
||||
|
||||
let output = `🚀 Batch tab creation completed
|
||||
📊 Summary: ${summary.successful}/${summary.total_requested} tabs created successfully
|
||||
⏱️ Execution time: ${summary.execution_time_ms}ms
|
||||
📦 Chunks processed: ${summary.chunks_processed}
|
||||
|
||||
`;
|
||||
|
||||
// Add warnings if any
|
||||
if (warnings && warnings.length > 0) {
|
||||
output += `⚠️ Warnings:\n${warnings.map(w => ` • ${w}`).join('\n')}\n\n`;
|
||||
}
|
||||
|
||||
// Add created tabs info
|
||||
if (created_tabs && created_tabs.length > 0) {
|
||||
output += `✅ Created tabs:\n`;
|
||||
created_tabs.forEach((tab, index) => {
|
||||
output += ` ${index + 1}. ${tab.title || 'New Tab'} (ID: ${tab.tab_id})\n`;
|
||||
output += ` 🌐 ${tab.actual_url || tab.url}\n`;
|
||||
if (tab.active) output += ` 🎯 Active tab\n`;
|
||||
});
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
// Add errors if any
|
||||
if (errors && errors.length > 0) {
|
||||
output += `❌ Errors:\n`;
|
||||
errors.forEach((error, index) => {
|
||||
output += ` ${index + 1}. ${error.url}: ${error.error}\n`;
|
||||
});
|
||||
output += '\n';
|
||||
}
|
||||
|
||||
// Add settings used (if available)
|
||||
if (settings_used) {
|
||||
output += `⚙️ Settings used:\n`;
|
||||
output += ` • Chunk size: ${settings_used.chunk_size}\n`;
|
||||
output += ` • Delay between chunks: ${settings_used.delay_between_chunks}ms\n`;
|
||||
output += ` • Delay between tabs: ${settings_used.delay_between_tabs}ms\n`;
|
||||
}
|
||||
|
||||
return output + `\n${JSON.stringify(metadata, null, 2)}`;
|
||||
}
|
||||
|
||||
// Handle single tab operations (existing logic)
|
||||
if (result.success) {
|
||||
return `✅ New tab created successfully
|
||||
🆔 Tab ID: ${result.tab_id}
|
||||
@@ -907,7 +955,7 @@ function getFallbackTools() {
|
||||
{
|
||||
name: "page_analyze",
|
||||
description:
|
||||
"🎯 Analyze page structure with anti-detection bypass (Extension required)",
|
||||
"🔍 BACKGROUND TAB READY: Analyze any tab without switching! Two-phase intelligent page analysis with token efficiency optimization. Use tab_id parameter to analyze background tabs while staying on current page. (Extension required)",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -930,7 +978,7 @@ function getFallbackTools() {
|
||||
{
|
||||
name: "page_extract_content",
|
||||
description:
|
||||
"📄 Extract structured content with smart summarization (Extension required)",
|
||||
"📄 BACKGROUND TAB READY: Extract content from any tab without switching! Perfect for analyzing multiple research tabs, articles, or pages simultaneously. Use tab_id to target specific background tabs. (Extension required)",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -952,7 +1000,7 @@ function getFallbackTools() {
|
||||
{
|
||||
name: "element_click",
|
||||
description:
|
||||
"🖱️ Click page elements with smart targeting (Extension required)",
|
||||
"🖱️ BACKGROUND TAB READY: Click elements in any tab without switching! Perform actions on background tabs while staying on current page. Use tab_id to target specific tabs. (Extension required)",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -972,7 +1020,7 @@ function getFallbackTools() {
|
||||
{
|
||||
name: "element_fill",
|
||||
description:
|
||||
"✍️ Fill input fields with anti-detection bypass for Twitter/X, LinkedIn, Facebook (Extension required)",
|
||||
"✏️ BACKGROUND TAB READY: Fill forms in any tab without switching! Enhanced focus and event simulation for modern web apps with anti-detection bypass for Twitter/X, LinkedIn, Facebook. Use tab_id to fill forms in background tabs. (Extension required)",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -1035,27 +1083,73 @@ function getFallbackTools() {
|
||||
// Tab Management Tools
|
||||
{
|
||||
name: "tab_create",
|
||||
description: "🆕 Create a new tab with optional URL and activation (Extension required)",
|
||||
description: "Creates tabs. CRITICAL: For multiple identical tabs, ALWAYS use 'count' parameter! Examples: {url: 'https://x.com', count: 5} creates 5 Twitter tabs. {url: 'https://github.com', count: 10} creates 10 GitHub tabs. Single tab: {url: 'https://example.com'}. Multiple different URLs: {urls: ['url1', 'url2']}.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
examples: [
|
||||
{ url: "https://x.com", count: 5 }, // CORRECT: Creates 5 identical Twitter tabs in one batch
|
||||
{ url: "https://github.com", count: 10 }, // CORRECT: Creates 10 GitHub tabs
|
||||
{ urls: ["https://x.com/post1", "https://x.com/post2", "https://google.com"] }, // CORRECT: Different URLs in batch
|
||||
{ url: "https://example.com" } // Single tab only
|
||||
],
|
||||
properties: {
|
||||
url: {
|
||||
type: "string",
|
||||
description: "URL to open in the new tab (optional)"
|
||||
description: "Single URL to open. Can be used with 'count' to create multiple identical tabs"
|
||||
},
|
||||
urls: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "PREFERRED FOR MULTIPLE URLS: Array of URLs to open ALL AT ONCE in a single batch operation. Pass ALL URLs here instead of making multiple calls! Example: ['https://x.com/post1', 'https://x.com/post2', 'https://google.com']",
|
||||
maxItems: 100
|
||||
},
|
||||
count: {
|
||||
type: "number",
|
||||
default: 1,
|
||||
minimum: 1,
|
||||
maximum: 50,
|
||||
description: "REQUIRED FOR MULTIPLE IDENTICAL TABS: Set this to N to create N copies of the same URL. For '5 Twitter tabs' use count=5 with url='https://x.com'. DO NOT make 5 separate calls!"
|
||||
},
|
||||
active: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
description: "Whether to activate the new tab"
|
||||
description: "Whether to activate the last created tab (single tab only)"
|
||||
},
|
||||
wait_for: {
|
||||
type: "string",
|
||||
description: "CSS selector to wait for after tab creation (if URL provided)"
|
||||
description: "CSS selector to wait for after tab creation (single tab only)"
|
||||
},
|
||||
timeout: {
|
||||
type: "number",
|
||||
default: 10000,
|
||||
description: "Maximum wait time in milliseconds"
|
||||
description: "Maximum wait time per tab in milliseconds"
|
||||
},
|
||||
batch_settings: {
|
||||
type: "object",
|
||||
description: "Performance control settings for batch operations",
|
||||
properties: {
|
||||
chunk_size: {
|
||||
type: "number",
|
||||
default: 5,
|
||||
minimum: 1,
|
||||
maximum: 10,
|
||||
description: "Number of tabs to create per batch"
|
||||
},
|
||||
delay_between_chunks: {
|
||||
type: "number",
|
||||
default: 1000,
|
||||
minimum: 100,
|
||||
maximum: 5000,
|
||||
description: "Delay between batches in milliseconds"
|
||||
},
|
||||
delay_between_tabs: {
|
||||
type: "number",
|
||||
default: 200,
|
||||
minimum: 50,
|
||||
maximum: 1000,
|
||||
description: "Delay between individual tabs in milliseconds"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1080,7 +1174,7 @@ function getFallbackTools() {
|
||||
},
|
||||
{
|
||||
name: "tab_list",
|
||||
description: "📋 Get list of all open tabs with their details (Extension required)",
|
||||
description: "📋 TAB DISCOVERY: Get list of all open tabs with IDs for background tab targeting! Shows content script readiness status and tab details. Essential for multi-tab workflows - use tab IDs with other tools to work on background tabs. (Extension required)",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -1215,7 +1309,7 @@ function getFallbackTools() {
|
||||
},
|
||||
{
|
||||
name: "get_selected_text",
|
||||
description: "📝 Get the currently selected text on the page (Extension required)",
|
||||
description: "📝 BACKGROUND TAB READY: Get selected text from any tab without switching! Perfect for collecting quotes, citations, or highlighted content from multiple research tabs simultaneously. (Extension required)",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -1234,7 +1328,7 @@ function getFallbackTools() {
|
||||
},
|
||||
{
|
||||
name: "page_scroll",
|
||||
description: "📜 Scroll the page in various directions - critical for long pages (Extension required)",
|
||||
description: "📜 BACKGROUND TAB READY: Scroll any tab without switching! Critical for long pages. Navigate through content in background tabs while staying on current page. Use tab_id to target specific tabs. (Extension required)",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
|
||||
Reference in New Issue
Block a user