🎁 Get the FREE AI Skills Starter Guide β€” Subscribe β†’
BytesAgainBytesAgain
πŸ¦€ ClawHub

Chat DeepSeek by Browser

by @qidu

openclaw browser open https://chat.deepseek.com and login it, then input questions from user's messages, extract response of deepseek chat to reply user.

Versionv1.0.4
Downloads1,038
TERMINAL
clawhub install freechat-deepseek

πŸ“– About This Skill


name: freechat-deepseek alias: - chat-deepseek - deepseek-it - ask-deepseek - open-deepseek - browse-deepseek - deepseek-search - deepseeking

description: openclaw browser open https://chat.deepseek.com and login it, then input questions from user's messages, extract response of deepseek chat to reply user. allowed-tools: Bash(npm:*) Bash(npx:*) Bash(openclaw:*) Bash(curl:*) Read Write WebFetch Browser


DeepSeek Chat Automation Skill

> ⚠️ 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, use profile: "user" for direct Chrome attachment. This avoids the need for the Chrome relay extension and provides more stable browser automation.

Dynamic Profile Selection (Recommended)

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/" });

Quick Start (OpenClaw >= v3.13)

// 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...

Direct Chrome Attachment (OpenClaw >= v3.13)

For OpenClaw version >= v3.13, use direct Chrome attachment:

Step 1: Check Browser Status

// Check user browser status
const browserStatus = await browser({
  action: "status",
  profile: "user"
});
console.log("Browser status:", browserStatus);

Step 2: Open Chrome Directly

// 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);

Step 3: Navigate to DeepSeek

// Navigate to DeepSeek
await browser({
  action: "navigate",
  profile: "user",
  targetId: tabId,
  url: "https://chat.deepseek.com/"
});

Step 4: List/Manage Tabs

// 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 });

Key Differences: user vs openclaw profile

| 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 |

Troubleshooting

If profile: "user" times out: 1. Restart the OpenClaw gateway: openclaw gateway restart 2. Try again with browser action


Prerequisites for Direct Chrome Attachment

To use profile: "user" for direct Chrome attachment, users must configure Chrome properly:

