Fedge
@fedge
// AGENT BRIDGE v19.0 – production hardening, global regex fix, resilient cleanup, serial sends
(async function agentBridge() {
'use strict';
// ── Configuration ──────────────────────────────────
const CONFIG = {
SERVER: 'http://localhost:8765',
TEXTAREA_SEL: '._27c9245',
SEND_BTN_SEL: '._52c986b',
SEND_DELAY: 5000,
RETRY_INTERVAL: 5000,
// Retry button identification (multiple strategies)
RETRY_SVG_PATH: "M1.272 6.21348C1.70645 3.08888 4.59169 0.908064 7.71634 1.34239C8.95495 1.51469 10.0438 2.07331 10.8814 2.87755L11.9458 1.81407C12.1347 1.6255 12.4572 1.75911 12.4575 2.02598V5.08751C12.4574 5.25303 12.3233 5.38731 12.1577 5.38731H9.0972C8.82993 5.38731 8.69629 5.06361 8.88528 4.87462L10.0327 3.72618C9.3732 3.09994 8.52006 2.66569 7.5513 2.53087C5.08313 2.18779 2.80376 3.91044 2.46048 6.37852C2.11747 8.84665 3.84009 11.1261 6.30814 11.4693C8.77612 11.8121 11.0557 10.0896 11.399 7.62169L11.9937 7.70372L12.5874 7.78673C12.153 10.9112 9.26756 13.0919 6.1431 12.6578C3.01854 12.2234 0.837738 9.33809 1.272 6.21348Z"
};
// ── Regex (separate non‑global for single capture, global for matchAll) ──
const TOOL_CALL_REGEX = /<tool_call>(.*?)<\/tool_call>/; // no g – for single match with groups
const TOOL_CALL_REGEX_GLOBAL = /<tool_call>(.*?)<\/tool_call>/g; // global – for matchAll scanning
// ── State ──────────────────────────────────────────
const processedPayloads = new Set();
let isProcessing = false; // prevents concurrent command execution
// ── Helpers ────────────────────────────────────────
const nativeTextAreaValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLTextAreaElement.prototype, 'value'
).set;
/**
* Extract the first unprocessed <tool_call> payload from a string.
* Returns { payload, jsonStr, fullMatch } or null.
*/
function extractAgentPayload(text) {
const match = text.match(TOOL_CALL_REGEX);
if (!match) return null;
const jsonStr = match[1].trim();
if (processedPayloads.has(jsonStr)) return null;
try {
const payload = JSON.parse(jsonStr);
return { payload, jsonStr, fullMatch: match[0] };
} catch (e) {
processedPayloads.add(jsonStr); // don't try broken JSON again
return null;
}
}
/**
* Mark all existing <tool_call> payloads in the current DOM to avoid re‑processing.
*/
function markExistingPayloads() {
const pres = document.querySelectorAll('[class*="ds-message"]:has(.ds-markdown) pre');
for (const pre of pres) {
const text = pre.textContent || '';
for (const match of text.matchAll(TOOL_CALL_REGEX_GLOBAL)) {
processedPayloads.add(match[1].trim());
}
}
console.log(`🔖 Pre-marked ${processedPayloads.size} existing tool call(s).`);
}
/**
* Find the most recent (last in DOM) unprocessed agent payload.
*/
function findAgentPayload() {
const pres = document.querySelectorAll('[class*="ds-message"]:has(.ds-markdown) pre');
for (let i = pres.length - 1; i >= 0; i--) {
const text = pres[i].textContent || '';
const result = extractAgentPayload(text);
if (result) {
return result;
}
}
return null;
}
/**
* Remove the exact fullMatch string from any <pre> element that contains it.
* (Safe even if the original element was replaced.)
*/
function cleanupPayloadFromDOM(fullMatch) {
const pres = document.querySelectorAll('[class*="ds-message"]:has(.ds-markdown) pre');
for (const pre of pres) {
if (pre.textContent.includes(fullMatch)) {
pre.textContent = pre.textContent.replace(fullMatch, '');
return true;
}
}
return false;
}
/**
* Simulate typing and sending a message in the DeepSeek UI.
*/
async function sendMessage(text) {
const textarea = document.querySelector(CONFIG.TEXTAREA_SEL);
if (!textarea) {
console.error('❌ Textarea not found!');
return;
}
textarea.focus();
nativeTextAreaValueSetter.call(textarea, text);
textarea.dispatchEvent(new InputEvent('input', {
bubbles: true, cancelable: true, inputType: 'insertText', data: text,
}));
textarea.dispatchEvent(new Event('change', { bubbles: true }));
await new Promise(r => setTimeout(r, 150));
const sendBtn = document.querySelector(CONFIG.SEND_BTN_SEL);
if (!sendBtn) {
console.error('❌ Send button not found!');
return;
}
if (sendBtn.classList.contains('ds-button--disabled')) {
console.warn('⚠️ Button disabled, using Enter key fallback.');
// A single keydown Enter is usually enough to trigger React's handler
textarea.dispatchEvent(new KeyboardEvent('keydown', {
key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true
}));
return;
}
sendBtn.click();
}
/**
* Locate the failed‑stream retry button using multiple strategies.
*/
function findFailedStreamButton() {
// Strategy 1: exact SVG path (most specific)
const xpath = `//*[local-name()='path' and @d="${CONFIG.RETRY_SVG_PATH}"]/ancestor::*[self::button or self::a or @role='button' or contains(@class,'button') or contains(@class,'retry')]`;
const result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
if (result.singleNodeValue) return result.singleNodeValue;
// Strategy 2: fallback – any path whose 'd' attribute starts with the known prefix
const paths = document.querySelectorAll('path');
for (const path of paths) {
const d = path.getAttribute('d') || '';
if (d.startsWith('M1.272 6.21348')) {
const btn = path.closest('button, a, [role="button"], .button, .retry');
if (btn) return btn;
}
}
return null;
}
// ── Main Loops ─────────────────────────────────────
async function retryLoop() {
while (true) {
await new Promise(r => setTimeout(r, CONFIG.RETRY_INTERVAL));
try {
const btn = findFailedStreamButton();
if (btn) {
console.log('🔄 Retrying failed stream...');
btn.click();
await new Promise(r => setTimeout(r, 2000));
}
} catch (err) {
console.error('Retry loop error:', err);
}
}
}
async function commandLoop() {
while (true) {
await new Promise(r => setTimeout(r, 1500));
// Don't start a new command if still processing the previous one
if (isProcessing) continue;
const agentData = findAgentPayload();
if (!agentData) continue;
isProcessing = true;
console.log('⏳ Command found, waiting', CONFIG.SEND_DELAY, 'ms before sending...');
await new Promise(r => setTimeout(r, CONFIG.SEND_DELAY));
console.log('📤 Sending to server:', agentData.payload);
try {
const res = await fetch(CONFIG.SERVER, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(agentData.payload)
});
const text = await res.text();
console.log('📥 Server response:', text);
processedPayloads.add(agentData.jsonStr);
// Clean up using the fullMatch string, not a stale element reference
cleanupPayloadFromDOM(agentData.fullMatch);
await sendMessage(text || 'Server replied with empty response');
} catch (err) {
console.error('❌ Bridge error:', err);
} finally {
isProcessing = false;
}
}
}
// ── Boot ───────────────────────────────────────────
markExistingPayloads();
console.log('🤖 Agent bridge v19.0 started (serial, robust cleanup, fixed matchAll).');
retryLoop();
commandLoop();
})();