DeepSeek Chat Automation Skill
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.
When to Use
✅ USE this skill when:
- User wants to ask or query on
DeepSeek Chat(https://chat.deepseek.com) - Sending user questions from messages to
DeepSeek Chat - Extracting AI responses as raw text (no LLM processing)
- Browser automation workflows
- Extract answers from
DeepSeek ChatPage with direct output
❌ DON'T use this skill when:
- Need real-time SSE stream capture (use curl/WebSocket directly)
- Page uses heavy anti-bot protection
- Requires complex CAPTCHA solving
- Need LLM-powered text processing (use Claude API directly)
Requirements
- Check browser extension with
openclaw browser status, getenabled: true - Browser tool with
profile: "openclaw" - No API keys or LLM credits needed
Workflow Overview (6 Steps)
┌─────────────────────────────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────────────────────────────────────┘
🎯 SIMPLE & RELIABLE RESPONSE EXTRACTION
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
Step 1: Open or Reuse Tab
// 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));
Step 2: Check Login Status (QR Code Needed?)
// 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)
}
Login Status Indicators
| 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 |
Step 3: Send QR Code to Enabled Channels
// 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);
}
QR Code Sending Priority
Send QR Code to session which is executing this skill firstly. And also send by other avlabile channels:
- iMessage (
imsg) - Most reliable on Mac (usesuserPhonevariable) - WhatsApp - If WhatsApp Web is active (uses
userPhonevariable) - QQBot - If QQ bot is configured (no phone number needed for QQBot)
- Signal - If Signal CLI is available (would need phone number configuration)
Step 4: Wait for Login (with Timeout & Retry Loop)
// 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");
}
Login Timeout Strategy
| 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 |
Step 5: Input Question and Press Enter
// 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");
Step 6: Extract Response from DOM
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);
Response Extraction Strategy
| 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 |
Complete Example: Full Workflow
// ============ 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
Common Selectors
| 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) |
Configuration
Phone Number Configuration
The user's phone number can be configured in several ways:
Environment Variable: Set
DEEPSEEK_PHONEenvironment variableexport DEEPSEEK_PHONE="+1234567890000"USER.md: Add phone number to USER.md file
# USER.md Phone: +1234567890000Configuration File: Add to OpenClaw configuration
// In your implementation code const userPhone = config.userPhone || process.env.DEEPSEEK_PHONE || "+1234567890000";
QR Code Path Handling
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.
Limitations
- Rate Limiting: DeepSeek may block requests (429 errors)
- Event Validation: Some buttons need real user events (use
kind: "press"not JS dispatchEvent) - Login Verification: QR Code scan requires user interaction
- No API Access: Direct API requires bypassing WAF
- No Files Uploading: DO NOT upload files to chat.deepseek.com
- No LLM Processing: Extract raw text only
- Login Timeout: Maximum 2 minutes for QR scan
Troubleshooting
| 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 |
🆕 Optimized: "Try Again with Fresh Question" Workflow
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:
Option 1: Start Fresh Chat (Recommended for unsatisfactory responses)
// 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;
}
Option 2: Continue in Same Chat (if context is needed)
// 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;
}
🐛 Common Errors and Fixes
Error: SyntaxError in JavaScript Evaluation
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
returnstatement in evaluate functions✅ Always include
returnstatement
Robust Response Extraction Function
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);
Common JavaScript Errors to Avoid
IMPORTANT: Always copy from working examples, not error examples!
Common mistakes:
- Typos in function names:
querySelectorAllnotquerySelecto rAll - Spaces in variable names:
assistantParagraphsnotassistantPar agraphs - Incorrect selectors: Use
closest('div')notclosest('generic') - Case sensitivity:
returnByValue: truenotreturnByValue: True - Missing semicolons: Always end statements properly
Fix: "No response found" Error
If 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;
}
Debugging Tips
// 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);
🛠️ Working Implementation Guide
Always Use These Pre-tested Functions
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);
Key Improvements in Optimized Workflow:
- Robust "New Chat" Detection: Uses semantic search for "New chat" text in sidebar links
- Fallback Mechanisms: Keyboard shortcut (Cmd+J) if sidebar link not found
- State Verification: Checks if we're already on a fresh chat page
- Better Response Extraction: Filters out system messages and thought processes
- Error Handling: Graceful fallbacks for missing elements
- Clear Documentation: Explains when to use fresh chat vs continue in same chat
When to Use Which Approach:
| 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 |
Common Pitfalls Fixed:
- ❌ WRONG: Clicking ref=e306 (was a share button, not new chat)
- ✅ CORRECT: Clicking "New chat" link in sidebar or using Cmd+J
- ❌ WRONG: Assuming input ref is always e102
- ✅ CORRECT: Using snapshot to get fresh refs each time
- ❌ WRONG: Extracting all paragraphs including system messages
- ✅ CORRECT: Filtering out AI-generated disclaimers and thought processes
⚠️ Important Note About Element References:
Never rely on fixed ref IDs like e306, e102, etc. These IDs are:
- Generated dynamically by the browser snapshot tool
- Can change between snapshots or page reloads
- Different for each user and session
Always use semantic selectors or get fresh refs from snapshot:
🧠 DeepThink Mode Optimization
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.
How to Enable DeepThink Mode
// 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(
…(truncated)