Install
openclaw skills install freechat-deepseekopenclaw browser open https://chat.deepseek.com and login it, then input questions from user's messages, extract response of deepseek chat to reply user.
openclaw skills install freechat-deepseek⚠️ Important for OpenClaw >= v3.13: Use direct Chrome attachment (
profile: "user") instead of the relay extension. See "Direct Chrome Attachment" section below.Version Check: Before executing any browser actions, check OpenClaw version (
openclaw --version). If >= v3.13, useprofile: "user"for direct Chrome attachment. This avoids the need for the Chrome relay extension and provides more stable browser automation.
To make your code work with both old and new OpenClaw versions, use this dynamic profile selection:
// Determine which profile to use based on OpenClaw version
async function getBrowserProfile() {
try {
// Check OpenClaw version
const versionResult = await exec({ command: "openclaw --version" });
const versionMatch = versionResult.match(/(\d+)\.(\d+)\.(\d+)/);
if (versionMatch) {
const major = parseInt(versionMatch[1]);
const minor = parseInt(versionMatch[2]);
const patch = parseInt(versionMatch[3]);
// v3.13+ uses "user" profile for direct Chrome
if (major > 3 || (major === 3 && minor > 13) || (major === 3 && minor === 13 && patch >= 0)) {
console.log("✅ Using direct Chrome (profile: 'user') for OpenClaw >= v3.13");
return "user";
}
}
console.log("ℹ️ Using legacy profile 'openclaw' for older OpenClaw versions");
return "openclaw";
} catch (error) {
console.warn("⚠️ Could not determine version, defaulting to 'user'");
return "user"; // Default to user profile
}
}
// Usage in your code:
const profile = await getBrowserProfile();
// Then use it in browser calls:
const result = await browser({
action: "open",
profile: profile,
url: "https://chat.deepseek.com/"
});
// 1. Check version - if >= v3.13, use "user" profile
// 2. Open Chrome directly (no extension needed)
const result = await browser({
action: "open",
profile: "user", // Use "user" for v3.13+
url: "about:blank"
});
// 3. Navigate to DeepSeek
await browser({
action: "navigate",
profile: "user",
targetId: result.targetId,
url: "https://chat.deepseek.com/"
});
// 4. Continue with the workflow below...
For OpenClaw version >= v3.13, use direct Chrome attachment:
// Check user browser status
const browserStatus = await browser({
action: "status",
profile: "user"
});
console.log("Browser status:", browserStatus);
// Open Chrome directly (no extension needed)
const result = await browser({
action: "open",
profile: "user",
url: "about:blank"
});
const tabId = result.targetId;
console.log("Opened tab:", tabId);
// Navigate to DeepSeek
await browser({
action: "navigate",
profile: "user",
targetId: tabId,
url: "https://chat.deepseek.com/"
});
// List all open tabs
const tabs = await browser({
action: "tabs",
profile: "user"
});
console.log("Open tabs:", tabs);
// Switch to specific tab
await browser({
action: "focus",
profile: "user",
targetId: "1" // Tab ID from tabs list
});
| Feature | profile: "user" | profile: "openclaw" (legacy) |
|---|---|---|
| Chrome extension needed | ❌ No | ✅ Yes |
| Uses | Local Chrome installation | Openclaw-managed browser |
| Reliability | ✅ More stable | May have issues |
| Login state | Uses your logged-in Chrome | Fresh browser session |
If profile: "user" times out:
openclaw gateway restartTo use profile: "user" for direct Chrome attachment, users must configure Chrome properly:
Step-by-step:
Open Chrome settings page:
chrome://inspect/#remote-debugging
Enable the option:

