Test the fix:


Bash

curl -s "https://mssql.danmarcrm.com/dev1/extension2/public/api.php?action=contacts&search=digitalj2&limit=5" 2>&1 | head -c 600

Test contacts search with MongoDB


{
    "contacts": [
        {
            "_id": "6942810214400b3b979fe09a",
            "email": "tbelt@digitalj2.com",
            "_created_at": "2025-12-17 10:08:02",
            "_migrated_at": "2025-12-17 12:08:30",
            "_source": "mysql_migration",
            "_updated_at": "2025-12-17 10:08:30",
            "company": "",
            "created_at": "2025-12-14 16:42:32",
            "first_contact": "2022-09-19 17:07:07",
            "gmail_account_id": 1,
            "id": 7,
            "is_active": 1,
            "last_contact": "2025-11-20 19:16:19",
            "mysql_id":


Now contacts search works. Let me test more:


curl -s "https://mssql.danmarcrm.com/dev1/extension2/public/api.php?action=contacts&search=ettiene&limit=5" 2>&1 | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d['contacts']), 'contacts found'); [print(f'  {c[\"email\"]} - {c.get(\"name\",\"\")} ({c.get(\"total_emails\",0)} emails)') for c in d['contacts'][:5]]" 2>&1

Test contacts search for 'ettiene'


0 contacts found


curl -s "https://mssql.danmarcrm.com/dev1/extension2/public/api.php?action=contacts&search=et&limit=5" 2>&1 | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d['contacts']), 'contacts found'); [print(f'  {c[\"email\"]} - {c.get(\"name\",\"\")} ({c.get(\"total_emails\",0)} emails)') for c in d['contacts'][:5]]" 2>&1

Test contacts search for 'et'


5 contacts found
  rbennett@digitalj2.com - Ryan Bennett (227 emails)
  reti.randorg@windakgroup.com - Reti Randorg (0 emails)
  newsletter@news.orange.ro - Orange (2 emails)
  nrithya@haass.io - Nrithya Theetharappan (2 emails)
  marketing@norsteel.com - Norsteel Buildings (0 emails)


curl -s "https://mssql.danmarcrm.com/dev1/extension2/public/api.php?action=contacts&search=asendia&limit=5" 2>&1 | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d['contacts']), 'contacts found'); [print(f'  {c[\"email\"]} - {c.get(\"name\",\"\")} ({c.get(\"total_emails\",0)} emails)') for c in d['contacts'][:5]]" 2>&1

Test search for 'asendia'


5 contacts found
  varsha.kanade@asendia.com - Varsha Kanade (0 emails)
  stephanie.kutschera@asendia.com - Stephanie Kutschera (0 emails)
  digitalmedia@asendia.com - Asendia Insights (6 emails)
  info@asendia.com - Asendia Communications (2 emails)
  noreply.crm@asendia.com - Asendia CRM (2 emails)


Search works now. The contact `stephanie.kutschera@asendia.com` shows `0 emails` though — that's because `total_emails` in MongoDB may not be populated. Let me check how threads work for a contact:


curl -s "https://mssql.danmarcrm.com/dev1/extension2/public/api.php?action=threads&contact=2&limit=3" 2>&1 | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('pagination',{})); [print(f'  {t[\"thread_id\"]} - {t.get(\"subject\",\"\")} ({t.get(\"email_count\",\"?\")} emails)') for t in (d.get('threads',[]))[:5]]" 2>&1

Test threads for a contact


{'page': 1, 'limit': 3, 'total': 2, 'pages': 1}
  FMfcgzQcpTHplSrtpktwwVKGqWlQZwQd - DanMar - August 2025 Payment (4 emails)
  193fa7810880e74d -  (1 emails)


Good, threads work. Now let me fix the second issue: **the UI needs to make it clearer how to add multiple threads**. Let me also add the ability to search threads directly (not just via contacts). Let me enhance the context builder:


TodoWrite

Fix contacts search to use QueryAdapter (MongoDB support)

Enhance context builder: search threads directly + better UX for adding

Test the fixes end-to-end


Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable

Fix contacts search to use QueryAdapter (MongoDB support)

Enhance context builder: allow searching threads directly + wildcards

Test the fixes end-to-end

Enhance context builder: search threads directly + better UX for adding

Test the fixes end-to-end