1. Chrome Version Requirement

  • Install Chrome version >= 144
  • 2. Enable Remote Debugging

    Step-by-step:

    1. Open Chrome settings page:

       chrome://inspect/#remote-debugging
       

    2. Enable the option: - Check/Enable: "Allow remote debugging for this browser instance" !Chrome remote debugging settings

    3. Restart Chrome if the option was just enabled

    3. Verify Setup

    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."); }

    Common Issues

    | 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 |


    Option 2: Using Chrome Relay Extension (Legacy)

    For users who prefer using the Chrome relay extension instead of direct Chrome attachment:

    Install the Extension

    1. Open Chrome Web Store:

       https://chromewebstore.google.com/detail/openclaw-browser-relay/nglingapjinhecnfejdcpihlpneeadjp
       

    2. Click "Add to Chrome" to install the extension

    3. Approve permissions when prompted

    Using the Relay

    Once installed, activate it before running browser automation:

    1. Open the Chrome tab you want to control 2. Click the OpenClaw Browser Relay toolbar icon (usually in the top-right of Chrome) 3. Wait for the badge to turn ON / green

    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 });

    Relay Status Indicators

    | 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! |

    Which Should I 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 |


    Workflow Overview (6 Steps)

    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 Chat Page 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 (Updated for OpenClaw >= v3.13)

    For OpenClaw >= v3.13:

  • Browser tool with profile: "user" (direct Chrome attachment)
  • No Chrome extension needed!
  • No API keys or LLM credits needed
  • For older versions (legacy):

  • Check browser extension with openclaw browser status, get enabled: true
  • Browser tool with profile: "openclaw"
  • 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' + screenshotPath + '"' }); 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: 1. iMessage (imsg) - Most reliable on Mac (uses userPhone variable) 2. WhatsApp - If WhatsApp Web is active (uses userPhone variable) 3. QQBot - If QQ bot is configured (no phone number needed for QQBot) 4. 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 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 |

    tags | | Filter out | AI-generated, Upload docs, Help text, Keyboard shortcuts | | Get | Last matching

    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

    in document.body (filtered) |


    Configuration

    Phone Number Configuration

    The user's phone number can be configured in several ways:

    1. Environment Variable: Set DEEPSEEK_PHONE environment variable

       export DEEPSEEK_PHONE="+1234567890000"
       

    2. USER.md: Add phone number to USER.md file

       # USER.md
       Phone: +1234567890000
       

    3. Configuration 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/.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 return statement in evaluate functions
  • βœ… Always include return statement
  • 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: querySelectorAll not querySelecto rAll
  • Spaces in variable names: assistantParagraphs not assistantPar agraphs
  • Incorrect selectors: Use closest('div') not closest('generic')
  • Case sensitivity: returnByValue: true not returnByValue: 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

    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

    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:

    1. Robust "New Chat" Detection: Uses semantic search for "New chat" text in sidebar links 2. Fallback Mechanisms: Keyboard shortcut (Cmd+J) if sidebar link not found 3. State Verification: Checks if we're already on a fresh chat page 4. Better Response Extraction: Filters out system messages and thought processes 5. Error Handling: Graceful fallbacks for missing elements 6. 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:

    1. ❌ WRONG: Clicking ref=e306 (was a share button, not new chat) 2. βœ… CORRECT: Clicking "New chat" link in sidebar or using Cmd+J 3. ❌ WRONG: Assuming input ref is always e102 4. βœ… CORRECT: Using snapshot to get fresh refs each time 5. ❌ WRONG: Extracting all paragraphs including system messages 6. βœ… 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('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;
    }
    

    Complete Workflow with DeepThink Mode

    // 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");

    When to Use DeepThink Mode

    | 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 |

    DeepThink Button Identification Strategies

    The DeepThink button can be identified in several ways:

    1. By text content: Button containing "DeepThink" 2. By position: Button immediately left of "Search" button 3. By aria-label: Button with aria-label containing "DeepThink" 4. By active state: When enabled, has [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 Modes

    // 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 (actopenclaw00AA2439B0832C440F6EDAF46A34029Fclicke306) failed because:

  • ref=e306 was not a "New chat" button (it was likely a share/export button)
  • The correct "New chat" element was ref=e292 in sidebar or ref=e35 after reload
  • ...but these refs are not reliable either!

    Always use semantic content-based selection instead of ref IDs.

    Why dispatchEvent Doesn't Work

    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.


    🎯 Best Practices & Recommendations

    Based on analysis of common failures and successful implementations, follow these best practices:

    1. Response Extraction 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));

    2. Element Selection Best Practices

    βœ… 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" });
    

    3. JavaScript Code Quality

    βœ… 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 ...; })()

    4. Debugging & Testing

    βœ… 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 }

    5. Common Failure Patterns to Avoid

    | 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() | | | DO NOT send raw html tags content to user session. Just retry to extract or tell user to go on asking. |

    6. Quick Reference: Working Implementation

    // 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};
      }
    }
    

    7. When to Use Which Approach

    | 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 |


    See Also

  • imsg skill - Send results to iMessage
  • openclaw channels status skill - Get enabled channels
  • openclaw browser status skill - Get browser status
  • browser tool - OpenClaw browser control
  • ⚑ When to Use

    TriggerAction
    - 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 Chat` Page 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)

    βš™οΈ Configuration

    Phone Number Configuration

    The user's phone number can be configured in several ways:

    1. Environment Variable: Set DEEPSEEK_PHONE environment variable

       export DEEPSEEK_PHONE="+1234567890000"
       

    2. USER.md: Add phone number to USER.md file

       # USER.md
       Phone: +1234567890000
       

    3. Configuration 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/.jpg. Always use the path returned by the browser tool.


    πŸ“‹ Tips & Best Practices

    If profile: "user" times out: 1. Restart the OpenClaw gateway: openclaw gateway restart 2. Try again with browser action