Restart Chrome if the option was just enabled
Run this to verify Chrome is ready:
// Check user browser status
const browserStatus = await browser({
action: "status",
profile: "user"
});
if (browserStatus.running === true) {
console.log("✅ Chrome is ready for direct attachment!");
} else {
console.log("❌ Chrome not ready. Check chrome://inspect settings.");
}
| Issue | Solution |
|---|---|
| Timeout errors | Ensure "Allow remote debugging" is enabled in chrome://inspect |
| Chrome not detected | Make sure Chrome version >= 144 is installed |
| Connection refused | Restart Chrome and try again |
| Already in use | Close other Chrome instances or use a different Chrome profile |
For users who prefer using the Chrome relay extension instead of direct Chrome attachment:
Open Chrome Web Store:
https://chromewebstore.google.com/detail/openclaw-browser-relay/nglingapjinhecnfejdcpihlpneeadjp
Click "Add to Chrome" to install the extension
Approve permissions when prompted
Once installed, activate it before running browser automation:
Then use profile: "chrome-relay" in your browser calls:
// Check relay status
const relayStatus = await browser({
action: "status",
profile: "chrome-relay"
});
// Open tab via relay
const result = await browser({
action: "open",
profile: "chrome-relay",
url: "https://chat.deepseek.com/"
});
// Take snapshot
await browser({
action: "snapshot",
profile: "chrome-relay",
targetId: result.targetId
});
| Status | Meaning |
|---|---|
running: false | Relay not activated - click the toolbar icon |
cdpReady: false | CDP not connected - click the toolbar icon |
running: true | Ready to use! |
| Feature | Direct Chrome (user) | Relay Extension (chrome-relay) |
|---|---|---|
| Setup required | Chrome >= 144 + remote debugging | Install extension |
| No. of steps | More initial setup | Easier install |
| Stability | ✅ More stable | May have occasional issues |
| Login state | Uses your Chrome profile | Uses extension's session |
| Recommended for | Power users | Beginners |
Automate login with scanning qr code snapshot, input questions from user messages, and extract responses from DeepSeek chat, then reply to user.
Key Feature: Pure browser automation + DOM extraction. No LLM requests or processing - just raw text from the rendered page.
✅ USE this skill when:
DeepSeek Chat (https://chat.deepseek.com)DeepSeek ChatDeepSeek Chat Page with direct output❌ DON'T use this skill when:
For OpenClaw >= v3.13:
profile: "user" (direct Chrome attachment)For older versions (legacy):
openclaw browser status, get enabled: trueprofile: "openclaw"┌─────────────────────────────────────────────────────────────────┐
│ 1. Open chat.deepseek.com OR check existing tab │
└─────────────────────────────────┬───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. Check login status - QR Code needed? │
│ - If login form → Take snapshot of login page │
│ - If chat interface → Already logged in, proceed to Step 3 │
└─────────────────────────────────┬───────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────────────────┐
│ 3. Send QR Code to enabled channels (iMessage/WhatsApp/QQBot/Feishu/WeCom/Slack │
│ - Send snapshot to user via imsg │
│ - If WhatsApp is active, send via WhatsApp │
│ - If QQBot is active, send via QQBot, Feishu, WeCom, Slack, etc │
└─────────────────────────────────┬────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. Wait for login (with timeout & retry loop) │
│ - Poll every 5 seconds for login success │
│ - If timeout: refresh page, re-snapshot QR, retry send │
│ - Repeat until logged in successfully │
└─────────────────────────────────┬───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 5. Input question and press Enter │
└─────────────────────────────────┬───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 6. Extract response from DOM and reply to user │
└─────────────────────────────────────────────────────────────────────────┘
IMPORTANT FOR AGENTS: Always use this simple extraction function instead of writing complex code. It's tested and reliable.
// SIMPLE & RELIABLE DeepSeek response extraction
// Use this function instead of writing your own extraction code
async function getDeepSeekResponseSimple(tabId) {
try {
const response = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `(function() {
// Get all text from the page body
const allText = document.body.innerText || document.body.textContent || '';
// Split into lines
const lines = allText.split('\n');
// Filter out system messages and find the response
const responseLines = [];
let foundResponse = false;
for (let i = lines.length - 1; i >= 0; i--) {
const line = lines[i].trim();
// Skip empty lines and system messages
if (line.length < 20) continue;
if (line.includes('AI-generated')) continue;
if (line.includes('Message DeepSeek')) continue;
if (line.includes('How can I help')) continue;
if (line.includes('Upload docs')) continue;
if (line.includes('⌘')) continue;
// This is likely the response
responseLines.unshift(line);
foundResponse = true;
}
// Combine response lines
if (foundResponse && responseLines.length > 0) {
return responseLines.join('\n\n').trim();
}
return 'No response found';
})()`,
returnByValue: true
});
return response;
} catch (error) {
console.error("Error extracting response:", error);
return `Error: ${error.message}`;
}
}
// Usage example:
// 1. Ask your question
// 2. Wait 10-15 seconds for response
// 3. Call: const answer = await getDeepSeekResponseSimple(tabId);
// 4. Use the answer
// Try to find existing DeepSeek tab first
const existingTabs = await sessions_list({ /* filter for chat.deepseek.com */ });
if (existingTabs.length > 0) {
console.log("✅ Reusing existing DeepSeek tab:", existingTabs[0]);
tabId = existingTabs[0];
} else {
// Open new tab
const result = await browser({
action: "open",
profile: "openclaw",
targetUrl: "https://chat.deepseek.com/"
});
tabId = result.targetId;
console.log("✅ Opened new DeepSeek tab:", tabId);
}
// Wait for page to load
await new Promise(r => setTimeout(r, 3000));
// Check if login required (QR Code scan) or already logged in
await browser({
action: "snapshot",
profile: "openclaw",
targetId: tabId
});
// Evaluate login status
const loginStatus = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `{
const loginForm = document.querySelector('input[type="tel"], input[placeholder*="Phone"]');
const qrCode = document.querySelector('iframe[src*="wechat"], img[alt*="WeChat"], img[src*="wechat"]');
const chatInput = document.querySelector('textarea[placeholder*="Message DeepSeek"]');
const userAvatar = document.querySelector('[class*="avatar"], [class*="user"]');
JSON.stringify({
needLogin: !!(loginForm || qrCode),
hasChatInput: !!chatInput,
hasUser: !!userAvatar,
hasLoginForm: !!loginForm,
hasQrCode: !!qrCode
});
}`,
returnByValue: true
});
const status = JSON.parse(loginStatus);
if (!status.needLogin && status.hasChatInput) {
console.log("✅ Already logged in!");
// Proceed to Step 5 (input question)
} else {
console.log("⚠️ QR Code login required!");
// Proceed to Step 3 (send QR code)
}
| Status | Check | Action Required |
|---|---|---|
| ✅ Already Logged In | Chat input visible + user avatar | Proceed to Step 5 |
| ❌ Need Login | QR Code or login form visible | Proceed to Step 3 |
// Navigate to login page explicitly
await browser({
action: "navigate",
profile: "openclaw",
targetId: tabId,
targetUrl: "https://chat.deepseek.com/sign_in"
});
await new Promise(r => setTimeout(r, 2000));
// Take screenshot of QR Code login page
const screenshotResult = await browser({
action: "screenshot",
profile: "openclaw",
targetId: tabId
});
// Get the screenshot path from the browser tool response
const screenshotPath = screenshotResult.path;
// Get user phone number (configure this in your implementation)
const userPhone = process.env.DEEPSEEK_PHONE || "+1234567890000"; // Configure this
// Try to send via enabled and running channels such as iMessage (most reliable on Mac)
try {
await exec({
command: 'imsg send --to "' + userPhone + '" --text "Please scan this QR code with WeChat to login to DeepSeek:" --file "' + screenshotPath + '"'
});
console.log("✅ QR Code sent via iMessage");
} catch (e) {
console.log("⚠️ iMessage failed:", e.message);
}
// Try to send via WhatsApp if available
try {
await exec({
command: 'openclaw message send --channel whatsapp --target "' + userPhone + '" -m "Please scan QR code with WeChat to login" --media "' + screenshotPath + '"'
});
console.log("✅ QR Code sent via WhatsApp");
} catch (e) {
console.log("⚠️ WhatsApp not available:", e.message);
}
// Try to send via QQBot (use media tags)
try {
await exec({
command: 'openclaw message send --channel qqbot -m "Please scan this QR code with WeChat to login to DeepSeek:\\n<qqimg>' + screenshotPath + '</qqimg>"'
});
console.log("✅ QR Code sent via QQBot");
} catch (e) {
console.log("⚠️ QQBot not available:", e.message);
}
Send QR Code to session which is executing this skill firstly. And also send by other avlabile channels:
imsg) - Most reliable on Mac (uses userPhone variable)userPhone variable)// Poll for login success
let attempts = 0;
const maxAttempts = 24; // 24 * 5 seconds = 2 minutes max
const retryInterval = 5000;
async function checkLoginSuccess() {
const result = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `{
const chatInput = document.querySelector('textarea[placeholder*="Message DeepSeek"]');
const userAvatar = document.querySelector('[class*="avatar"], [class*="user"]');
{ hasChatInput: !!chatInput, hasUser: !!userAvatar };
}`,
returnByValue: true
});
return JSON.parse(result);
}
while (attempts < maxAttempts) {
await new Promise(r => setTimeout(r, retryInterval));
const loginResult = await checkLoginSuccess();
if (loginResult.hasChatInput && loginResult.hasUser) {
console.log("✅ Login successful!");
break;
}
attempts++;
console.log("⏳ Waiting for login... (" + attempts + "/" + maxAttempts + ")");
// If timeout (after 12 attempts = 1 minute), refresh and retry
if (attempts === 12) {
console.log("⏰ Timeout, refreshing QR code...");
await browser({
action: "navigate",
profile: "openclaw",
targetId: tabId,
targetUrl: "https://chat.deepseek.com/sign_in"
});
await new Promise(r => setTimeout(r, 3000));
// Re-take screenshot and send again
await browser({
action: "screenshot",
profile: "openclaw",
targetId: tabId
});
// Re-take screenshot and send again
const newScreenshotResult = await browser({
action: "screenshot",
profile: "openclaw",
targetId: tabId
});
const newScreenshotPath = newScreenshotResult.path;
// Re-send QR code
try {
await exec({
command: 'imsg send --to "' + userPhone + '" --text "QR code refreshed - please scan again with WeChat" --file "' + newScreenshotPath + '"'
});
} catch (e) {}
}
}
if (attempts >= maxAttempts) {
throw new Error("Login timeout - user did not complete QR scan in time");
}
| Attempt | Time | Action |
|---|---|---|
| 1-12 | 0-60s | Poll every 5s, wait for scan |
| 12 | 60s | Refresh page, re-send QR code |
| 13-24 | 65-120s | Continue polling |
| 24 | 120s | Fail with timeout error |
// Ensure we're on chat page
await browser({
action: "navigate",
profile: "openclaw",
targetId: tabId,
targetUrl: "https://chat.deepseek.com/"
});
await new Promise(r => setTimeout(r, 2000));
// Take snapshot to get fresh refs
await browser({
action: "snapshot",
profile: "openclaw",
targetId: tabId
});
// Type message into input box (use textbox ref from snapshot)
await browser({
action: "act",
kind: "type",
profile: "openclaw",
targetId: tabId,
ref: "e102", // Get from snapshot - textarea ref
text: "Your question here"
});
console.log("✅ Text typed into input box");
// Press Enter to submit
await browser({
action: "act",
kind: "press",
profile: "openclaw",
targetId: tabId,
key: "Enter"
});
console.log("✅ Enter key pressed");
NEVER send raw html content with <tool_call> to user session.
// Wait for response to generate (8-15 seconds depending on complexity)
await new Promise(r => setTimeout(r, 10000));
// Extract assistant response from page body
const response = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `{
// Get all paragraph elements from the entire page body
const body = document.body;
const paras = Array.from(body.querySelectorAll('p'));
// Filter to find assistant messages (not user messages, not system text)
const assistantMsgs = paras.filter(p => {
const text = p.innerText || '';
const parent = p.parentElement;
// Exclude system messages and user messages
return text.length > 15 &&
!text.includes('AI-generated') &&
!text.includes('Upload docs') &&
!text.includes('How can I help') &&
!text.includes('⌘') &&
parent && !parent.closest('header') &&
parent && !parent.closest('footer');
});
// Get the most recent assistant message
const lastResponse = assistantMsgs.length > 0
? assistantMsgs[assistantMsgs.length - 1].innerText
: 'No response found';
// Clean up the response
return lastResponse.trim();
}`,
returnByValue: true
});
console.log("✅ DeepSeek Response:", response);
| Element | Strategy |
|---|---|
| Container | document.body |
| Message type | <p> tags |
| Filter out | AI-generated, Upload docs, Help text, Keyboard shortcuts |
| Get | Last matching <p> from body |
// ============ DEEPSEEK CHAT AUTOMATION ============
const profile = "openclaw";
// Get user phone number from USER.md or configuration
// Example: Read from USER.md or use environment variable
const userPhone = process.env.DEEPSEEK_PHONE || "+1234567890000"; // Default fallback - configure this
// Step 1: Open or reuse tab
const openResult = await browser({
action: "open",
profile: profile,
targetUrl: "https://chat.deepseek.com/"
});
const tabId = openResult.targetId;
await new Promise(r => setTimeout(r, 3000));
// Step 2: Check login status
await browser({ action: "snapshot", profile, targetId: tabId });
const loginCheck = await browser({
action: "act",
kind: "evaluate",
profile,
targetId: tabId,
fn: `{
const chatInput = document.querySelector('textarea[placeholder*="Message DeepSeek"]');
const qrCode = document.querySelector('img[src*="wechat"]');
{ needLogin: !chatInput, hasQrCode: !!qrCode };
}`,
returnByValue: true
});
const status = JSON.parse(loginCheck);
if (!status.needLogin) {
console.log("Already logged in!");
} else {
// Step 3: Send QR code
await browser({ action: "navigate", profile, targetId: tabId, targetUrl: "https://chat.deepseek.com/sign_in" });
await new Promise(r => setTimeout(r, 2000));
const screenshotResult = await browser({ action: "screenshot", profile, targetId: tabId });
const screenshotPath = screenshotResult.path;
// Send via iMessage
await exec({
command: 'imsg send --to "' + userPhone + '" --text "Please scan QR code to login to DeepSeek" --file "' + screenshotPath + '"'
});
// Step 4: Wait for login with retry
let attempts = 0;
while (attempts < 24) {
await new Promise(r => setTimeout(r, 5000));
const result = await browser({
action: "act",
kind: "evaluate",
profile,
targetId: tabId,
fn: `{
const chatInput = document.querySelector('textarea[placeholder*="Message DeepSeek"]');
!!chatInput;
}`,
returnByValue: true
});
if (result === 'true') {
console.log("Login successful!");
break;
}
attempts++;
// Refresh QR every 60 seconds
if (attempts === 12) {
await browser({ action: "navigate", profile, targetId: tabId, targetUrl: "https://chat.deepseek.com/sign_in" });
await new Promise(r => setTimeout(r, 3000));
const newScreenshotResult = await browser({ action: "screenshot", profile, targetId: tabId });
const newScreenshotPath = newScreenshotResult.path;
await exec({
command: 'imsg send --to "' + userPhone + '" --text "QR refreshed - please scan again" --file "' + newScreenshotPath + '"'
});
}
}
}
// Step 5: Input question
await browser({ action: "navigate", profile, targetId: tabId, targetUrl: "https://chat.deepseek.com/" });
await new Promise(r => setTimeout(r, 2000));
await browser({ action: "snapshot", profile, targetId: tabId });
await browser({
action: "act",
kind: "type",
profile,
targetId: tabId,
ref: "e102", // From snapshot
text: "What is the capital of France?"
});
await browser({ action: "act", kind: "press", profile, targetId: tabId, key: "Enter" });
// Step 6: Extract response
await new Promise(r => setTimeout(r, 10000));
// Robust response extraction
const response = await browser({
action: "act",
kind: "evaluate",
profile,
targetId: tabId,
fn: `(function() {
// Get all paragraph elements
const paragraphs = Array.from(document.querySelectorAll('p'));
// Filter to find assistant responses
const assistantResponses = paragraphs.filter(p => {
const text = p.innerText || p.textContent || '';
return text.length > 20 &&
!text.includes('AI-generated') &&
!text.includes('Upload docs') &&
!text.includes('How can I help') &&
!text.includes('⌘');
});
// Get the most recent (last) response
if (assistantResponses.length > 0) {
return assistantResponses[assistantResponses.length - 1].innerText.trim();
}
// Fallback: try to find any substantial text
const bodyText = document.body.innerText || document.body.textContent || '';
const lines = bodyText.split('\n').filter(line =>
line.trim().length > 50 &&
!line.includes('AI-generated') &&
!line.includes('How can I help')
);
return lines.length > 0 ? lines[lines.length - 1].trim() : 'No response found';
})()`,
returnByValue: true
});
console.log("Response:", response);
// Reply to user with response
| Element | Selector |
|---|---|
| Chat input | textarea[placeholder*="Message DeepSeek"] |
| Send (Enter) | Press Enter key |
| QR Code | img[src*="wechat"], iframe[src*="wechat"] |
| User avatar | [class*="avatar"], [class*="user"] |
| Response content | Last <p> in document.body (filtered) |
The user's phone number can be configured in several ways:
Environment Variable: Set DEEPSEEK_PHONE environment variable
export DEEPSEEK_PHONE="+1234567890000"
USER.md: Add phone number to USER.md file
# USER.md
Phone: +1234567890000
Configuration File: Add to OpenClaw configuration
// In your implementation code
const userPhone = config.userPhone || process.env.DEEPSEEK_PHONE || "+1234567890000";
The screenshot path is automatically returned by the browser tool's screenshot action:
const screenshotResult = await browser({ action: "screenshot", profile: "openclaw", targetId: tabId });
const screenshotPath = screenshotResult.path; // Use the actual path from result
Important: Never assume a fixed path like /Users/chris/.openclaw/media/browser/<uuid>.jpg. Always use the path returned by the browser tool.
kind: "press" not JS dispatchEvent)| Issue | Solution |
|---|---|
| Input not found | Refresh page, take new snapshot |
| Enter not working | Use browser act kind="press" with key="Enter" |
| ❌ Enter via JS dispatchEvent doesn't work | DeepSeek uses React/event validation - must use kind: "press" with key: "Enter" instead |
| Response not found | Wait longer, check page rendering |
| QR Code timeout | Automatically refreshes and re-sends |
| Login with phone | DO NOT ask and input phone number |
| All channels fail | Ask user to check browser tab manually |
When a user wants to retry with a fresh question (e.g., after getting "Sorry, that's beyond my current scope"), use this optimized workflow:
// Function to start a fresh chat session
async function startFreshChat(tabId) {
// Navigate to main page to ensure clean state
await browser({
action: "navigate",
profile: "openclaw",
targetId: tabId,
targetUrl: "https://chat.deepseek.com/"
});
await new Promise(r => setTimeout(r, 2000));
// Take snapshot to get fresh refs
await browser({
action: "snapshot",
profile: "openclaw",
targetId: tabId
});
// Check if we're on a fresh chat page
const status = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `(function() {
const welcomeMsg = document.querySelector('img[alt*="DeepSeek"], img[src*="deepseek"]');
const chatInput = document.querySelector('textarea[placeholder*="Message DeepSeek"]');
const previousChat = document.querySelector('[class*="message"]');
return JSON.stringify({
isFresh: !!welcomeMsg && !!chatInput && !previousChat,
hasWelcome: !!welcomeMsg,
hasInput: !!chatInput,
hasPreviousChat: !!previousChat
});
})()`,
returnByValue: true
});
const statusObj = JSON.parse(status);
if (!statusObj.isFresh) {
// Click "New chat" in sidebar (more reliable than top button)
const sidebarResult = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `(function() {
// Find "New chat" link in sidebar
const sidebarLinks = Array.from(document.querySelectorAll('a'));
const newChatLink = sidebarLinks.find(link =>
link.innerText.includes('New chat') ||
link.textContent.includes('新聊天') ||
(link.querySelector('div') && link.querySelector('div').innerText.includes('New chat'))
);
if (newChatLink) {
newChatLink.click();
return true;
}
return false;
})()`,
returnByValue: true
});
if (!sidebarResult) {
// Fallback: use keyboard shortcut Cmd+J (⌘J)
await browser({
action: "act",
kind: "press",
profile: "openclaw",
targetId: tabId,
key: "Meta", // Command key on Mac
modifiers: ["Meta"]
});
await new Promise(r => setTimeout(r, 100));
await browser({
action: "act",
kind: "type",
profile: "openclaw",
targetId: tabId,
text: "j"
});
await new Promise(r => setTimeout(r, 100));
await browser({
action: "act",
kind: "press",
profile: "openclaw",
targetId: tabId,
key: "Meta",
modifiers: []
});
}
await new Promise(r => setTimeout(r, 2000));
}
return true;
}
// Function to continue in current chat but clear previous question
async function continueInCurrentChat(tabId, newQuestion) {
// Just type the new question directly
await browser({
action: "snapshot",
profile: "openclaw",
targetId: tabId
});
// Find and focus the input
const inputFound = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `(function() {
const input = document.querySelector('textarea[placeholder*="Message DeepSeek"], input[placeholder*="Message DeepSeek"]');
if (input) {
input.focus();
input.value = '';
return true;
}
return false;
})()`,
returnByValue: true
});
if (!inputFound) {
throw new Error("Chat input not found");
}
// Type the new question
await browser({
action: "act",
kind: "type",
profile: "openclaw",
targetId: tabId,
ref: "e102", // Get from snapshot - adjust as needed
text: newQuestion
});
// Submit
await browser({
action: "act",
kind: "press",
profile: "openclaw",
targetId: tabId,
key: "Enter"
});
return true;
}
IMPORTANT: Never copy error examples! Always use the working functions provided below.
Common JavaScript syntax errors to avoid:
❌ document.querySelecto rAll('p') (space in function name)
✅ document.querySelectorAll('p') (correct)
❌ split('\\n') (double backslashes)
✅ split('\n') (single backslash)
❌ Missing return statement in evaluate functions
✅ Always include return statement
Here's a corrected and more robust version:
// Robust response extraction function
async function extractDeepSeekResponse(tabId) {
try {
const response = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `(function() {
// Find the most recent assistant response
const paragraphs = Array.from(document.querySelectorAll('p'));
// Filter out system messages and short texts
const assistantParagraphs = paragraphs.filter(p => {
const text = p.innerText || p.textContent || '';
const parent = p.parentElement;
const isSystemMessage =
text.includes('AI-generated') ||
text.includes('Upload docs') ||
text.includes('How can I help') ||
text.includes('⌘') ||
text.includes('We are asked:') ||
text.length < 20;
// Also check if it's in a response container (not in input area)
const isInInputArea = parent && (
parent.closest('textarea') ||
parent.closest('input') ||
parent.closest('[class*="input"]') ||
parent.closest('[class*="message-input"]')
);
return !isSystemMessage && !isInInputArea && text.trim().length > 0;
});
// Get the last assistant response
const lastResponse = assistantParagraphs[assistantParagraphs.length - 1];
if (!lastResponse) return 'No response found';
// Try to get the full response container
let responseContainer = lastResponse.closest('div[class*="message"], div[class*="response"], div[class*="assistant"]');
if (!responseContainer) {
responseContainer = lastResponse.closest('div');
}
if (responseContainer) {
// Get all text content from the response container
const allText = responseContainer.innerText || responseContainer.textContent || '';
// Clean up the text - use single backslashes in regex
return allText
.replace(/\s+/g, ' ')
.replace(/\n\s*\n/g, '\n\n')
.trim();
}
// Fallback to just the paragraph text
return lastResponse.innerText || lastResponse.textContent || '';
})()`,
returnByValue: true
});
return response;
} catch (error) {
console.error("Error extracting response:", error);
// Fallback: try simpler extraction
const fallbackResponse = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `(function() {
// Simple fallback: get all text on page and find the last substantial block
const allText = document.body.innerText || document.body.textContent || '';
const lines = allText.split('\n').filter(line =>
line.trim().length > 50 &&
!line.includes('AI-generated') &&
!line.includes('How can I help')
);
return lines.length > 0 ? lines[lines.length - 1].trim() : 'No response found';
})()`,
returnByValue: true
});
return fallbackResponse;
}
}
// Usage:
// const answer = await extractDeepSeekResponse(tabId);
IMPORTANT: Always copy from working examples, not error examples!
Common mistakes:
querySelectorAll not querySelecto rAllassistantParagraphs not assistantPar agraphsclosest('div') not closest('generic')returnByValue: true not returnByValue: TrueIf you're getting "No response found" or "failed to extract response", DeepSeek's page structure has likely changed. Use this more robust extraction method:
// ULTRA-ROBUST response extraction - tries multiple approaches
async function extractDeepSeekResponse(tabId) {
const response = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `(function() {
// Helper: check if text is a system message
function isSystemText(text) {
const systemPatterns = [
'AI-generated', 'Upload docs', 'How can I help', '⌘',
'We are asked:', 'deepseek', 'Sign in', '登录',
'Message DeepSeek', 'keyboard', 'shortcut'
];
return systemPatterns.some(p => text.includes(p));
}
// Approach 1: Try finding message containers
const messageDivs = document.querySelectorAll('div[class*="message"], div[class*="response"], div[class*="content"]');
let allTexts = [];
messageDivs.forEach(div => {
const text = div.innerText || div.textContent || '';
if (text.length > 30 && !isSystemText(text)) {
allTexts.push(text.trim());
}
});
// If we found messages in containers, return the last one
if (allTexts.length > 0) {
return allTexts[allTexts.length - 1];
}
// Approach 2: Try all <p> tags with relaxed filtering
const paragraphs = document.querySelectorAll('p');
const validParagraphs = [];
paragraphs.forEach(p => {
const text = p.innerText || p.textContent || '';
if (text.length > 20 && !isSystemText(text)) {
validParagraphs.push(text.trim());
}
});
if (validParagraphs.length > 0) {
return validParagraphs[validParagraphs.length - 1];
}
// Approach 3: Try <div> tags (newer DeepSeek structure)
const divs = document.querySelectorAll('div');
const validDivs = [];
divs.forEach(div => {
const text = div.innerText || div.textContent || '';
// Look for substantial text blocks in the main content area
if (text.length > 100 && !isSystemText(text) && !div.closest('header') && !div.closest('footer') && !div.closest('nav')) {
validDivs.push(text.trim());
}
});
if (validDivs.length > 0) {
return validDivs[validDivs.length - 1];
}
// Approach 4: Fallback to full page body text
const bodyText = document.body.innerText || document.body.textContent || '';
const lines = bodyText.split('\n').filter(line =>
line.trim().length > 50 && !isSystemText(line)
);
if (lines.length > 0) {
return lines[lines.length - 1].trim();
}
// Last resort: return debug info
return 'No response found - page may be loading or have unexpected structure';
})()`,
returnByValue: true
});
console.log("Extracted response:", response.substring(0, 200) + "...");
return response;
}
// Add debug logging to browser evaluate functions
const debugInfo = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `(function() {
// Debug: log what we find
const paragraphs = document.querySelectorAll('p');
console.log('Found', paragraphs.length, 'paragraphs');
for (let i = 0; i < Math.min(paragraphs.length, 5); i++) {
console.log('Paragraph', i, ':', paragraphs[i].innerText.substring(0, 100));
}
return JSON.stringify({
paragraphCount: paragraphs.length,
sampleTexts: Array.from(paragraphs).slice(0, 3).map(p => p.innerText.substring(0, 50))
});
})()`,
returnByValue: true
});
console.log("Debug info:", debugInfo);
Instead of writing your own extraction code, always use these pre-tested functions:
// SIMPLE & RELIABLE extraction function
async function extractDeepSeekResponseSimple(tabId) {
const response = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `(function() {
// Get all text from the page
const allText = document.body.innerText || document.body.textContent || '';
// Split into lines and filter out system messages
const lines = allText.split('\n').filter(line => {
const trimmed = line.trim();
return trimmed.length > 50 &&
!trimmed.includes('AI-generated') &&
!trimmed.includes('Message DeepSeek') &&
!trimmed.includes('How can I help') &&
!trimmed.includes('Upload docs');
});
// Return the last substantial line (most likely the response)
return lines.length > 0 ? lines[lines.length - 1].trim() : 'No response found';
})()`,
returnByValue: true
});
return response;
}
// Usage:
// const answer = await extractDeepSeekResponseSimple(tabId);
| Scenario | Recommended Approach | Why |
|---|---|---|
| Previous response was "Sorry, that's beyond my current scope" | Start Fresh Chat | DeepSeek might be in a restricted mode; fresh chat resets context |
| Want to ask unrelated follow-up | Start Fresh Chat | Avoids confusing the AI with previous context |
| Continuing same topic with refined question | Continue in Current Chat | Maintains context for better responses |
| Previous response was good but need clarification | Continue in Current Chat | AI can reference previous answer |
Never rely on fixed ref IDs like e306, e102, etc. These IDs are:
Always use semantic selectors or get fresh refs from snapshot:
The DeepThink button (labeled "DeepThink" to the left of the "Search" button) enables enhanced reasoning mode. When activated, DeepSeek provides more detailed, step-by-step responses.
// Function to toggle DeepThink mode
async function toggleDeepThinkMode(tabId, enable = true) {
await browser({
action: "snapshot",
profile: "openclaw",
targetId: tabId
});
// Check current DeepThink state
const currentState = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `(function() {
const deepThinkBtn = document.querySelector('button[aria-label*="DeepThink"], button:has(> div:contains("DeepThink"))');
const searchBtn = document.querySelector('button[aria-label*="Search"], button:has(> div:contains("Search"))');
return JSON.stringify({
hasDeepThink: !!deepThinkBtn,
hasSearch: !!searchBtn,
deepThinkActive: deepThinkBtn ? deepThinkBtn.getAttribute('aria-pressed') === 'true' ||
deepThinkBtn.classList.contains('active') ||
deepThinkBtn.getAttribute('data-active') === 'true' : false,
deepThinkText: deepThinkBtn ? deepThinkBtn.innerText || deepThinkBtn.textContent : '',
searchText: searchBtn ? searchBtn.innerText || searchBtn.textContent : ''
});
})()`,
returnByValue: true
});
const state = JSON.parse(currentState);
console.log("DeepThink state:", state);
// If we want DeepThink enabled but it's not active
if (enable && state.hasDeepThink && !state.deepThinkActive) {
console.log("Enabling DeepThink mode...");
// Click the DeepThink button
const clicked = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `(function() {
// Find DeepThink button by text content
const buttons = Array.from(document.querySelectorAll('button'));
const deepThinkBtn = buttons.find(btn =>
btn.innerText.includes('DeepThink') ||
btn.textContent.includes('DeepThink') ||
(btn.querySelector('div') && btn.querySelector('div').innerText.includes('DeepThink'))
);
if (deepThinkBtn) {
deepThinkBtn.click();
return true;
}
// Alternative: Find button near search button
const searchBtn = buttons.find(btn =>
btn.innerText.includes('Search') ||
btn.textContent.includes('Search')
);
if (searchBtn && searchBtn.previousElementSibling) {
searchBtn.previousElementSibling.click();
return true;
}
return false;
})()`,
returnByValue: true
});
if (clicked) {
console.log("DeepThink mode enabled");
await new Promise(r => setTimeout(r, 1000)); // Wait for UI update
return true;
}
}
// If we want DeepThink disabled but it's active
if (!enable && state.hasDeepThink && state.deepThinkActive) {
console.log("Disabling DeepThink mode...");
// Click the DeepThink button again to toggle off
const clicked = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `(function() {
const buttons = Array.from(document.querySelectorAll('button'));
const deepThinkBtn = buttons.find(btn =>
btn.innerText.includes('DeepThink') ||
btn.textContent.includes('DeepThink')
);
if (deepThinkBtn) {
deepThinkBtn.click();
return true;
}
return false;
})()`,
returnByValue: true
});
if (clicked) {
console.log("DeepThink mode disabled");
await new Promise(r => setTimeout(r, 1000));
return true;
}
}
return false;
}
// Ask a question with DeepThink mode enabled
async function askWithDeepThink(tabId, question) {
console.log("Asking question with DeepThink mode...");
// Enable DeepThink mode
await toggleDeepThinkMode(tabId, true);
// Wait for UI to update
await new Promise(r => setTimeout(r, 1000));
// Take fresh snapshot
await browser({
action: "snapshot",
profile: "openclaw",
targetId: tabId
});
// Find and focus input
const inputFound = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `(function() {
const input = document.querySelector('textarea[placeholder*="Message DeepSeek"], input[placeholder*="Message DeepSeek"]');
if (input) {
input.focus();
input.value = '';
return true;
}
return false;
})()`,
returnByValue: true
});
if (!inputFound) {
throw new Error("Chat input not found");
}
// Type the question
await browser({
action: "act",
kind: "type",
profile: "openclaw",
targetId: tabId,
text: question
});
// Submit
await browser({
action: "act",
kind: "press",
profile: "openclaw",
targetId: tabId,
key: "Enter"
});
// Wait longer for DeepThink response (it thinks before answering)
console.log("Waiting for DeepThink response (may take 10-15 seconds)...");
await new Promise(r => setTimeout(r, 15000));
// Extract response
const response = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `(function() {
// Get all paragraph elements
const paras = Array.from(document.querySelectorAll('p'));
// Filter to find assistant responses (including DeepThink reasoning)
const assistantResponses = paras.filter(p => {
const text = p.innerText || '';
return text.length > 20 &&
!text.includes('AI-generated') &&
!text.includes('Upload docs') &&
!text.includes('How can I help') &&
!text.includes('⌘');
});
// Get the most recent (last) response
return assistantResponses.length > 0
? assistantResponses[assistantResponses.length - 1].innerText.trim()
: 'No response found';
})()`,
returnByValue: true
});
console.log("DeepThink response received");
return response;
}
// Example usage:
// const deepThinkResponse = await askWithDeepThink(tabId, "Solve: 2x + 5 = 15");
| Use Case | Recommended Mode | Why |
|---|---|---|
| Math problems, logic puzzles | DeepThink ON | Provides step-by-step reasoning |
| Code debugging, algorithm design | DeepThink ON | Shows thought process and analysis |
| Simple factual questions | DeepThink OFF | Faster response, no need for reasoning |
| Creative writing, brainstorming | DeepThink ON | More thoughtful, structured responses |
| Quick information lookup | DeepThink OFF | Faster, more concise answers |
The DeepThink button can be identified in several ways:
aria-label containing "DeepThink"[active] attribute or aria-pressed="true"// Multiple strategies for finding DeepThink button
function findDeepThinkButton() {
// Strategy 1: By text content
const byText = document.querySelector('button:has(> div:contains("DeepThink"))');
if (byText) return byText;
// Strategy 2: By aria-label
const byAria = document.querySelector('button[aria-label*="DeepThink"]');
if (byAria) return byAria;
// Strategy 3: Relative to Search button
const searchBtn = document.querySelector('button:has(> div:contains("Search"))');
if (searchBtn && searchBtn.previousElementSibling) {
return searchBtn.previousElementSibling;
}
// Strategy 4: By class names (may change)
const byClass = document.querySelector('button[class*="deepthink"], button[class*="DeepThink"]');
if (byClass) return byClass;
return null;
}
// Toggle between DeepThink and regular mode
async function toggleResponseMode(tabId) {
const currentState = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `(function() {
const btn = document.querySelector('button[aria-label*="DeepThink"], button:has(> div:contains("DeepThink"))');
return btn ? (btn.getAttribute('aria-pressed') === 'true' || btn.classList.contains('active')) : false;
})()`,
returnByValue: true
});
const isActive = currentState === 'true';
console.log(`DeepThink is currently ${isActive ? 'active' : 'inactive'}`);
// Toggle the button
await toggleDeepThinkMode(tabId, !isActive);
return !isActive;
}
// Usage: const newState = await toggleResponseMode(tabId);
// Returns true if DeepThink is now active, false if regular mode
// ❌ WRONG - Hardcoded ref IDs
await browser({ action: "act", kind: "click", ref: "e306" });
// ✅ CORRECT - Semantic selection
await browser({ action: "act", kind: "evaluate", fn: `(function() {
const newChatLink = document.querySelector('a[href*="/a/chat"]');
if (newChatLink && newChatLink.innerText.includes('New chat')) {
newChatLink.click();
return true;
}
return false;
})()` });
// ✅ CORRECT - Get ref from snapshot
// First take snapshot, then use ref from the snapshot response
const snapshot = await browser({ action: "snapshot", targetId: tabId });
// Parse snapshot to find "New chat" element ref
const newChatRef = findRefByText(snapshot, "New chat");
await browser({ action: "act", kind: "click", ref: newChatRef });
The user's original attempt (<tool_call><function=browser><action>act</action><profile>openclaw</profile><targetId>00AA2439B0832C440F6EDAF46A34029F</targetId><kind>click</kind><ref>e306</ref></function></tool_call>) failed because:
Always use semantic content-based selection instead of ref IDs.
DeepSeek's chat input is built with React and validates that keyboard events come from real user interactions. Using JavaScript's dispatchEvent() to simulate key presses will silently fail:
// ❌ WRONG - This doesn't work!
const input = document.querySelector('textarea[placeholder*="Message DeepSeek"]');
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
input.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }));
// ✅ CORRECT - Use browser press action
await browser({
action: "act",
kind: "press",
profile: "openclaw",
targetId: tabId,
key: "Enter"
});
The press action sends a real key event through the browser automation layer, which passes DeepSeek's event validation.
Based on analysis of common failures and successful implementations, follow these best practices:
✅ DO: Use the Simple Extraction Function
// ALWAYS use this simple function - it's tested and reliable
const response = await getDeepSeekResponseSimple(tabId);
// Only use complex functions if simple one fails
if (response.includes('No response found') || response.includes('Error:')) {
// Try robust version as fallback
const response = await extractDeepSeekResponse(tabId);
}
✅ DO: Add Error Handling
try {
const response = await extractDeepSeekResponse(tabId);
console.log("Success:", response);
} catch (error) {
console.error("Extraction failed:", error);
// Implement fallback strategy
}
✅ DO: Increase Wait Times for Complex Responses
// Simple questions: 10 seconds
await new Promise(r => setTimeout(r, 10000));
// Complex questions or DeepThink mode: 15-20 seconds
await new Promise(r => setTimeout(r, 15000));
✅ DO: Use Semantic Selectors
// Find elements by content, not hardcoded refs
const newChatLink = document.querySelector('a[href*="/a/chat"]');
if (newChatLink && newChatLink.innerText.includes('New chat')) {
newChatLink.click();
}
✅ DO: Get Fresh Refs from Snapshots
// Take snapshot to get current element references
const snapshot = await browser({ action: "snapshot", targetId: tabId });
// Parse snapshot to find elements dynamically
❌ DON'T: Use Hardcoded Ref IDs
// ❌ WRONG - These change dynamically!
await browser({ action: "act", kind: "click", ref: "e306" });
await browser({ action: "act", kind: "click", ref: "e102" });
✅ DO: Use Correct Regex Patterns
// ❌ WRONG - Double backslashes
.replace(/\\s+/g, ' ')
.split('\\n')
// ✅ CORRECT - Single backslashes
.replace(/\s+/g, ' ')
.split('\n')
✅ DO: Avoid Optional Chaining in Older Environments
// ❌ WRONG - Might not be supported
msgs[msgs.length - 1]?.innerText || 'No response';
// ✅ CORRECT - Traditional check
if (msgs.length > 0) {
return msgs[msgs.length - 1].innerText;
}
return 'No response';
✅ DO: Use Function Wrappers
// ❌ WRONG - Direct code
fn: `{ const paras = document.querySelectorAll('p'); ... }`
// ✅ CORRECT - Function wrapper
fn: `(function() {
const paras = document.querySelectorAll('p');
return ...;
})()`
✅ DO: Add Debug Logging
const debugInfo = await browser({
action: "act",
kind: "evaluate",
profile: "openclaw",
targetId: tabId,
fn: `(function() {
const paragraphs = document.querySelectorAll('p');
console.log('Found', paragraphs.length, 'paragraphs');
// Log first few paragraphs for debugging
for (let i = 0; i < Math.min(paragraphs.length, 5); i++) {
console.log('Paragraph', i, ':', paragraphs[i].innerText.substring(0, 100));
}
return JSON.stringify({
paragraphCount: paragraphs.length,
sampleTexts: Array.from(paragraphs).slice(0, 3).map(p => p.innerText.substring(0, 50))
});
})()`,
returnByValue: true
});
console.log("Debug info:", debugInfo);
✅ DO: Test Extraction Before Reliance
// Test the extraction function with a simple question first
const testQuestion = "What is 2+2?";
await askQuestion(tabId, testQuestion);
const testResponse = await extractDeepSeekResponse(tabId);
console.log("Test response:", testResponse);
// If test works, proceed with actual question
if (testResponse && testResponse.includes('4')) {
console.log("✅ Extraction test passed");
// Proceed with actual question
}
| Failure Pattern | Solution |
|---|---|
| "No response found" | Increase wait time, use robust extraction, check page structure |
| JavaScript syntax errors | Fix regex patterns, avoid optional chaining, use function wrappers |
| Element not found | Use semantic selectors, take fresh snapshot, add fallbacks |
| Login timeout | Increase timeout, implement QR refresh, better error messaging |
| Event validation fails | Use kind: "press" not dispatchEvent() |
<tool_call> | DO NOT send raw html tags content to user session. Just retry to extract or tell user to go on asking. |
// Most reliable implementation pattern
async function askDeepSeekReliably(tabId, question) {
try {
// 1. Ensure fresh chat session
await startFreshChat(tabId);
// 2. Type question with proper event handling
await browser({
action: "act",
kind: "type",
profile: "openclaw",
targetId: tabId,
text: question
});
// 3. Submit with press action (not dispatchEvent)
await browser({
action: "act",
kind: "press",
profile: "openclaw",
targetId: tabId,
key: "Enter"
});
// 4. Wait appropriately
await new Promise(r => setTimeout(r, 15000));
// 5. Extract with robust function
const response = await extractDeepSeekResponse(tabId);
// 6. Handle potential failures
if (response.includes('No response found') || response.length < 10) {
// Try fallback extraction
return await fallbackExtraction(tabId);
}
return response;
} catch (error) {
console.error("DeepSeek query failed:", error);
return `Error: ${error.message}`;
}
}
| Use Case | Recommended Function | Wait Time | Notes |
|---|---|---|---|
| Simple factual questions | extractDeepSeekResponse | 10s | Fast, reliable for most cases |
| Complex reasoning questions | ultraRobustExtractDeepSeekResponse | 15-20s | Handles complex page structures |
| DeepThink mode responses | extractDeepSeekResponse with DeepThink enabled | 20s | Longer thinking time needed |
| Critical queries | Try multiple extraction methods | 20s+ | Combine results for reliability |
imsg skill - Send results to iMessageopenclaw channels status skill - Get enabled channelsopenclaw browser status skill - Get browser statusbrowser tool - OpenClaw browser control