Now let me improve the context builder with:
1. A search that finds **both contacts AND threads** in one go
2. Better visual feedback when threads are added
3. Clearer "Add All" and individual add buttons


  2815→function renderQuickPrompts() {
  2816→    const container = document.getElementById('ai-quick-prompts');
  2817→    if (!container) return;
  2818→    // Use agent prompts if agent selected, then multithread if context loaded, then mode
  2819→    let prompts;
  2820→    const agent = AI_AGENTS[aiChat.agent];
  2821→    if (agent && agent.id !== 'general') {
  2822→        prompts = agent.quickPrompts;
  2823→    } else if (aiChat.contextThreads.length > 0) {
  2824→        prompts = AI_QUICK_PROMPTS.multithread;

function renderQuickPrompts() {
    const container = document.getElementById('ai-quick-prompts');
    if (!container) return;
    // Use agent prompts if agent selected, then multithread if context loaded, then mode
    let prompts;
    const agent = AI_AGENTS[aiChat.agent];
    if (agent && agent.id !== 'general') {
        prompts = agent.quickPrompts;
    } else if (aiChat.contextThreads.length > 0) {
        prompts = AI_QUICK_PROMPTS.multithread;


Now let me replace the `searchContextContacts` and `selectContextContact` functions with a unified search that finds both contacts and threads:


Edit

async function searchContextContacts() {
    const query = document.getElementById('ai-context-search-input').value.trim();
    if (!query) return;
    const resultsDiv = document.getElementById('ai-context-search-results');
    resultsDiv.innerHTML = '
Searching...
';
    try {
        const data = await apiCall('contacts', { search: query, limit: 5 });
        if (!data.contacts || data.contacts.length === 0) {
            resultsDiv.innerHTML = '
No contacts found
';
            return;
        }
        resultsDiv.innerHTML = data.contacts.map(c => `

                ${escapeHtml(c.name || c.email)} ${escapeHtml(c.email)}
                ${c.total_emails || 0} emails

        `).join('');
    } catch (err) {
        resultsDiv.innerHTML = '
Error: ' + escapeHtml(err.message) + '
';
    }
}

async function searchContextContacts() {
    const query = document.getElementById('ai-context-search-input').value.trim();
    if (!query) return;
    // Strip wildcard chars — backend already does partial matching
    const cleanQuery = query.replace(/[*%]/g, '').trim();
    if (!cleanQuery) return;

    const resultsDiv = document.getElementById('ai-context-search-results');
    resultsDiv.innerHTML = '
Searching contacts & threads...
';

    try {
        // Search both contacts and threads in parallel
        const [contactsData, threadsData] = await Promise.all([
            apiCall('contacts', { search: cleanQuery, limit: 10 }),
            apiCall('threads', { search: cleanQuery, limit: 10 }),
        ]);

        let html = '';
        const contacts = contactsData.contacts || [];
        const threads = threadsData.threads || [];

        if (contacts.length === 0 && threads.length === 0) {
            resultsDiv.innerHTML = '
No contacts or threads found for "' + escapeHtml(cleanQuery) + '"
';
            return;
        }

        // Contacts section
        if (contacts.length > 0) {
            html += '
CONTACTS
';
            html += contacts.map(c => {
                const name = c.name || c.email;
                return `

                    \u{1F464} ${escapeHtml(name)} ${escapeHtml(c.email)}
                    show threads \u25B6

`;
            }).join('');
        }

        // Threads section — directly addable
        if (threads.length > 0) {
            html += '
THREADS
';
            html += threads.map(t => {
                const isAdded = aiChat.contextThreads.some(ct => ct.thread_id === t.thread_id);
                const subject = t.subject || '(No Subject)';
                return `

                        \u{1F4E7} ${escapeHtml(subject.substring(0, 45))}
                        (${t.email_count || '?'})

                    <button onclick="event.stopPropagation();addThreadToContext('${t.thread_id}', \`${escapeHtml(subject.substring(0,50))}\`, ${t.email_count||0}, '')"
                            style="background:${isAdded ? '#10b981' : '#6366f1'}; color:#fff; border:none; border-radius:4px; padding:2px 8px; font-size:0.85em; cursor:pointer; margin-left:4px; white-space:nowrap;"
                            ${isAdded ? 'disabled' : ''}>
                        ${isAdded ? '\u2713 Added' : '+ Add'}
                    </button>

`;
            }).join('');
        }

        resultsDiv.innerHTML = html;
    } catch (err) {
        resultsDiv.innerHTML = '
Error: ' + escapeHtml(err.message) + '
';
    }
}