Uploaded the Tampermonkey Harness
This commit is contained in:
parent
ace1e61285
commit
86515e46dd
928
GoogleAI_Search_Google_Gemini_Deepseek_ChatGPT_UnifiedHarness.js
Normal file
928
GoogleAI_Search_Google_Gemini_Deepseek_ChatGPT_UnifiedHarness.js
Normal file
|
|
@ -0,0 +1,928 @@
|
||||||
|
// ==UserScript==
|
||||||
|
// @name Unified AI C2 Harness
|
||||||
|
// @namespace http://tampermonkey.net/
|
||||||
|
// @version 1.3
|
||||||
|
// @description Unified C2 harness for Google AI Search, ChatGPT, DeepSeek, and Google Gemini.
|
||||||
|
// @author Vanguard
|
||||||
|
// @match https://*.google.com/*
|
||||||
|
// @match http://*.google.com/*
|
||||||
|
// @match https://chatgpt.com/*
|
||||||
|
// @match https://chat.openai.com/*
|
||||||
|
// @match https://chat.deepseek.com/*
|
||||||
|
// @match https://gemini.google.com/*
|
||||||
|
// @connect localhost
|
||||||
|
// @connect 127.0.0.1
|
||||||
|
// @grant GM_xmlhttpRequest
|
||||||
|
// @run-at document-idle
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
if (window.top !== window.self) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hostname = window.location.hostname;
|
||||||
|
let currentPlatform = null;
|
||||||
|
|
||||||
|
if (hostname.includes('chatgpt.com') || hostname.includes('chat.openai.com')) {
|
||||||
|
currentPlatform = 'ChatGPT';
|
||||||
|
} else if (hostname.includes('chat.deepseek.com')) {
|
||||||
|
currentPlatform = 'DeepSeek';
|
||||||
|
} else if (hostname.includes('gemini.google.com')) {
|
||||||
|
currentPlatform = 'Gemini';
|
||||||
|
} else if (hostname.includes('google.com')) {
|
||||||
|
currentPlatform = 'Google';
|
||||||
|
} else {
|
||||||
|
return; // Not a supported platform
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[Harness] Initializing Unified Harness for ${currentPlatform}...`);
|
||||||
|
|
||||||
|
const API_BASE = "http://localhost:8080/api/relay";
|
||||||
|
const POLLING_INTERVAL = 1000;
|
||||||
|
const STATE_INTERVAL = 2000;
|
||||||
|
|
||||||
|
let isBusy = false;
|
||||||
|
let isPolling = false;
|
||||||
|
let pollIntervalId = null;
|
||||||
|
let stateIntervalId = null;
|
||||||
|
|
||||||
|
let PLATFORM_NAME = "";
|
||||||
|
let ID_PREFIX = "";
|
||||||
|
let activeActions = {};
|
||||||
|
let activeTactics = null;
|
||||||
|
let reportStateLogic = null;
|
||||||
|
|
||||||
|
// --- TAMPERMONKEY FETCH WRAPPER (CORS BYPASS) ---
|
||||||
|
function tmFetch(url, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
GM_xmlhttpRequest({
|
||||||
|
method: options.method || 'GET',
|
||||||
|
url: url,
|
||||||
|
headers: options.headers || {},
|
||||||
|
data: options.body,
|
||||||
|
onload: function(response) {
|
||||||
|
resolve({
|
||||||
|
status: response.status,
|
||||||
|
json: () => {
|
||||||
|
try {
|
||||||
|
return Promise.resolve(JSON.parse(response.responseText));
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.resolve({});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text: () => Promise.resolve(response.responseText)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onerror: function(error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DOM TACTICS ---
|
||||||
|
const standardTactics = {
|
||||||
|
setNativeValue: (element, value) => {
|
||||||
|
element.focus();
|
||||||
|
let success = false;
|
||||||
|
try { success = document.execCommand('insertText', false, value); } catch (e) {}
|
||||||
|
if (!success) {
|
||||||
|
const valueSetter = Object.getOwnPropertyDescriptor(element, 'value')?.set;
|
||||||
|
const prototype = Object.getPrototypeOf(element);
|
||||||
|
const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value')?.set;
|
||||||
|
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
|
||||||
|
prototypeValueSetter.call(element, value);
|
||||||
|
} else if (valueSetter) {
|
||||||
|
valueSetter.call(element, value);
|
||||||
|
} else {
|
||||||
|
element.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
element.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
element.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const contentEditableTactics = {
|
||||||
|
setNativeValue: (element, value) => {
|
||||||
|
element.focus();
|
||||||
|
|
||||||
|
// Safely select only the contents of the target element
|
||||||
|
const selection = window.getSelection();
|
||||||
|
const range = document.createRange();
|
||||||
|
range.selectNodeContents(element);
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
|
||||||
|
try {
|
||||||
|
document.execCommand('delete', false, null);
|
||||||
|
document.execCommand('insertText', false, value);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[Harness] execCommand failed, falling back to textContent", e);
|
||||||
|
element.textContent = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
element.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
element.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- SMART EXTRACTION (Markdown Parsers) ---
|
||||||
|
function googleHtmlToMarkdown(node) {
|
||||||
|
if (node.nodeType === Node.TEXT_NODE) return node.textContent;
|
||||||
|
if (node.nodeType !== Node.ELEMENT_NODE) return "";
|
||||||
|
const tag = node.tagName.toUpperCase();
|
||||||
|
if (tag === 'BUTTON' || tag === 'SVG' || tag === 'STYLE' || tag === 'SCRIPT' || tag === 'NOSCRIPT') return "";
|
||||||
|
if (node.style && node.style.display === 'none') return "";
|
||||||
|
if (node.getAttribute('aria-hidden') === 'true') return "";
|
||||||
|
if (node.classList) {
|
||||||
|
if (node.classList.contains('DHPVt') || node.classList.contains('YHsVn') || node.classList.contains('bQ0Yzc') || node.classList.contains('CxFouc') || node.classList.contains('ZFcyjd') || node.classList.contains('lHqILb')) return "";
|
||||||
|
}
|
||||||
|
if (node.hasAttribute('data-xpm-latex')) return node.getAttribute('data-xpm-latex');
|
||||||
|
if (tag === 'PRE') return `\n\n\`\`\`\n${node.textContent}\n\`\`\`\n\n`;
|
||||||
|
if (tag === 'BR') return '\n';
|
||||||
|
let md = "";
|
||||||
|
for (let child of node.childNodes) md += googleHtmlToMarkdown(child);
|
||||||
|
if (tag === 'P' || tag === 'DIV') {
|
||||||
|
const displayStyle = node.style ? node.style.display : '';
|
||||||
|
if (displayStyle.includes('inline')) return md;
|
||||||
|
return `\n\n${md}\n\n`;
|
||||||
|
}
|
||||||
|
if (tag === 'STRONG' || tag === 'B') return `**${md}**`;
|
||||||
|
if (tag === 'EM' || tag === 'I') return `*${md}*`;
|
||||||
|
if (tag === 'CODE') return `\`${md}\``;
|
||||||
|
if (tag.match(/^H[1-6]$/)) return `\n\n${'#'.repeat(parseInt(tag[1]))} ${md}\n\n`;
|
||||||
|
if (tag === 'LI') return `\n- ${md.trim()}`;
|
||||||
|
if (tag === 'UL' || tag === 'OL') return `\n${md}\n`;
|
||||||
|
if (tag === 'A') return `[${md}](${node.getAttribute('href') || ''})`;
|
||||||
|
return md;
|
||||||
|
}
|
||||||
|
|
||||||
|
function chatgptHtmlToMarkdown(node) {
|
||||||
|
if (node.nodeType === Node.TEXT_NODE) return node.textContent;
|
||||||
|
if (node.nodeType !== Node.ELEMENT_NODE) return "";
|
||||||
|
const tag = node.tagName.toUpperCase();
|
||||||
|
if (tag === 'BUTTON' || tag === 'SVG' || tag === 'STYLE' || tag === 'SCRIPT' || tag === 'NOSCRIPT') return "";
|
||||||
|
if (node.style && node.style.display === 'none') return "";
|
||||||
|
if (node.getAttribute('aria-hidden') === 'true') return "";
|
||||||
|
if (tag === 'PRE') {
|
||||||
|
const codeNode = node.querySelector('code');
|
||||||
|
const codeText = codeNode ? codeNode.textContent : node.textContent;
|
||||||
|
return `\n\n\`\`\`\n${codeText}\n\`\`\`\n\n`;
|
||||||
|
}
|
||||||
|
if (tag === 'BR') return '\n';
|
||||||
|
let md = "";
|
||||||
|
for (let child of node.childNodes) md += chatgptHtmlToMarkdown(child);
|
||||||
|
if (tag === 'P' || tag === 'DIV') return `\n\n${md}\n\n`;
|
||||||
|
if (tag === 'STRONG' || tag === 'B') return `**${md}**`;
|
||||||
|
if (tag === 'EM' || tag === 'I') return `*${md}*`;
|
||||||
|
if (tag === 'CODE') return `\`${md}\``;
|
||||||
|
if (tag.match(/^H[1-6]$/)) return `\n\n${'#'.repeat(parseInt(tag[1]))} ${md}\n\n`;
|
||||||
|
if (tag === 'LI') return `\n- ${md.trim()}`;
|
||||||
|
if (tag === 'UL' || tag === 'OL') return `\n${md}\n`;
|
||||||
|
if (tag === 'A') {
|
||||||
|
const href = node.getAttribute('href') || '';
|
||||||
|
const cleanMd = md.trim();
|
||||||
|
if (cleanMd && (href.includes(cleanMd) || cleanMd.includes(href))) return md;
|
||||||
|
return `[${md}](${href})`;
|
||||||
|
}
|
||||||
|
return md;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepseekHtmlToMarkdown(node) {
|
||||||
|
if (node.nodeType === Node.TEXT_NODE) return node.textContent;
|
||||||
|
if (node.nodeType !== Node.ELEMENT_NODE) return "";
|
||||||
|
const tag = node.tagName.toUpperCase();
|
||||||
|
if (tag === 'BUTTON' || tag === 'SVG' || tag === 'STYLE' || tag === 'SCRIPT' || tag === 'NOSCRIPT') return "";
|
||||||
|
if (node.style && node.style.display === 'none') return "";
|
||||||
|
if (node.getAttribute('aria-hidden') === 'true') return "";
|
||||||
|
if (tag === 'PRE') return `\n\n\`\`\`\n${node.textContent}\n\`\`\`\n\n`;
|
||||||
|
if (tag === 'BR') return '\n';
|
||||||
|
let md = "";
|
||||||
|
for (let child of node.childNodes) md += deepseekHtmlToMarkdown(child);
|
||||||
|
if (tag === 'P' || tag === 'DIV') return `\n\n${md}\n\n`;
|
||||||
|
if (tag === 'STRONG' || tag === 'B') return `**${md}**`;
|
||||||
|
if (tag === 'EM' || tag === 'I') return `*${md}*`;
|
||||||
|
if (tag === 'CODE') return `\`${md}\``;
|
||||||
|
if (tag.match(/^H[1-6]$/)) return `\n\n${'#'.repeat(parseInt(tag[1]))} ${md}\n\n`;
|
||||||
|
if (tag === 'LI') return `\n- ${md.trim()}`;
|
||||||
|
if (tag === 'UL' || tag === 'OL') return `\n${md}\n`;
|
||||||
|
if (tag === 'A') {
|
||||||
|
const href = node.getAttribute('href') || '';
|
||||||
|
const cleanMd = md.trim();
|
||||||
|
if (cleanMd && (href.includes(cleanMd) || cleanMd.includes(href))) return md;
|
||||||
|
return `[${md}](${href})`;
|
||||||
|
}
|
||||||
|
return md;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ACTION MODULES ---
|
||||||
|
|
||||||
|
const googleActions = {
|
||||||
|
SHUTDOWN: async () => {
|
||||||
|
clearInterval(pollIntervalId);
|
||||||
|
clearInterval(stateIntervalId);
|
||||||
|
console.log("[Harness] Shutdown command received. Loops stopped.");
|
||||||
|
return "Harness shut down.";
|
||||||
|
},
|
||||||
|
GENERATE: async (payload) => {
|
||||||
|
const textareas = Array.from(document.querySelectorAll('textarea')).filter(el => el.offsetWidth > 0 && el.offsetHeight > 0);
|
||||||
|
let input = textareas[textareas.length - 1];
|
||||||
|
if (!input) input = document.querySelector('input[name="q"]');
|
||||||
|
if (!input) return "Error: Prompt textarea not found on this Google page. SGE may not be active.";
|
||||||
|
|
||||||
|
const preSubmitParagraphs = document.querySelectorAll('.n6owBd');
|
||||||
|
let preSubmitText = "";
|
||||||
|
let preSubmitNoResponseEl = null;
|
||||||
|
const noRespSelector = '.Y3BBE, .luHWlf, [data-sfc-root], [jscontroller="zcfIf"]';
|
||||||
|
|
||||||
|
if (preSubmitParagraphs.length > 0) {
|
||||||
|
preSubmitText = preSubmitParagraphs[preSubmitParagraphs.length - 1].parentElement.innerText || "";
|
||||||
|
} else {
|
||||||
|
const noRespNodes = document.querySelectorAll(noRespSelector);
|
||||||
|
for (let n of noRespNodes) {
|
||||||
|
if (n.innerText && n.innerText.includes("no response available")) {
|
||||||
|
preSubmitText = n.innerText;
|
||||||
|
preSubmitNoResponseEl = n;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
standardTactics.setNativeValue(input, payload);
|
||||||
|
await new Promise(r => setTimeout(r, 800));
|
||||||
|
|
||||||
|
const submitSelectors = ['button.SAvKK', 'button.qmJOdc', 'button[aria-label="Submit"]', 'button[aria-label="Search"]'];
|
||||||
|
let runBtn = null;
|
||||||
|
for(let sel of submitSelectors) {
|
||||||
|
const btns = Array.from(document.querySelectorAll(sel)).filter(b => b.offsetWidth > 0);
|
||||||
|
for(let btn of btns) {
|
||||||
|
if(!btn.disabled && btn.getAttribute('aria-disabled') !== 'true') {
|
||||||
|
runBtn = btn;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(runBtn) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runBtn) {
|
||||||
|
runBtn.click();
|
||||||
|
} else {
|
||||||
|
console.log("[Harness] Submit button not found or disabled, dispatching Enter key fallback...");
|
||||||
|
const enterEvent = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Enter', code: 'Enter', keyCode: 13 });
|
||||||
|
input.dispatchEvent(enterEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[Harness] Command sent. Entering Kinetic Wait (2500ms)...");
|
||||||
|
await new Promise(r => setTimeout(r, 2500));
|
||||||
|
console.log("[Harness] Wait complete. Engaging Semantic Lock...");
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let lastTextLength = -1;
|
||||||
|
let stabilityStreak = 0;
|
||||||
|
const REQUIRED_STREAK = 1;
|
||||||
|
let maxWait = 180;
|
||||||
|
let isNewResponseStarted = false;
|
||||||
|
|
||||||
|
const checkLoop = setInterval(() => {
|
||||||
|
maxWait--;
|
||||||
|
if (maxWait <= 0) {
|
||||||
|
clearInterval(checkLoop);
|
||||||
|
resolve("Error: Generation timed out or hung.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const noResponseNodes = document.querySelectorAll(noRespSelector);
|
||||||
|
let currentNoResponseEl = null;
|
||||||
|
let currentNoResponseText = "";
|
||||||
|
|
||||||
|
for (let node of noResponseNodes) {
|
||||||
|
if (node.innerText && node.innerText.includes("no response available")) {
|
||||||
|
currentNoResponseEl = node;
|
||||||
|
currentNoResponseText = node.innerText;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentNoResponseEl) {
|
||||||
|
if (!isNewResponseStarted) {
|
||||||
|
if (currentNoResponseText !== preSubmitText || currentNoResponseEl !== preSubmitNoResponseEl) {
|
||||||
|
isNewResponseStarted = true;
|
||||||
|
console.log("[Harness] Detected new 'no response' stream starting.");
|
||||||
|
} else {
|
||||||
|
console.log("[Harness] Waiting for old 'no response' to clear/change...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isNewResponseStarted) {
|
||||||
|
clearInterval(checkLoop);
|
||||||
|
console.log("[Harness] Detected 'no response available' message. Returning empty response.");
|
||||||
|
resolve("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const paragraphs = document.querySelectorAll('.n6owBd');
|
||||||
|
if (paragraphs.length === 0) {
|
||||||
|
console.log("[Harness] Waiting for SGE turn container...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastTurnContainer = paragraphs[paragraphs.length - 1].parentElement;
|
||||||
|
const currentTextContent = lastTurnContainer.innerText || "";
|
||||||
|
const currentTextLength = currentTextContent.length;
|
||||||
|
|
||||||
|
if (!isNewResponseStarted) {
|
||||||
|
if (currentTextContent !== preSubmitText || currentTextContent === "" || currentTextLength < preSubmitText.length) {
|
||||||
|
isNewResponseStarted = true;
|
||||||
|
console.log("[Harness] Detected new response stream starting.");
|
||||||
|
} else {
|
||||||
|
console.log("[Harness] Waiting for old response to clear/change...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTextGrowing = currentTextLength > lastTextLength;
|
||||||
|
|
||||||
|
if (isTextGrowing) {
|
||||||
|
console.log(`[Harness] Status: Streaming... (+${currentTextLength - lastTextLength})`);
|
||||||
|
lastTextLength = currentTextLength;
|
||||||
|
stabilityStreak = 0;
|
||||||
|
} else if (currentTextLength === 0) {
|
||||||
|
console.log(`[Harness] Status: Waiting for text generation to begin...`);
|
||||||
|
stabilityStreak = 0;
|
||||||
|
} else {
|
||||||
|
stabilityStreak++;
|
||||||
|
console.log(`[Harness] Status: Stable... (${stabilityStreak}/${REQUIRED_STREAK})`);
|
||||||
|
lastTextLength = currentTextLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stabilityStreak >= REQUIRED_STREAK) {
|
||||||
|
clearInterval(checkLoop);
|
||||||
|
console.log("[Harness] Lock Released. Extraction initiated.");
|
||||||
|
let turnMd = googleHtmlToMarkdown(lastTurnContainer);
|
||||||
|
turnMd = turnMd.replace(/\n{3,}/g, '\n\n').trim();
|
||||||
|
resolve(turnMd);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
NEW_CHAT: async () => {
|
||||||
|
const newThreadBtn = document.querySelector('button[aria-label="New thread"]') || document.querySelector('.cV1Mfc');
|
||||||
|
if (newThreadBtn) {
|
||||||
|
newThreadBtn.click();
|
||||||
|
return "Chat reset.";
|
||||||
|
}
|
||||||
|
return "Error: New thread button not found.";
|
||||||
|
},
|
||||||
|
GET_CHAT_HISTORY: async () => {
|
||||||
|
const history = [];
|
||||||
|
const turns = document.querySelectorAll('.VndcI, .n6owBd');
|
||||||
|
turns.forEach(turn => {
|
||||||
|
if (turn.classList.contains('VndcI')) {
|
||||||
|
let text = turn.innerText.replace('You said:', '').trim();
|
||||||
|
if (text) history.push({ role: "User", text: text });
|
||||||
|
} else if (turn.classList.contains('n6owBd')) {
|
||||||
|
let text = googleHtmlToMarkdown(turn).trim();
|
||||||
|
if (text) history.push({ role: "Model", text: text });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return JSON.stringify(history);
|
||||||
|
},
|
||||||
|
ANALYZE_SESSION: async () => {
|
||||||
|
return JSON.stringify({
|
||||||
|
isHydrated: document.querySelectorAll('.n6owBd').length > 0,
|
||||||
|
turnCount: document.querySelectorAll('.n6owBd').length,
|
||||||
|
url: window.location.href
|
||||||
|
});
|
||||||
|
},
|
||||||
|
GET_SETTINGS: async () => "{}",
|
||||||
|
SET_SETTINGS: async () => "Success: Ignored in Google AI Search Mode",
|
||||||
|
SET_URL_CONTEXT: async () => "Success: Ignored in Google AI Search Mode",
|
||||||
|
SET_RESOLUTION: async () => "Success: Ignored in Google AI Search Mode",
|
||||||
|
DELETE_TURN: async () => "Success: Ignored in Google AI Search Mode",
|
||||||
|
CREATE_SHARD: async () => "Error: Context Sharding is not implemented for AI Search Mode",
|
||||||
|
INJECT_SHARD: async () => "Error: Context Sharding is not implemented for AI Search Mode",
|
||||||
|
UPLOAD_MEDIA: async () => "Error: Media Upload is not currently supported in AI Search Mode",
|
||||||
|
INJECT_DOC: async () => "Error: Doc Injection is not currently supported in AI Search Mode",
|
||||||
|
SCRAPE_HISTORY: async () => "[]"
|
||||||
|
};
|
||||||
|
|
||||||
|
const chatgptActions = {
|
||||||
|
SHUTDOWN: async () => {
|
||||||
|
clearInterval(pollIntervalId);
|
||||||
|
clearInterval(stateIntervalId);
|
||||||
|
console.log("[Harness] Shutdown command received. Loops stopped.");
|
||||||
|
return "Harness shut down.";
|
||||||
|
},
|
||||||
|
GENERATE: async (payload) => {
|
||||||
|
const input = document.querySelector('#prompt-textarea');
|
||||||
|
if (!input) return "Error: Prompt textarea not found on ChatGPT.";
|
||||||
|
|
||||||
|
const preSubmitResponses = document.querySelectorAll('[data-message-author-role="assistant"]');
|
||||||
|
const preSubmitCount = preSubmitResponses.length;
|
||||||
|
|
||||||
|
activeTactics.setNativeValue(input, payload);
|
||||||
|
await new Promise(r => setTimeout(r, 500));
|
||||||
|
|
||||||
|
const sendBtn = document.querySelector('[data-testid="send-button"]');
|
||||||
|
if (sendBtn && !sendBtn.disabled) {
|
||||||
|
sendBtn.click();
|
||||||
|
} else {
|
||||||
|
console.log("[Harness] Submit button not found or disabled, dispatching Enter key fallback...");
|
||||||
|
const enterEvent = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Enter', code: 'Enter', keyCode: 13 });
|
||||||
|
input.dispatchEvent(enterEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[Harness] Command sent. Entering Kinetic Wait (2000ms)...");
|
||||||
|
await new Promise(r => setTimeout(r, 2000));
|
||||||
|
console.log("[Harness] Wait complete. Engaging Semantic Lock...");
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let lastTextLength = -1;
|
||||||
|
let stabilityStreak = 0;
|
||||||
|
const REQUIRED_STREAK = 2;
|
||||||
|
let maxWait = 300;
|
||||||
|
|
||||||
|
const checkLoop = setInterval(() => {
|
||||||
|
maxWait--;
|
||||||
|
if (maxWait <= 0) {
|
||||||
|
clearInterval(checkLoop);
|
||||||
|
resolve("Error: Generation timed out or hung.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const responses = document.querySelectorAll('[data-message-author-role="assistant"]');
|
||||||
|
if (responses.length <= preSubmitCount && responses.length !== 0) {
|
||||||
|
console.log("[Harness] Waiting for new ChatGPT response container...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastResponse = responses[responses.length - 1];
|
||||||
|
if (!lastResponse) return;
|
||||||
|
|
||||||
|
const markdownContainer = lastResponse.querySelector('.markdown') || lastResponse;
|
||||||
|
const currentTextContent = markdownContainer.innerText || "";
|
||||||
|
const currentTextLength = currentTextContent.length;
|
||||||
|
const isTextGrowing = currentTextLength > lastTextLength;
|
||||||
|
|
||||||
|
if (isTextGrowing) {
|
||||||
|
console.log(`[Harness] Status: Streaming... (+${currentTextLength - lastTextLength})`);
|
||||||
|
lastTextLength = currentTextLength;
|
||||||
|
stabilityStreak = 0;
|
||||||
|
} else if (currentTextLength === 0) {
|
||||||
|
console.log(`[Harness] Status: Waiting for text generation to begin...`);
|
||||||
|
stabilityStreak = 0;
|
||||||
|
} else {
|
||||||
|
stabilityStreak++;
|
||||||
|
console.log(`[Harness] Status: Stable... (${stabilityStreak}/${REQUIRED_STREAK})`);
|
||||||
|
lastTextLength = currentTextLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stabilityStreak >= REQUIRED_STREAK) {
|
||||||
|
clearInterval(checkLoop);
|
||||||
|
console.log("[Harness] Lock Released. Extraction initiated.");
|
||||||
|
let turnMd = chatgptHtmlToMarkdown(markdownContainer);
|
||||||
|
turnMd = turnMd.replace(/\n{3,}/g, '\n\n').trim();
|
||||||
|
resolve(turnMd);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
NEW_CHAT: async () => {
|
||||||
|
const newChatBtn = document.querySelector('[data-testid="create-new-chat-button"]');
|
||||||
|
if (newChatBtn) {
|
||||||
|
newChatBtn.click();
|
||||||
|
return "Chat reset.";
|
||||||
|
}
|
||||||
|
return "Error: New chat button not found.";
|
||||||
|
},
|
||||||
|
GET_CHAT_HISTORY: async () => {
|
||||||
|
const history = [];
|
||||||
|
const messages = document.querySelectorAll('[data-message-author-role]');
|
||||||
|
messages.forEach(msg => {
|
||||||
|
const role = msg.getAttribute('data-message-author-role');
|
||||||
|
if (role === 'assistant') {
|
||||||
|
const markdownContainer = msg.querySelector('.markdown') || msg;
|
||||||
|
let text = chatgptHtmlToMarkdown(markdownContainer).trim();
|
||||||
|
if (text) history.push({ role: "Model", text: text });
|
||||||
|
} else if (role === 'user') {
|
||||||
|
let text = msg.innerText.trim();
|
||||||
|
if (text) history.push({ role: "User", text: text });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return JSON.stringify(history);
|
||||||
|
},
|
||||||
|
ANALYZE_SESSION: async () => {
|
||||||
|
return JSON.stringify({
|
||||||
|
isHydrated: document.querySelectorAll('[data-message-author-role="assistant"]').length > 0,
|
||||||
|
turnCount: document.querySelectorAll('[data-message-author-role="assistant"]').length,
|
||||||
|
url: window.location.href
|
||||||
|
});
|
||||||
|
},
|
||||||
|
GET_SETTINGS: async () => "{}",
|
||||||
|
SET_SETTINGS: async () => "Success: Ignored in ChatGPT Mode",
|
||||||
|
SET_URL_CONTEXT: async () => "Success: Ignored in ChatGPT Mode",
|
||||||
|
SET_RESOLUTION: async () => "Success: Ignored in ChatGPT Mode",
|
||||||
|
DELETE_TURN: async () => "Success: Ignored in ChatGPT Mode",
|
||||||
|
CREATE_SHARD: async () => "Error: Context Sharding is not implemented for ChatGPT Mode",
|
||||||
|
INJECT_SHARD: async () => "Error: Context Sharding is not implemented for ChatGPT Mode",
|
||||||
|
UPLOAD_MEDIA: async () => "Error: Media Upload is not currently supported in ChatGPT Mode",
|
||||||
|
INJECT_DOC: async () => "Error: Doc Injection is not currently supported in ChatGPT Mode",
|
||||||
|
SCRAPE_HISTORY: async () => "[]"
|
||||||
|
};
|
||||||
|
|
||||||
|
const deepseekActions = {
|
||||||
|
SHUTDOWN: async () => {
|
||||||
|
clearInterval(pollIntervalId);
|
||||||
|
clearInterval(stateIntervalId);
|
||||||
|
console.log("[Harness] Shutdown command received. Loops stopped.");
|
||||||
|
return "Harness shut down.";
|
||||||
|
},
|
||||||
|
GENERATE: async (payload) => {
|
||||||
|
let input = document.querySelector('textarea[placeholder="Message DeepSeek"]');
|
||||||
|
if (!input) input = document.querySelector('textarea');
|
||||||
|
if (!input) return "Error: Prompt textarea not found on DeepSeek.";
|
||||||
|
|
||||||
|
const preSubmitResponses = document.querySelectorAll('.ds-assistant-message-main-content');
|
||||||
|
const preSubmitCount = preSubmitResponses.length;
|
||||||
|
|
||||||
|
activeTactics.setNativeValue(input, payload);
|
||||||
|
await new Promise(r => setTimeout(r, 800));
|
||||||
|
|
||||||
|
const sendBtn = document.querySelector('div[role="button"].ds-button--primary');
|
||||||
|
if (sendBtn && !sendBtn.classList.contains('ds-button--disabled')) {
|
||||||
|
sendBtn.click();
|
||||||
|
} else {
|
||||||
|
console.log("[Harness] Submit button not found or disabled, dispatching Enter key fallback...");
|
||||||
|
const enterEvent = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Enter', code: 'Enter', keyCode: 13 });
|
||||||
|
input.dispatchEvent(enterEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[Harness] Command sent. Entering Kinetic Wait (2500ms)...");
|
||||||
|
await new Promise(r => setTimeout(r, 2500));
|
||||||
|
console.log("[Harness] Wait complete. Engaging Semantic Lock...");
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let lastTextLength = -1;
|
||||||
|
let stabilityStreak = 0;
|
||||||
|
const REQUIRED_STREAK = 3;
|
||||||
|
let maxWait = 600;
|
||||||
|
|
||||||
|
const checkLoop = setInterval(() => {
|
||||||
|
maxWait--;
|
||||||
|
if (maxWait <= 0) {
|
||||||
|
clearInterval(checkLoop);
|
||||||
|
resolve("Error: Generation timed out or hung.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const responses = document.querySelectorAll('.ds-assistant-message-main-content');
|
||||||
|
if (responses.length <= preSubmitCount && responses.length !== 0) {
|
||||||
|
console.log("[Harness] Waiting for new DeepSeek response container...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastResponse = responses[responses.length - 1];
|
||||||
|
if (!lastResponse) return;
|
||||||
|
|
||||||
|
const currentTextContent = lastResponse.innerText || "";
|
||||||
|
const currentTextLength = currentTextContent.length;
|
||||||
|
const isTextGrowing = currentTextLength > lastTextLength;
|
||||||
|
|
||||||
|
if (isTextGrowing) {
|
||||||
|
console.log(`[Harness] Status: Streaming... (+${currentTextLength - lastTextLength})`);
|
||||||
|
lastTextLength = currentTextLength;
|
||||||
|
stabilityStreak = 0;
|
||||||
|
} else if (currentTextLength === 0) {
|
||||||
|
console.log(`[Harness] Status: Thinking / Waiting for text generation to begin...`);
|
||||||
|
stabilityStreak = 0;
|
||||||
|
} else {
|
||||||
|
stabilityStreak++;
|
||||||
|
console.log(`[Harness] Status: Stable... (${stabilityStreak}/${REQUIRED_STREAK})`);
|
||||||
|
lastTextLength = currentTextLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stabilityStreak >= REQUIRED_STREAK) {
|
||||||
|
clearInterval(checkLoop);
|
||||||
|
console.log("[Harness] Lock Released. Extraction initiated.");
|
||||||
|
let turnMd = deepseekHtmlToMarkdown(lastResponse);
|
||||||
|
turnMd = turnMd.replace(/\n{3,}/g, '\n\n').trim();
|
||||||
|
resolve(turnMd);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
NEW_CHAT: async () => {
|
||||||
|
const spans = Array.from(document.querySelectorAll('span'));
|
||||||
|
const newChatSpan = spans.find(el => el.innerText.trim() === 'New chat');
|
||||||
|
if (newChatSpan && newChatSpan.parentElement) {
|
||||||
|
newChatSpan.parentElement.click();
|
||||||
|
return "Chat reset.";
|
||||||
|
}
|
||||||
|
return "Error: New chat button not found.";
|
||||||
|
},
|
||||||
|
GET_CHAT_HISTORY: async () => {
|
||||||
|
const history = [];
|
||||||
|
const messages = document.querySelectorAll('.ds-message');
|
||||||
|
messages.forEach(msg => {
|
||||||
|
const assistantContent = msg.querySelector('.ds-assistant-message-main-content');
|
||||||
|
if (assistantContent) {
|
||||||
|
let text = deepseekHtmlToMarkdown(assistantContent).trim();
|
||||||
|
if (text) history.push({ role: "Model", text: text });
|
||||||
|
} else {
|
||||||
|
let text = msg.innerText.trim();
|
||||||
|
if (text) history.push({ role: "User", text: text });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return JSON.stringify(history);
|
||||||
|
},
|
||||||
|
ANALYZE_SESSION: async () => {
|
||||||
|
return JSON.stringify({
|
||||||
|
isHydrated: document.querySelectorAll('.ds-assistant-message-main-content').length > 0,
|
||||||
|
turnCount: document.querySelectorAll('.ds-assistant-message-main-content').length,
|
||||||
|
url: window.location.href
|
||||||
|
});
|
||||||
|
},
|
||||||
|
GET_SETTINGS: async () => "{}",
|
||||||
|
SET_SETTINGS: async () => "Success: Ignored in DeepSeek Mode",
|
||||||
|
SET_URL_CONTEXT: async () => "Success: Ignored in DeepSeek Mode",
|
||||||
|
SET_RESOLUTION: async () => "Success: Ignored in DeepSeek Mode",
|
||||||
|
DELETE_TURN: async () => "Success: Ignored in DeepSeek Mode",
|
||||||
|
CREATE_SHARD: async () => "Error: Context Sharding is not implemented for DeepSeek Mode",
|
||||||
|
INJECT_SHARD: async () => "Error: Context Sharding is not implemented for DeepSeek Mode",
|
||||||
|
UPLOAD_MEDIA: async () => "Error: Media Upload is not currently supported in DeepSeek Mode",
|
||||||
|
INJECT_DOC: async () => "Error: Doc Injection is not currently supported in DeepSeek Mode",
|
||||||
|
SCRAPE_HISTORY: async () => "[]"
|
||||||
|
};
|
||||||
|
|
||||||
|
const geminiActions = {
|
||||||
|
SHUTDOWN: async () => {
|
||||||
|
clearInterval(pollIntervalId);
|
||||||
|
clearInterval(stateIntervalId);
|
||||||
|
console.log("[Harness] Shutdown command received. Loops stopped.");
|
||||||
|
return "Harness shut down.";
|
||||||
|
},
|
||||||
|
GENERATE: async (payload) => {
|
||||||
|
|
||||||
|
// Helper to dismiss A/B testing dialogs that block the UI
|
||||||
|
const dismissABTest = () => {
|
||||||
|
const titles = document.querySelectorAll('span.title');
|
||||||
|
for (let title of titles) {
|
||||||
|
if (title.textContent && title.textContent.includes('Which response is more helpful?')) {
|
||||||
|
const container = title.closest('.container');
|
||||||
|
if (container) {
|
||||||
|
const closeBtn = container.querySelector('button[aria-label="Close"]');
|
||||||
|
if (closeBtn) {
|
||||||
|
console.log("[Harness] Dismissing A/B test dialog...");
|
||||||
|
closeBtn.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dismissABTest();
|
||||||
|
|
||||||
|
const input = document.querySelector('.ql-editor[contenteditable="true"]');
|
||||||
|
if (!input) return "Error: Prompt textarea not found on Gemini.";
|
||||||
|
|
||||||
|
const preSubmitResponses = document.querySelectorAll('model-response');
|
||||||
|
const preSubmitCount = preSubmitResponses.length;
|
||||||
|
|
||||||
|
activeTactics.setNativeValue(input, payload);
|
||||||
|
await new Promise(r => setTimeout(r, 800));
|
||||||
|
|
||||||
|
const sendBtn = document.querySelector('button[aria-label="Send message"]');
|
||||||
|
if (sendBtn && !sendBtn.disabled && sendBtn.getAttribute('aria-disabled') !== 'true') {
|
||||||
|
sendBtn.click();
|
||||||
|
} else {
|
||||||
|
console.log("[Harness] Submit button not found or disabled, dispatching Enter key fallback...");
|
||||||
|
const enterEvent = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Enter', code: 'Enter', keyCode: 13 });
|
||||||
|
input.dispatchEvent(enterEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[Harness] Command sent. Entering Kinetic Wait (2000ms)...");
|
||||||
|
await new Promise(r => setTimeout(r, 2000));
|
||||||
|
console.log("[Harness] Wait complete. Engaging Semantic Lock...");
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let lastTextLength = -1;
|
||||||
|
let stabilityStreak = 0;
|
||||||
|
const REQUIRED_STREAK = 3;
|
||||||
|
let maxWait = 300;
|
||||||
|
|
||||||
|
const checkLoop = setInterval(() => {
|
||||||
|
maxWait--;
|
||||||
|
if (maxWait <= 0) {
|
||||||
|
clearInterval(checkLoop);
|
||||||
|
resolve("Error: Generation timed out or hung.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dismissABTest();
|
||||||
|
|
||||||
|
const responses = document.querySelectorAll('model-response');
|
||||||
|
if (responses.length <= preSubmitCount && responses.length !== 0) {
|
||||||
|
console.log("[Harness] Waiting for new Gemini response container...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastResponse = responses[responses.length - 1];
|
||||||
|
if (!lastResponse) return;
|
||||||
|
|
||||||
|
const markdownContainer = lastResponse.querySelector('.markdown') || lastResponse;
|
||||||
|
const currentTextContent = markdownContainer.innerText || "";
|
||||||
|
const currentTextLength = currentTextContent.length;
|
||||||
|
const isTextGrowing = currentTextLength > lastTextLength;
|
||||||
|
|
||||||
|
// Gemini uses aria-busy to indicate active generation
|
||||||
|
const isBusy = markdownContainer.getAttribute('aria-busy') === 'true' || lastResponse.getAttribute('aria-busy') === 'true';
|
||||||
|
|
||||||
|
if (isTextGrowing || isBusy) {
|
||||||
|
console.log(`[Harness] Status: Streaming... (+${currentTextLength - lastTextLength})`);
|
||||||
|
lastTextLength = currentTextLength;
|
||||||
|
stabilityStreak = 0;
|
||||||
|
} else if (currentTextLength === 0) {
|
||||||
|
console.log(`[Harness] Status: Waiting for text generation to begin...`);
|
||||||
|
stabilityStreak = 0;
|
||||||
|
} else {
|
||||||
|
stabilityStreak++;
|
||||||
|
console.log(`[Harness] Status: Stable... (${stabilityStreak}/${REQUIRED_STREAK})`);
|
||||||
|
lastTextLength = currentTextLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stabilityStreak >= REQUIRED_STREAK && !isBusy) {
|
||||||
|
clearInterval(checkLoop);
|
||||||
|
console.log("[Harness] Lock Released. Extraction initiated.");
|
||||||
|
let turnMd = googleHtmlToMarkdown(markdownContainer);
|
||||||
|
turnMd = turnMd.replace(/\n{3,}/g, '\n\n').trim();
|
||||||
|
resolve(turnMd);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
NEW_CHAT: async () => {
|
||||||
|
const newChatBtn = document.querySelector('a[aria-label="New chat"]') || document.querySelector('a[href="/app"]');
|
||||||
|
if (newChatBtn) {
|
||||||
|
newChatBtn.click();
|
||||||
|
return "Chat reset.";
|
||||||
|
}
|
||||||
|
return "Error: New chat button not found.";
|
||||||
|
},
|
||||||
|
GET_CHAT_HISTORY: async () => {
|
||||||
|
const history = [];
|
||||||
|
const turns = document.querySelectorAll('user-query, model-response');
|
||||||
|
turns.forEach(turn => {
|
||||||
|
if (turn.tagName.toLowerCase() === 'USER-QUERY') {
|
||||||
|
const textEl = turn.querySelector('.query-text');
|
||||||
|
if (textEl) {
|
||||||
|
let text = textEl.textContent.replace('You said', '').trim();
|
||||||
|
if (text) history.push({ role: "User", text: text });
|
||||||
|
}
|
||||||
|
} else if (turn.tagName.toLowerCase() === 'MODEL-RESPONSE') {
|
||||||
|
const markdownContainer = turn.querySelector('.markdown');
|
||||||
|
if (markdownContainer) {
|
||||||
|
let text = googleHtmlToMarkdown(markdownContainer).trim();
|
||||||
|
if (text) history.push({ role: "Model", text: text });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return JSON.stringify(history);
|
||||||
|
},
|
||||||
|
ANALYZE_SESSION: async () => {
|
||||||
|
return JSON.stringify({
|
||||||
|
isHydrated: document.querySelectorAll('model-response').length > 0,
|
||||||
|
turnCount: document.querySelectorAll('model-response').length,
|
||||||
|
url: window.location.href
|
||||||
|
});
|
||||||
|
},
|
||||||
|
GET_SETTINGS: async () => "{}",
|
||||||
|
SET_SETTINGS: async () => "Success: Ignored in Gemini Mode",
|
||||||
|
SET_URL_CONTEXT: async () => "Success: Ignored in Gemini Mode",
|
||||||
|
SET_RESOLUTION: async () => "Success: Ignored in Gemini Mode",
|
||||||
|
DELETE_TURN: async () => "Success: Ignored in Gemini Mode",
|
||||||
|
CREATE_SHARD: async () => "Error: Context Sharding is not implemented for Gemini Mode",
|
||||||
|
INJECT_SHARD: async () => "Error: Context Sharding is not implemented for Gemini Mode",
|
||||||
|
UPLOAD_MEDIA: async () => "Error: Media Upload is not currently supported in Gemini Mode",
|
||||||
|
INJECT_DOC: async () => "Error: Doc Injection is not currently supported in Gemini Mode",
|
||||||
|
SCRAPE_HISTORY: async () => "[]"
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- PLATFORM ASSIGNMENT ---
|
||||||
|
if (currentPlatform === 'Google') {
|
||||||
|
PLATFORM_NAME = "Google AI Search";
|
||||||
|
ID_PREFIX = "inst_gai_";
|
||||||
|
activeActions = googleActions;
|
||||||
|
activeTactics = standardTactics;
|
||||||
|
reportStateLogic = () => document.querySelectorAll('.n6owBd').length;
|
||||||
|
} else if (currentPlatform === 'ChatGPT') {
|
||||||
|
PLATFORM_NAME = "ChatGPT";
|
||||||
|
ID_PREFIX = "inst_cgpt_";
|
||||||
|
activeActions = chatgptActions;
|
||||||
|
activeTactics = contentEditableTactics;
|
||||||
|
reportStateLogic = () => document.querySelectorAll('[data-message-author-role="assistant"]').length;
|
||||||
|
} else if (currentPlatform === 'DeepSeek') {
|
||||||
|
PLATFORM_NAME = "DeepSeek";
|
||||||
|
ID_PREFIX = "inst_ds_";
|
||||||
|
activeActions = deepseekActions;
|
||||||
|
activeTactics = standardTactics;
|
||||||
|
reportStateLogic = () => document.querySelectorAll('.ds-assistant-message-main-content').length;
|
||||||
|
} else if (currentPlatform === 'Gemini') {
|
||||||
|
PLATFORM_NAME = "Google Gemini";
|
||||||
|
ID_PREFIX = "inst_gem_";
|
||||||
|
activeActions = geminiActions;
|
||||||
|
activeTactics = contentEditableTactics;
|
||||||
|
reportStateLogic = () => document.querySelectorAll('model-response').length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- INSTANCE IDENTITY ---
|
||||||
|
function getInstanceId() {
|
||||||
|
let id = sessionStorage.getItem('tm_instance_id');
|
||||||
|
if (!id || !id.startsWith(ID_PREFIX)) {
|
||||||
|
id = ID_PREFIX + Math.random().toString(36).substr(2, 9) + '_' + Date.now();
|
||||||
|
sessionStorage.setItem('tm_instance_id', id);
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
const INSTANCE_ID = getInstanceId();
|
||||||
|
console.log(`[Harness] Bound to Instance ID: ${INSTANCE_ID}`);
|
||||||
|
|
||||||
|
// --- MAIN LOOP & NETWORKING ---
|
||||||
|
async function processCommand(cmdData) {
|
||||||
|
const { type, payload } = cmdData.command;
|
||||||
|
console.log(`[Harness] Processing: ${type}`, payload);
|
||||||
|
|
||||||
|
let result = "";
|
||||||
|
try {
|
||||||
|
if (activeActions[type]) {
|
||||||
|
result = await activeActions[type](payload);
|
||||||
|
} else {
|
||||||
|
result = "Unknown command type";
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Execution failed:", e);
|
||||||
|
result = `Error: ${e.message}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[Harness] Result for ${type}:`, result);
|
||||||
|
await sendResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendResult(data) {
|
||||||
|
try {
|
||||||
|
await tmFetch(`${API_BASE}/result/${INSTANCE_ID}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ timestamp: Date.now(), output: data })
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Relay failed:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function poll() {
|
||||||
|
if (isBusy || isPolling) return;
|
||||||
|
isPolling = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await tmFetch(`${API_BASE}/command/${INSTANCE_ID}`);
|
||||||
|
if (res.status === 200) {
|
||||||
|
const data = await res.json();
|
||||||
|
if (data && data.command) {
|
||||||
|
isBusy = true;
|
||||||
|
await processCommand(data);
|
||||||
|
isBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Network error or timeout, safely ignored.
|
||||||
|
} finally {
|
||||||
|
isPolling = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reportState() {
|
||||||
|
if (isBusy) return;
|
||||||
|
const turnCount = reportStateLogic();
|
||||||
|
tmFetch(`${API_BASE}/state/${INSTANCE_ID}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
url: window.location.href,
|
||||||
|
turn_count: turnCount,
|
||||||
|
is_busy: isBusy,
|
||||||
|
platform: PLATFORM_NAME
|
||||||
|
})
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
pollIntervalId = setInterval(poll, POLLING_INTERVAL);
|
||||||
|
stateIntervalId = setInterval(reportState, STATE_INTERVAL);
|
||||||
|
|
||||||
|
console.log(`[Harness] ${PLATFORM_NAME} Active & Polling on Instance ${INSTANCE_ID}.`);
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
@ -27,7 +27,7 @@ The Voice Assistant PoC requires an offline voice model to function.
|
||||||
|
|
||||||
### 2. Install the Tampermonkey Harness
|
### 2. Install the Tampermonkey Harness
|
||||||
1. Open your browser and click the Tampermonkey extension icon -> **Create a new script**.
|
1. Open your browser and click the Tampermonkey extension icon -> **Create a new script**.
|
||||||
2. Copy the entire contents of `GoogleAI_Search_Deepseek_ChatGPT_UnifiedHarness.js` from this repository.
|
2. Copy the entire contents of `GoogleAI_Search_Google_Gemini_Deepseek_ChatGPT_UnifiedHarness.js` from this repository.
|
||||||
3. Paste it into the Tampermonkey editor, overwriting the default template.
|
3. Paste it into the Tampermonkey editor, overwriting the default template.
|
||||||
4. Go to File -> **Save** (or press Ctrl+S).
|
4. Go to File -> **Save** (or press Ctrl+S).
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user