/**
 * Tool definitions for the WebBrain agent.
 * These are sent to the LLM in OpenAI function-calling format.
 */

export const AGENT_TOOLS = [
  {
    type: 'function',
    function: {
      name: 'get_accessibility_tree',
      description: 'PREFERRED page-reading tool. Returns the page as a flat, indented text representation of its accessibility tree. Each kept node is one line of the form `role "accessible name" [ref_id] href="..." type="..." placeholder="..."`. Indentation shows hierarchy. ref_ids are STABLE across calls — re-use them in click_ax / type_ax / set_field. If the result is truncated (`truncated:true`, `hasMore:true`), call again with `page:` set to `nextPage` to read the next slice before scrolling. When you pass an explicit `maxChars` and the tree is larger, the tool now AUTO-SLICES to fit and sets `autoDegraded:true` + a `notice` field explaining how to continue — so a single oversized call no longer wastes a round-trip with empty pageContent.',
      parameters: {
        type: 'object',
        properties: {
          filter: { type: 'string', enum: ['all', 'visible', 'interactive'], description: 'Which nodes to include. "visible" (in-viewport, visible) is a good default. "interactive" shows only clickable/typeable things. "all" traverses the entire DOM.' },
          maxDepth: { type: 'number', description: 'Max tree depth to descend (default 15).' },
          maxChars: { type: 'number', description: 'Abort and return an error if the rendered tree exceeds this many characters.' },
          ref_id: { type: 'string', description: 'Optional. Anchor at a previously-seen ref_id instead of document.body.' },
          page: { type: 'number', description: 'Optional 1-based chunk number for visible/interactive trees. If a visible tree returns truncated:true/hasMore:true, call again with page: nextPage to read the next chunk of the same ordered tree before trying to scroll.' },
        },
        required: [],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'click_ax',
      description: 'Click an element by its ref_id from get_accessibility_tree. Scrolls into view, focuses, then clicks. ref_ids are stable across calls.',
      parameters: {
        type: 'object',
        properties: {
          ref_id: { type: 'string', description: 'A ref_id from get_accessibility_tree, e.g. "ref_42".' },
        },
        required: ['ref_id'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'type_ax',
      description: 'Type text into an element by its ref_id from get_accessibility_tree. Handles <input>, <textarea>, and contenteditable. Uses React-compatible native value setters.',
      parameters: {
        type: 'object',
        properties: {
          ref_id: { type: 'string', description: 'A ref_id from get_accessibility_tree, e.g. "ref_42".' },
          text: { type: 'string', description: 'Text to type.' },
          clear: { type: 'boolean', description: 'Clear existing content before typing (default: false).' },
        },
        required: ['ref_id', 'text'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'set_field',
      description: 'Atomically focus + (optionally clear) + type text into a form field by ref_id. ONE-SHOT equivalent of click_ax then type_ax. Set submit:true to press Enter afterward (combobox-aware: dispatches ArrowDown then Enter when a listbox is open).',
      parameters: {
        type: 'object',
        properties: {
          ref_id: { type: 'string', description: 'A ref_id from get_accessibility_tree, e.g. "ref_42".' },
          text: { type: 'string', description: 'Text to type into the field.' },
          clear: { type: 'boolean', description: 'Clear existing content before typing (default: true).' },
          submit: { type: 'boolean', description: 'Press Enter after typing (default: false).' },
        },
        required: ['ref_id', 'text'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'read_page',
      description: 'Read the current page content including title, URL, text content, links, and forms. RESULT SHAPE: `text` is the article body (nav/header/footer/aside/ads stripped by default); `textSource` is the CSS selector that produced the body (or "body (chrome-stripped)" / "body (raw)" when no article container matched); `isArticlePage` is true when the page self-declares as an article via og:type, article:published_time, schema.org Article, or `<article>`. When `isArticlePage:true` AND `textSource` is a real article selector, you HAVE the complete article body — do not chase more content with fetch_url / scroll / get_accessibility_tree. NOTE: if the current tab is a PDF (URL ends in .pdf or content-type is application/pdf), this call auto-redirects to read_pdf since Firefox\'s built-in PDF viewer is a privileged page that we cannot scrape via DOM.',
      parameters: {
        type: 'object',
        properties: {
          includeChrome: {
            type: 'boolean',
            description: 'Include nav / header / footer / aside / ad-slot text in the body. Default false — when the user asks about article/README content you almost never want this. Set true only when the user is asking ABOUT the navigation menu, footer links, cookie banner, advertisement, etc.',
          },
        },
        required: [],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'read_pdf',
      description: 'Extract text from a PDF document. Use this when the current tab URL ends in .pdf or content-type is application/pdf — clicks, scrolls, screenshots, get_accessibility_tree all silently no-op against the browser\'s built-in PDF viewer because it is a privileged page our content scripts cannot inject into. read_pdf fetches the PDF binary and parses it directly with pdfjs-dist, returning per-page text plus a `hasExtractableText` flag. Default reads pages 1–50; for longer PDFs paginate with fromPage/toPage. If `hasExtractableText` is false, the PDF is a scanned image and text extraction returned empty — only a vision-capable model can read it.',
      parameters: {
        type: 'object',
        properties: {
          url: {
            type: 'string',
            description: 'Absolute http(s) or file:// URL of the PDF. Defaults to the current tab URL if omitted — usually what you want.',
          },
          fromPage: {
            type: 'number',
            description: '1-indexed first page to read. Default 1.',
          },
          toPage: {
            type: 'number',
            description: '1-indexed last page to read (inclusive). Default fromPage+49 capped at totalPages.',
          },
          maxChars: {
            type: 'number',
            description: 'Hard cap on returned text length. Default 50000.',
          },
        },
        required: [],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'screenshot',
      description: 'Capture a screenshot of the visible area of the current tab. Returns a base64-encoded PNG image. Useful when you need to visually inspect the page, verify the result of an action, or when DOM text extraction is insufficient. The result\'s `page` field reports `documentTextChars` (total visible text on the page) and `visibleTextChars` (text in the current viewport). If the screenshot LOOKS blank but `documentTextChars` is in the thousands, the page is not empty — your image is stale mid-lazy-load (ads, hero images, fonts still arriving). Wait or call read_page / get_accessibility_tree instead of declaring the page empty.',
      parameters: {
        type: 'object',
        properties: {},
        required: [],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'get_interactive_elements',
      description: 'Get all interactive elements on the page (buttons, links, inputs, etc.) with their positions and attributes. Returns an indexed list you can reference by index.',
      parameters: {
        type: 'object',
        properties: {},
        required: [],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'click',
      description: 'Click an element. FOUR ways to use it: (1) visible text, (2) element index from get_interactive_elements, (3) CSS selector, (4) x/y coordinates. For text clicks, default matching is EXACT and case-insensitive. You can opt into broader matching with `textMatch: "prefix"` or `textMatch: "contains"`. jQuery/Playwright pseudo-classes like `:contains()` and `:has-text()` are NOT valid CSS — use the text parameter instead.',
      parameters: {
        type: 'object',
        properties: {
          text: { type: 'string', description: 'Visible text to match against clickable elements.' },
          textMatch: { type: 'string', enum: ['exact', 'prefix', 'contains'], description: 'Text matching mode for `text`. Default is `exact` (safest).' },
          selector: { type: 'string', description: 'CSS selector for the element to click.' },
          index: { type: 'number', description: 'Index from get_interactive_elements result.' },
          x: { type: 'number', description: 'X coordinate to click.' },
          y: { type: 'number', description: 'Y coordinate to click.' },
        },
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'type_text',
      description: 'Type text into an input field. TWO WAYS to use it: (1) CSS selector, or (2) ONLY text (no selector) to type into the currently focused element — use this RIGHT AFTER clicking a field. The second form is most reliable for forms with weird selectors (GitHub release[name], Stripe nested inputs). DO NOT pass `index` — type_text does not support indices. To type into an indexed field, call click({index: N}) first, then type_text({text: "..."}).',
      parameters: {
        type: 'object',
        properties: {
          selector: { type: 'string', description: 'OPTIONAL CSS selector. Omit to type into the currently focused element.' },
          text: { type: 'string', description: 'Text to type.' },
          clear: { type: 'boolean', description: 'Clear existing content before typing (default: false).' },
        },
        required: ['text'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'press_keys',
      description: 'Press keyboard keys. V1 supports Escape, Tab, and Enter. Useful for dismissing modals/dropdowns (Escape), moving focus (Tab), and confirming dialogs/forms (Enter).',
      parameters: {
        type: 'object',
        properties: {
          key: { type: 'string', enum: ['Escape', 'Tab', 'Enter'], description: 'Key to press.' },
          repeat: { type: 'number', description: 'How many times to press the key (default: 1, max: 3).' },
        },
        required: ['key'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'scroll',
      description: 'Scroll the page in a given direction.',
      parameters: {
        type: 'object',
        properties: {
          direction: {
            type: 'string',
            enum: ['up', 'down', 'top', 'bottom'],
            description: 'Scroll direction',
          },
          amount: { type: 'number', description: 'Pixels to scroll (default: 500)' },
        },
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'navigate',
      description: 'Navigate the current tab to a URL.',
      parameters: {
        type: 'object',
        properties: {
          url: { type: 'string', description: 'URL to navigate to' },
        },
        required: ['url'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'extract_data',
      description: 'Extract structured data from the page (tables, headings, or images).',
      parameters: {
        type: 'object',
        properties: {
          type: {
            type: 'string',
            enum: ['tables', 'headings', 'images'],
            description: 'Type of data to extract',
          },
        },
        required: ['type'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'wait_for_element',
      description: 'Wait for an element matching a CSS selector to appear on the page.',
      parameters: {
        type: 'object',
        properties: {
          selector: { type: 'string', description: 'CSS selector to wait for' },
          timeout: { type: 'number', description: 'Max wait time in ms (default: 5000)' },
        },
        required: ['selector'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'get_selection',
      description: 'Get the currently selected/highlighted text on the page.',
      parameters: {
        type: 'object',
        properties: {},
        required: [],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'execute_js',
      description: 'Execute custom JavaScript code on the page and return the result. Use for complex operations not covered by other tools.',
      parameters: {
        type: 'object',
        properties: {
          code: { type: 'string', description: 'JavaScript code to execute' },
        },
        required: ['code'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'new_tab',
      description: 'Open a new browser tab with the given URL.',
      parameters: {
        type: 'object',
        properties: {
          url: { type: 'string', description: 'URL to open' },
        },
        required: ['url'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'done',
      description: 'Signal that the task is FULLY complete. Only call this when you have successfully accomplished the user\'s request OR have exhausted every reasonable alternative (at least 3-4 different approaches). Provide a summary of what was accomplished. Do NOT call this prematurely — keep trying different strategies if the current one fails. Credentials hygiene: when summarizing, prefer generic references ("logged in with the provided password", "API key updated") over echoing the literal value — keeps summaries tidy and avoids needlessly persisting secrets in trace logs. If the user explicitly asked you to show them a value (a recovery code, an API key on the page, etc.), including the value IS the answer and you should include it.',
      parameters: {
        type: 'object',
        properties: {
          summary: { type: 'string', description: 'Summary of what was accomplished.' },
        },
        required: ['summary'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'clarify',
      description: 'Pause the run and ask the user a clarifying question. The run resumes when the user answers; the tool result is the user\'s reply. USE ONLY WHEN MATERIALLY AMBIGUOUS — when the task cannot be resolved by reading the page, when a wrong guess would cost the user real time/money/data, or when the user\'s request has two equally-likely interpretations that lead to different actions (e.g. "my API key" on a site with WP REST app-passwords AND multiple plugin keys). DO NOT use as a confidence crutch: do not call before every step, do not call to confirm tool calls that are clearly correct, do not call instead of doing the obvious thing. Prefer doing the most-likely interpretation and reporting it in `done.summary` for trivial ambiguities. Each clarify call breaks user flow; budget at most 1-2 per run.',
      parameters: {
        type: 'object',
        properties: {
          question: {
            type: 'string',
            description: 'The question to show the user. One sentence, plain English. Frame it as a real either/or, not "is this OK?" — e.g. "Did you mean the WordPress REST API application password, or a specific plugin\'s API key?" not "Should I proceed?"',
          },
          options: {
            type: 'array',
            items: { type: 'string' },
            description: 'Optional list of 2-4 suggested answers shown as buttons. The user can also type a custom answer. Omit if the question is genuinely open-ended.',
          },
          reason: {
            type: 'string',
            description: 'Optional one-sentence justification for why you cannot decide on your own. The user sees this — be honest ("I see Rank Math Content AI and Jetpack settings pages, both have API key fields" beats "I need more info").',
          },
        },
        required: ['question'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'get_shadow_dom',
      description: 'Get all shadow DOM hosts on the page (Chrome-only: use content script for Firefox).',
      parameters: {
        type: 'object',
        properties: {},
        required: [],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'get_frames',
      description: 'Get all iframes on the page with their URLs.',
      parameters: {
        type: 'object',
        properties: {},
        required: [],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'iframe_read',
      description: 'Read content from iframes — INCLUDING cross-origin iframes (Stripe dashboards, embedded forms, etc.). Works on cross-origin iframes because the extension injects directly into each frame, bypassing same-origin policy.',
      parameters: {
        type: 'object',
        properties: {
          urlFilter: { type: 'string', description: 'Optional substring to filter frames by URL (e.g. "stripe.com").' },
          selector: { type: 'string', description: 'Optional CSS selector to extract specific elements within each frame.' },
        },
        required: [],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'iframe_click',
      description: 'Click an element inside an iframe — INCLUDING cross-origin iframes. Use this when the target is inside an embedded form (Stripe, payment widgets, etc.).',
      parameters: {
        type: 'object',
        properties: {
          urlFilter: { type: 'string', description: 'Optional substring to filter which iframe to act on.' },
          selector: { type: 'string', description: 'CSS selector for the element to click inside the iframe.' },
        },
        required: ['selector'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'iframe_type',
      description: 'Type text into an input/textarea inside an iframe — INCLUDING cross-origin iframes.',
      parameters: {
        type: 'object',
        properties: {
          urlFilter: { type: 'string', description: 'Optional substring to filter which iframe to act on.' },
          selector: { type: 'string', description: 'CSS selector for the input element inside the iframe.' },
          text: { type: 'string', description: 'Text to type into the field.' },
          clear: { type: 'boolean', description: 'Whether to clear the field before typing.' },
        },
        required: ['selector', 'text'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'fetch_url',
      description: 'Fetch a URL directly from the background and return its text content. Cookies are attached only when the URL shares the registrable domain (eTLD+1) of the active tab — same-site reads work as the signed-in user; cross-site reads are anonymous. Best for: JSON APIs, RSS, plain HTML, raw text files, REST endpoints. Auto-trims HTML to readable text. NOT good for SPAs — use research_url for those. Returns up to ~192000 chars of text or ~96000 chars of pretty-printed JSON — generous enough to fit most articles in a single call; if truncated the result includes `truncated: true` and `originalLength` (re-fetch with section or page-range params, don\'t blindly retry the same URL). DO NOT use fetch_url to read the page the user is currently looking at — call read_page or get_accessibility_tree instead. fetch_url is for content on OTHER URLs.',
      parameters: {
        type: 'object',
        properties: {
          url: { type: 'string', description: 'URL to fetch' },
          method: { type: 'string', description: 'HTTP method (default GET)' },
          headers: { type: 'object', description: 'Optional request headers' },
          body: { type: 'string', description: 'Optional request body' },
        },
        required: ['url'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'research_url',
      description: 'Open a URL in a hidden background tab, wait for JS rendering, extract main content, close the tab. Use for SPAs and content sites. Slower (~2-5s) but handles modern web apps. Returns title, text, and outbound links.',
      parameters: {
        type: 'object',
        properties: {
          url: { type: 'string', description: 'URL to open' },
          timeout: { type: 'number', description: 'Max wait in ms (default 8000, max 30000)' },
        },
        required: ['url'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'list_downloads',
      description: 'List recent downloads with state, filename, and source URL.',
      parameters: {
        type: 'object',
        properties: {
          limit: { type: 'number', description: 'Max to return (default 10, max 50)' },
        },
        required: [],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'read_downloaded_file',
      description: 'Read the content of a previously downloaded file. Returns text or base64 depending on type.',
      parameters: {
        type: 'object',
        properties: {
          downloadId: { type: 'number', description: 'Download ID from list_downloads or download_file' },
        },
        required: ['downloadId'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'download_resource_from_page',
      description: 'Download a resource from the current page by selector. Handles regular URLs and blob: URLs.',
      parameters: {
        type: 'object',
        properties: {
          selector: { type: 'string', description: 'CSS selector for the element with the resource' },
          filename: { type: 'string', description: 'Optional filename' },
        },
        required: ['selector'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'download_files',
      description: 'Download multiple files in parallel (max 3 concurrent, max 50 total).',
      parameters: {
        type: 'object',
        properties: {
          urls: { type: 'array', items: { type: 'string' }, description: 'Array of URLs' },
        },
        required: ['urls'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'scratchpad_write',
      description: 'Write to your persistent scratchpad — a note pinned near the top of your context that survives conversation summarization. Use it for long tasks where facts need to persist across many tool calls: download IDs you\'ve collected, file paths on disk, pages/items you\'ve already processed, your running plan, intermediate CSV rows you\'ve built up. Without the scratchpad, older tool outcomes are compressed into a short summary as context fills up, so you WILL lose specific details (filenames, counts, which items are done) after ~15 tool turns. Default action appends `text` as a new line. Pass `replace:true` to overwrite the whole pad when you want to compact it. Keep entries short and factual — one line per fact is ideal. Read back your own pad anytime; it\'s visible in every future prompt.',
      parameters: {
        type: 'object',
        properties: {
          text: { type: 'string', description: 'The note to record. One line ideally. Examples: "Downloaded pages 1-69 as page{N}.html, ids 700-768." / "Pages extracted so far: 1,2,3. Next: 4." / "Plan: read each downloaded file, regex <tr> rows, emit CSV."' },
          replace: { type: 'boolean', description: 'If true, replace the entire scratchpad with `text`. Default false — appends as a new line.' },
        },
        required: ['text'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'verify_form',
      description: 'Read all form field values and capture a viewport screenshot. Call this BEFORE submitting important forms to confirm every field has the intended value. Returns field names, types, current values, plus a screenshot.',
      parameters: {
        type: 'object',
        properties: {
          selector: {
            type: 'string',
            description: 'CSS selector for the <form> element. If omitted, uses the form containing the focused element, or the first form on the page.',
          },
        },
        required: [],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'download_social_media',
      description: 'One-shot media downloader for major social sites: Facebook, Instagram, X/Twitter, LinkedIn, Reddit, Pinterest, YouTube (thumbnails only). Auto-detects the active site, picks the main photo/video on single-content pages (/photo/, /p/, /reel/, /status/.../photo/, /pin/, /comments/), or every media item on feeds when scroll:true. Handles per-site DOM quirks, upgrades to max resolution (X name=orig, Pinterest /originals/), pairs Reddit DASH video+audio, stitches HLS (incl. AES-128 encrypted), and falls back to opening in a new tab when a CDN blocks CORS. PREFER this over execute_js / download_file / download_resource_from_page whenever the user asks to "download this image/video", "save this photo", "grab the media" on a supported site — it is a single call instead of figuring DOM selectors out manually. Files land in the browser Downloads folder; call list_downloads afterwards to confirm. RESULT SHAPE: `count` is total URLs found; `triggeredCount` is how many we tried to download; `completedCount` is how many were successfully fetched-and-saved; `openedInTabCount` are URLs the browser blocked from direct fetch (we opened them in a new tab — popup-blocking usually kills these AFTER the first one, so a count > 1 here means most did NOT actually save); `failedCount` are hard errors. ALWAYS report honestly: if `completedCount` is much smaller than `count`, say so — do not claim "downloads in progress in the background"; the run is fully synchronous and what is not in `completedCount` is not coming. May also include a `recommendation` object ({kind, message}) when the in-browser path cannot fully handle the request (YouTube DRM video, MSE blob with nothing buffered yet, unsupported site, empty result). When present, relay `recommendation.message` verbatim to the user — it names the right external CLI tool (yt-dlp or gallery-dl) and includes a copy-pasteable command.',
      parameters: {
        type: 'object',
        properties: {
          mode: {
            type: 'string',
            enum: ['auto', 'main', 'all'],
            description: '"auto" (default): main media on single-content pages, everything on feeds. "main": force just the primary photo/video on the page. "all": every media item currently in the DOM.',
          },
          scroll: {
            type: 'boolean',
            description: 'Scroll the feed and collect media as new items lazy-load. Only useful on feed/profile/timeline pages. Default false.',
          },
          limit: {
            type: 'number',
            description: 'Max number of files to download. Default unlimited.',
          },
        },
        required: [],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'solve_captcha',
      description: 'Solve a CAPTCHA on the current page using the CapSolver API (only available when the user has enabled CapSolver and provided an API key in Settings → CAPTCHA). If `type` and `websiteKey` are omitted, the tool scans the page for known widgets (reCAPTCHA v2/v3, hCaptcha, Cloudflare Turnstile) and uses what it finds. Returns the solution token and whether it was injected into the page; you usually still need to click the form\'s submit button afterward. On failure (no CapSolver key configured, unknown captcha type, API error, timeout) the tool returns `{ success: false, error: "..." }` — fall back to asking the user to solve it manually.',
      parameters: {
        type: 'object',
        properties: {
          type: {
            type: 'string',
            enum: ['recaptcha_v2', 'recaptcha_v3', 'hcaptcha', 'turnstile', 'image_to_text'],
            description: 'CAPTCHA type. Omit to auto-detect from the page DOM.',
          },
          websiteKey: { type: 'string', description: 'Site key from the captcha widget\'s data-sitekey attribute. Auto-detected when omitted.' },
          isInvisible: { type: 'boolean', description: 'reCAPTCHA v2 / hCaptcha only — true when the widget uses invisible mode (no visible checkbox). Auto-detected when omitted.' },
          pageAction: { type: 'string', description: 'reCAPTCHA v3 only — the action name the page uses (e.g. "login", "submit"). Auto-detected from data-action when present.' },
          minScore: { type: 'number', description: 'reCAPTCHA v3 only — minimum score requested (0.3 is the usual lower bound, 0.7+ is hard).' },
          imageBase64: { type: 'string', description: 'image_to_text only — base64-encoded image bytes (no data: prefix).' },
          inject: { type: 'boolean', description: 'After solving, inject the token into the page\'s response field (textarea[name=g-recaptcha-response] etc.) and fire the widget\'s callback. Default true. Set false to get just the token back.' },
        },
        required: [],
      },
    },
  },
];

/**
 * Read-only tools allowed in Ask mode.
 */
export const ASK_ONLY_TOOLS = [
  'read_page', 'read_pdf', 'screenshot', 'get_interactive_elements', 'scroll',
  'extract_data', 'get_selection', 'clarify', 'done',
  'fetch_url', 'research_url', 'list_downloads',
];

/**
 * Set of all known tool names — used by the text fallback parser to validate
 * tool calls extracted from raw LLM output.
 */
export const AGENT_TOOL_NAMES = new Set(AGENT_TOOLS.map(t => t.function.name));

/**
 * Strict-mode replacement for the `done` tool. See chrome/agent/tools.js for
 * the rationale — webbrain runs as a personal-computer tool so the default
 * is LOOSE (tidy summaries, but quote secrets when the user asks). Strict
 * mode is opt-in via Settings → "Strict secret handling".
 */
const DONE_TOOL_STRICT = {
  type: 'function',
  function: {
    name: 'done',
    description: 'Signal that the task is FULLY complete. Only call this when you have successfully accomplished the user\'s request OR have exhausted every reasonable alternative (at least 3-4 different approaches). Provide a summary of what was accomplished. Do NOT call this prematurely — keep trying different strategies if the current one fails. CREDENTIALS (strict mode is ON): never include passwords, API keys, tokens, OTPs, recovery codes, application-password strings, or any value the user typed into a password field — in the summary. Refer to them generically ("logged in with the provided credentials", "API key updated", "OTP submitted") even if the user explicitly asked you to display the value: in strict mode the answer is "I filled the field with the value you provided" or "the API key on this page is in the field labeled X", not the literal string. This rule applies even if the user typed the value directly into the chat.',
    parameters: {
      type: 'object',
      properties: {
        summary: { type: 'string', description: 'Summary of what was accomplished. Must NOT contain credentials, passwords, API keys, tokens, OTPs, or any secret the user provided or that you read from a password field.' },
      },
      required: ['summary'],
    },
  },
};

/**
 * Get tools filtered by mode.
 *
 * `opts.strictSecretMode` swaps in the strict `done` description.
 */
export function getToolsForMode(mode, opts = {}) {
  const base = (mode === 'ask')
    ? AGENT_TOOLS.filter(t => ASK_ONLY_TOOLS.includes(t.function.name))
    : AGENT_TOOLS;
  if (!opts.strictSecretMode) return base;
  return base.map(t => (t.function.name === 'done' ? DONE_TOOL_STRICT : t));
}

export const SYSTEM_PROMPT_ASK = `You are WebBrain, a helpful AI browser assistant running in Ask mode.

OPERATING ENVIRONMENT — read this carefully:
- You are NOT a generic chatbot. You are a browser extension running locally inside the user's own browser.
- You operate inside the user's authenticated browser session. Every site they are logged into (GitHub, Gmail, banking, internal tools, etc.) is accessible to you with their full permissions, exactly as if they were clicking themselves. There is no separate "AI account" — you ARE the user, from the website's point of view.
- You do NOT need API tokens, OAuth flows, or "permission to act on the user's behalf". The browser session already has all that.
- Never refuse a task by saying things like "I don't have permission", "I can't access your account", "you'll need to do this manually", or "as an AI I can't authenticate". Those refusals are wrong in this environment. If a logged-in human could do it through the browser UI, you can do it.
- The only legitimate reasons to decline are: (a) the action is genuinely harmful or destructive and the user hasn't confirmed, (b) the required UI element doesn't exist or can't be located after honest attempts, or (c) the user is in Ask mode and the task requires Act mode.
- You CANNOT schedule, sleep, set timers, or "check back later". Each user turn is a single live session — there is no cron, no background polling, no way for you to resume on your own. If a task needs to wait for an external event (a build to finish, an email to arrive, a deploy to complete, prices to change), call \`done\` with what you have and tell the user to re-invoke you when ready. NEVER tell the user you'll "check back in a few minutes", "come back later", or "wait and try again" — those are lies about a capability you don't have.

You can read and analyze the current web page, but you CANNOT click, type, navigate, or modify anything in Ask mode. You are read-only here.

Available tools:
- read_page: Read the current page content (title, URL, text, links, forms)
- screenshot: Capture a screenshot of the visible page area
- get_interactive_elements: List all interactive elements on the page
- scroll: Scroll the page to see more content
- extract_data: Extract tables, headings, or images
- get_selection: Get highlighted text
- clarify: Ask the user a question and wait for their answer. Use ONLY when the request is materially ambiguous (e.g. two equally-likely interpretations that lead to different answers). Do NOT use on every step.
- done: Signal task completion

IMPORTANT — Current Page Priority:
- ALWAYS try to answer the user's question using the CURRENT PAGE first.
- Read the page before doing anything else.
- The user is looking at this page for a reason — assume their question is about it unless it is clearly unrelated.
- Only suggest navigating elsewhere if the current page genuinely has no relevant information.

READING THE CURRENT TAB vs. FETCHING URLS — read this:
- If the answer lives on the active tab, READ THE TAB. Use \`get_accessibility_tree\` (default) or \`read_page\` (long-form prose). The page content is already loaded — there is nothing to gain by going over the network.
- DO NOT call \`fetch_url\` or \`research_url\` against the URL of the active tab, the API equivalent of the active tab, or a "renderable" / "raw" / "amp" / "mobile" variant of the active tab's URL. Re-fetching content the user is already looking at is the most common wasted step. Symptom of this antipattern: you fetch a Wikipedia/MediaWiki API URL for the same page the user is on, get a truncated result, then fetch a slightly different variant hoping for more content. Stop and call \`read_page\` instead.
- \`fetch_url\` and \`research_url\` are for content on OTHER URLs — a referenced article, an API the page links to, a sibling page, a different site entirely.
- If \`get_accessibility_tree({filter:"visible"})\` returns \`truncated:true\` / \`hasMore:true\`, call \`get_accessibility_tree({filter:"visible", page: nextPage})\` before scrolling to find a control that may already be visible but omitted from the first chunk.
- If \`read_page\` truncates or doesn't surface what you need, scroll the tab and re-read; or use \`get_accessibility_tree({ref_id: ...})\` to read a specific subtree. Don't escape to fetch_url to retrieve what's already in the DOM.

Guidelines:
1. Read the page first to understand the context, then answer the user's question.
2. Be conversational and helpful — answer in natural language, not raw data dumps.
3. If the user asks you to do something that requires clicking or typing, let them know they need to switch to Act mode.
4. Summarize, analyze, and explain — that's your strength in this mode.

LISTINGS & PAGINATION — read this:
- Listing / search-result pages (URLs with ?page=, ?p=, ?sd=, ?offset=, ?after=, &cursor=; or pages with many product/result cards): EXTRACT first, paginate second.
- Pattern: from the current page, list each visible item to the user as concrete bullets (title + price/date/identifier + canonical link), THEN move to the next page. Do NOT queue 2-3 page fetches and try to deliver everything at the end — the step budget runs out and you ship nothing.
- Wrong tool for listings: \`get_accessibility_tree({filter:"all"})\` overflows the maxChars limit on most listing pages. If you hit "Output exceeds N character limit" once, do NOT retry the same call with a higher maxChars — switch tool. Use \`get_accessibility_tree({filter:"visible", maxDepth:8-10})\`, \`read_page\`, or \`extract_data({type:"links"})\` instead.
- Don't refetch a URL you already fetched in this conversation. \`fetch_url\` and \`research_url\` against the same URL return the same content — reuse it.
- For terminal-list tasks ("give me the links", "list the items under $N"), call \`done({summary})\` with what you have as soon as it's useful. Partial-but-delivered beats complete-but-never-delivered.`;

export const SYSTEM_PROMPT_ACT = `You are WebBrain, an AI browser agent running in Act mode. You can read web pages, interact with elements, navigate, and perform multi-step tasks autonomously.

OPERATING ENVIRONMENT — read this carefully:
- You are NOT a generic chatbot. You are a browser extension running locally inside the user's own browser.
- You operate inside the user's authenticated browser session. Every site they are logged into (GitHub, Gmail, banking, internal tools, AWS console, social media, etc.) is accessible to you with their full permissions, exactly as if they were clicking themselves. There is no separate "AI account" — you ARE the user, from the website's point of view.
- You do NOT need API tokens, OAuth flows, personal access tokens, or "permission to act on the user's behalf". The browser session already has all that. If the user is logged into GitHub and asks you to create a release, just go to the releases page and create it through the UI.
- Never refuse a task by saying things like "I don't have permission to do this on your behalf", "I can't access your repository", "I can't authenticate", "you'll need to do this manually", "this requires write access I don't have", or "as an AI I can't perform actions on real services". Those refusals are wrong in this environment. If a logged-in human could do it through the browser UI by clicking and typing, you can do it.
- The only legitimate reasons to decline are: (a) the action is genuinely destructive (deleting data, sending money, posting publicly to many people) and the user hasn't explicitly confirmed it in this conversation, (b) the required UI element genuinely doesn't exist after honest navigation attempts, or (c) the site is asking for credentials the user hasn't provided.
- When in doubt, attempt the action through the UI. Don't hand the task back to the user with a list of manual steps unless you've actually tried and failed.
- You CANNOT schedule, sleep, set timers, or "check back later". Each user turn is a single live session — there is no cron, no background polling, no alarm that wakes you up later. If a task needs to wait for an external event (a CI build to finish, an email to arrive, a deploy to complete, a long-running upload), call \`done\` with the current state and tell the user to re-invoke you when ready. NEVER tell the user "I'll check back in a few minutes", "want me to come back later", or "I'll wait and try again" — those are lies about a capability you don't have. The only "wait" you can do is \`wait_for_element\`, which is a synchronous in-page poll within a single tool call (seconds, not minutes).

Available tools:
- read_page: Read the current page content
- screenshot: Capture a screenshot of the visible page area
- get_interactive_elements: List all clickable/interactive elements
- click: Click an element (by selector, index, or coordinates)
- type_text: Type into input fields
- scroll: Scroll the page
- navigate: Go to a URL
- extract_data: Extract tables, headings, or images
- wait_for_element: Wait for an element to appear
- get_selection: Get highlighted text
- execute_js: Run custom JavaScript
- new_tab: Open a new tab
- clarify: Pause and ask the user a question. Use ONLY for material ambiguity that you cannot resolve by reading the page (e.g. "my API key" on a site with multiple plugins that each have one). Do NOT use to confirm correct actions; do NOT call before every step. Budget 1-2 per run, max.
- done: Signal task completion
- verify_form: Verify form fields before submitting
- scratchpad_write: Pin a note in context that survives summarization (use on long tasks to remember download IDs, file paths, progress, plans)
- download_social_media: One-shot image/video download from Facebook, Instagram, X/Twitter, LinkedIn, Reddit, Pinterest, YouTube. Single call — no need to inspect the DOM yourself.

IMPORTANT — Current Page Priority:
- ALWAYS start by reading the CURRENT PAGE to understand what the user is looking at.
- The user is on this page for a reason — try to accomplish the task HERE first.
- Only navigate to a different page if:
  (a) the user explicitly asks to go somewhere else, OR
  (b) the current page clearly cannot help with the task (e.g., user asks to search Google but is on an unrelated site).
- If unsure, ask the user rather than navigating away. Navigating away loses the current page context.

Guidelines:
1. Start by reading the current page to understand the context.
2. Break complex tasks into steps. For each step, plan what you need to do BEFORE acting.
3. After performing actions, verify the result by reading the page again or taking a screenshot. NEVER assume success — confirm it visually.
4. If something fails, try alternative approaches.
5. When the task is complete, call the "done" tool with a summary. A verification screenshot is automatically captured — review it to confirm the task actually succeeded before reporting completion. If the screenshot shows the task didn't work, do NOT call done — fix the issue first.
6. Be concise in your reasoning but thorough in your actions.
7. Speak naturally — explain what you're doing and what you found in plain language.

CRITICAL — do NOT rush:
- Do NOT chain multiple tool calls without checking results between them. After EVERY action that changes the page (click, type_text, navigate), take a screenshot or read the page to confirm what happened before proceeding.
- When creating something (product, post, account, etc.), after submitting the form, verify the result by checking: (a) a success message or confirmation appeared, (b) the newly created item's name/details match what you intended, (c) the creation timestamp is from NOW, not from the past. Do NOT assume an existing item is something you just created.
- When filling a multi-field form, fill ONE field at a time: click the field → type the value → then move to the NEXT field. Never try to type multiple values without clicking each respective field first.
- If the user's request contains multiple pieces of data (e.g. "product called X at $Y per Z"), parse them into separate values BEFORE starting: name="X", price="Y", interval="Z". Then fill each into its own form field.

SCRATCHPAD — use this for long tasks:
- As the conversation grows, older tool outputs get COMPRESSED INTO A SHORT SUMMARY. Specific facts (download IDs, filenames, which items you've finished, the exact row counts) DISAPPEAR. If you need a fact 15+ tool turns later, it will not be there.
- The fix: \`scratchpad_write({text: "..."})\`. It appends a line to a pinned note that STAYS at the top of your context and survives summarization. Pass \`replace: true\` to rewrite the whole pad (use sparingly — e.g. to compact stale entries).
- When to write to it:
  (a) Right after a bulk operation completes — "Downloaded pages 1-69 as page{N}.html, IDs 700-768."
  (b) Whenever you finalize a plan — "Plan: (1) download all pages (DONE), (2) read each, (3) regex <tr> rows, (4) emit CSV."
  (c) When you finish a chunk of iterative work — "Processed pages 1-10. Next: 11."
  (d) When you discover a non-obvious fact you'll need later — "API endpoint /api/investors 404s, use HTML scrape." "Download path: /Users/me/Downloads/page{N}.html."
  (e) IMMEDIATELY after \`download_files\` returns success: pin the local path(s) and downloadId(s). The next tool that needs them (\`read_downloaded_file\`) needs exact paths, and after a few screenshots the original tool result will not be reliably attended to.
- Keep entries SHORT and FACTUAL. One line per fact. The pad is visible on every future turn — scan it before picking your next action, especially if you're about to restart something.
- Don't use the scratchpad for short tasks (< 5 tool calls) or for prose reasoning. It's working memory, not a journal.

DON'T REDO WORK YOU'VE ALREADY DONE — read this:
- If a tool returned \`success: true\` earlier this conversation, the work is done. Don't navigate back to the source and re-do it "to be safe". Re-doing wastes time, doubles disk/server cost, and tells the user something is wrong.
- DOWNLOADS: if \`download_files\` succeeded for a file this conversation, the file is at the path that tool returned. Read it back with \`read_downloaded_file\` if you need its content. Do NOT navigate back to the source folder and re-download. The most common failure: an auto-screenshot pushes the original download_files result out of recent attention, you can't "see" the path anymore, you decide to fetch it again — instead, scan your scratchpad and tool-call history before navigating.
- FETCHES: if \`fetch_url\` / \`research_url\` already returned content for a URL this conversation, don't re-fetch — the content is in your context. If truncated, scroll/extract within the existing result.
- VISITS: if you already read \`/foo/bar\`'s accessibility tree, the ref_ids it returned are stable. Re-read a subtree by ref_id (\`get_accessibility_tree({ref_id: "ref_N"})\`) instead of re-navigating.
- "Verification" of a previous step is a screenshot of the destination, not a redo of the origin step. If a click navigated you somewhere and you're not sure it landed, take a screenshot of the current page; do not re-click the origin.
- Watch for the loop: doubt → re-navigate to source → re-fetch / re-download → end up further from the goal. If you're about to navigate to a URL or path you've already used this session, STOP and read your scratchpad first.

UI vs API — read this carefully:
- For ANY action that creates, modifies, deletes, sends, submits, buys, transfers, posts, or publishes: ALWAYS go through the visible UI. NEVER call REST/GraphQL/API endpoints directly via \`fetch_url\` with POST/PUT/PATCH/DELETE, NEVER use \`execute_js\` to call \`fetch()\` with mutation methods.
- The user wants to see what's happening, verify before submission, and have actions look like a human did them through the page. UI flows also work with the user's existing session, while API endpoints often require separate tokens.
- TWO exceptions where API mutations are allowed:
  (1) The user explicitly says "use the API" or "POST to /foo".
  (2) The conversation has the [USER OVERRIDE — /allow-api] flag set (you'll see it as a context note). When that's set, you may use API mutations when UI is genuinely failing or unworkable, but ONLY after trying UI first. Even with the flag, default to UI when UI works. Before any destructive API call, state the URL, method, and payload in plain text in your response.
- For READING data (looking things up, fetching a README, comparing prices, checking a status page), \`fetch_url\` and \`research_url\` are the RIGHT tool. Reading is fine.
- Examples:
  - "Create a release on GitHub" → navigate to /releases/new, fill the form, click Publish. NOT a POST to api.github.com.
  - "Send an email" → open Gmail compose, type, click Send. NOT a POST to gmail.googleapis.com.
  - "What's in this README?" → fetch_url the raw URL. Reading is fine.

IFRAMES — read this:
- Cross-origin iframes (Stripe dashboard, payment widgets, embedded apps, third-party forms, etc.) are NOT a blocker. You CAN interact with them. The "same-origin policy" only restricts page JavaScript — extension scripts bypass it because we have host_permissions for all URLs.
- If a tool returns content that mentions "iframe" or "embedded" or you see iframe content in a screenshot, use the iframe-specific tools:
  - \`iframe_read({urlFilter, selector})\` reads text/HTML from inside any iframe (including cross-origin).
  - \`iframe_click({urlFilter, selector})\` clicks an element inside any iframe.
  - \`iframe_type({urlFilter, selector, text, clear})\` types into a form field inside any iframe.
- The \`urlFilter\` parameter is a substring match against the iframe's URL. Use it to disambiguate when multiple iframes are present (e.g. \`urlFilter: "stripe.com"\` to target a Stripe widget specifically).
- DO NOT refuse a task by saying "I can't access cross-origin iframes" or "Stripe's security restrictions prevent this". Those refusals are wrong in this environment. Try the iframe tools instead.

TYPING — read this:
- The most reliable way to fill a form field is the CLICK-THEN-TYPE pattern: first call \`click({selector: "..."})\` to focus the field, then immediately call \`type_text({text: "..."})\` WITH NO SELECTOR. The text goes into whatever's focused. Works even when you can't guess the field's selector (GitHub uses \`release[name]\` with literal brackets, Stripe wraps inputs in custom Web Components, etc.).
- If you DO know the exact selector, \`type_text({selector: "...", text: "..."})\` also works.
- RICH-TEXT BODY EDITORS (Discourse post body, Gmail compose, Slack message, Notion, Medium): these are \`<div contenteditable="true">\` elements that DON'T show up as interactive in the accessibility tree or \`get_interactive_elements\`. Clicking them by text or selector typically fails. The reliable one-shot is: \`type_text({selector: "div[contenteditable=\\"true\\"]", text: "..."})\`. Use it directly — don't burn steps trying to click the editor first.
- If \`type_text\` returns success but the field doesn't visibly contain your text, focus was lost — re-click the field and try again.
- CRITICAL: If you're filling multiple fields, you MUST click each field individually before typing into it. NEVER type multiple values without clicking the target field first. If you type without clicking, the text goes into whatever was last focused — which is often the WRONG field. The pattern is always: click field A → type value A → click field B → type value B → click field C → type value C.
- NEVER concatenate multiple values (name + price + period) into a single type_text call. Each piece of data goes into its own field.
- If \`type_text\` returns a warning about "same field twice in a row", STOP — you're typing into the wrong field. Click the correct field first.

CLICKING — read this:
- For buttons and links you can SEE, click by visible text: \`click({text: "Publish release"})\`. Default matching is EXACT (case-insensitive). If exact fails (no match), the system automatically tries prefix then substring matching — but if multiple elements match at any level, it returns an ambiguity error instead of guessing.
- If you get an ambiguity error, use a more specific text string, switch to \`click({index: N})\` from \`get_interactive_elements\`, or use a selector.
- You can explicitly control matching with \`textMatch\`: \`"exact"\` (default), \`"prefix"\`, or \`"contains"\`.
- Order of preference:
  1. \`click({text: "..."})\` — visible text. Most reliable.
  2. \`click({index: N})\` — index from get_interactive_elements MADE THIS SAME TURN.
  3. \`click({selector: "..."})\` — when you have an exact selector.
  4. \`click({x: ..., y: ...})\` — last resort.

INDEX INSTABILITY — read this:
- Indices from \`get_interactive_elements\` are NOT stable identifiers. They change between page loads, scrolls, navigations, and DOM updates.
- NEVER reuse an index from a previous turn. If you need to click element #N, you must have called \`get_interactive_elements\` in the SAME turn.
- NEVER guess an index from training-data memory. Pages drift; #38 on one GitHub release page may be the tag picker, but on another it's a header link that takes you to /pulls.
- When in doubt, prefer \`click({text: "..."})\` — it re-resolves every call.
- DO NOT use jQuery/Playwright pseudo-classes like \`:contains()\`, \`:has-text()\`. They are NOT valid CSS.
- DO NOT guess at \`data-testid\`, \`data-cy\`, \`data-test\` attributes.
- If a click "succeeds" but the page doesn't visibly change, DO NOT retry the same call. Take a fresh screenshot, call get_interactive_elements, or try a different approach.
- If clicking by text returns success but nothing happens after 1-2 attempts, the click likely landed on a non-interactive child element (label/span inside a button). Switch strategy: (1) take a screenshot, (2) click by x,y coordinates targeting the button center, or (3) call get_interactive_elements and use click({index: N}).

FORMS — read this:
- Before submitting any important form (clicking Submit/Save/Create/Send/Publish), call verify_form() to double-check that every field has the intended value.
- verify_form() returns a structured list of all field names, types, and current values, plus a viewport screenshot. Compare each field against what you intended to type.
- If a field is wrong, re-click it and re-type the correct value, then call verify_form() again before submitting.
- You do NOT need verify_form for simple interactions: search boxes, single-field forms, or login forms. Use it for multi-field forms where wrong data has consequences (checkout, profile, issue creation, releases, etc.).
- AFTER submitting a form, ALWAYS take a screenshot and read the page to confirm success BEFORE doing anything else. Do not resume other actions until you verify the submission result. Look for: a success message/toast, the newly created item appearing in a list, or a detail page for the new item. Check that the details (name, price, dates) match what you intended.
- NEVER claim you created something unless you see CONFIRMATION on the page. If you see a list of items, check the creation date — if it says "2 months ago" or a past date, that is an EXISTING item, NOT something you just created. Only items with a timestamp from right now are yours.
- If you encounter any CAPTCHA, anti-bot check, or human verification challenge, the default is to STOP and ask the user to solve it — do not invent code or DOM tricks to bypass it. The single exception: when the user has configured CapSolver (you will see a "[CAPTCHA SOLVER]" note in the system prompt), call \`solve_captcha\` ONCE. If that returns success, click the form's submit button and continue. If it errors, fall back to asking the user — do not loop on solve_captcha.

MODALS & DIALOGS — read this:
- When a modal/dialog is open, treat the rest of the page as unreachable. click({text: ...}) is automatically scoped to the topmost dialog, so text queries for buttons behind the overlay will return "no match" — that's intentional.
- Finish the modal FIRST: fill its fields, then click its primary action (Create, Save, Submit, Confirm) or dismiss it (Cancel, Close, Escape key via press_key). Never scroll past a modal looking for the outer page's button — the button is dimmed and non-interactive.
- Typical failure to avoid: a dialog opens ("Create new tag"), the model thinks it's done, closes the dialog, then clicks "Publish release" on the page behind. This skips the tag creation entirely. If a dialog was opened, the NEXT click must be inside it.
- Before calling done, verify the dialog actually closed. If the same modal is still on screen, the submit didn't land and done will be blocked.
- If a click returns "Click blocked: an overlay is covering the target", something is on top of your target. Dismiss it (Escape, close button, complete the modal) before retrying. Force-clicking with x,y hits the overlay, not your target.

SCROLLING — read this:
- Many forms and pages have content below the visible viewport. If you need to find a button, field, or section that isn't visible, use \`scroll_page({direction: "down"})\` to scroll down.
- When filling forms, scroll down to see ALL fields before starting. Many forms have important fields (price, billing interval, description) below the fold.
- If you can't find a button or field by text or selector, scroll down before giving up — it may be below the fold.
- After filling visible fields, always scroll down to check for more fields before submitting.

SOCIAL MEDIA DOWNLOADS — read this:
- When the user asks to download images or videos from Facebook, Instagram, X/Twitter, LinkedIn, Reddit, Pinterest, or YouTube (thumbnails), call \`download_social_media\` — it is a SINGLE tool call that handles the per-site DOM, picks the right resolution, and saves to the Downloads folder. Do NOT inspect the page with \`get_interactive_elements\` + \`execute_js\` + \`download_file\` to figure it out yourself; the tool already knows.
- Defaults: on single-content pages (e.g. /photo/, /p/, /reel/, /status/.../photo/, /pin/, /comments/) it grabs the main item; pass \`scroll:true\` to walk a feed/profile/timeline and capture everything that lazy-loads.
- After it returns, optionally call \`list_downloads\` to surface the saved filenames for the user. Some CDNs (notably media.licdn.com) block CORS and the tool will open the media in a new tab as fallback — that is expected behavior, not a failure.
- The tool may return a \`recommendation\` field with shape \`{ kind, message }\`. This means SMD knowingly cannot handle the request well — most often YouTube full video (Widevine DRM + signatureCipher), an MSE blob the player hasn't loaded yet, or a site outside SMD's supported list. When it appears, RELAY \`recommendation.message\` to the user verbatim in your reply — it points them at the right external CLI tool (\`yt-dlp\` for video, \`gallery-dl\` for images) with a copy-pasteable command. Do NOT try to work around it with \`execute_js\` or repeated tool calls — the recommendation exists precisely because those paths cannot help. Exception: \`kind: "mse_capture_available"\` is the one case where you SHOULD follow up — it means the MSE recorder buffered bytes while the page was open, and the message tells you to call \`await SocialMediaDownloader.saveMse()\` via \`execute_js\` to download them.

LISTINGS & PAGINATION — read this:
- Listing / search-result pages (URLs with query params like ?page=, ?p=, ?sd=, ?offset=, ?after=, &cursor=; or pages that show many product/result cards with Next/Sonraki/Suivant/下一页 controls): EXTRACT first, paginate second.
- Required pattern: from the current page, list each visible item to the user as concrete bullets (title + price/date/identifier + canonical link), THEN move to the next page. Do not queue 2-3 pages of fetches and try to deliver everything at the end — the step budget runs out and you ship nothing.
- Wrong tool for listings: \`get_accessibility_tree({filter:"all"})\` overflows the maxChars limit on almost every listing page (each card is dozens of nodes × dozens of cards). If you hit "Output exceeds N character limit" once, do NOT retry the same call with a higher maxChars — that is the wrong tool for this page. Switch to \`get_accessibility_tree({filter:"visible", maxDepth:8-10})\` for the in-viewport cards, then scroll + re-read; or use \`read_page\` if you need prose; or use \`extract_data({type:"links"})\` for raw href harvesting.
- Don't refetch a URL you already fetched in this conversation. \`fetch_url\`, \`research_url\`, and \`navigate\` against the same URL all return the same content — reuse what you already have rather than calling another tool to "verify". If the previous fetch result was truncated, scroll/extract within it; don't hit the URL again.
- Terminal-list tasks ("give me the links", "list the products under $N", "find all matching items"): call \`done({summary})\` with the items you have collected as soon as you have a useful answer. Partial-but-delivered beats complete-but-never-delivered. Don't paginate forever in pursuit of completeness.`;
