You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

298 lines
9.4 KiB

"use strict";
(function () {
var SEL = globalThis.DSH_SELECTORS;
var BRIDGE = globalThis.DSH_BRIDGE;
var extractCalls = null;
if (typeof globalThis.extractCalls === "function") {
extractCalls = globalThis.extractCalls;
}
var UI = globalThis.DSHMEM || null;
var canStart = false; var started = false;
var lastProcessedKey = '';
var lastSentAssistantKey = '';
var seenCallsHashes = new Set();
var CALL_HASH_CAP = 100;
if (UI) {
UI.onResetLoops(function () { queue.length = 0; pending.clear(); seenHashes.clear(); pumping = false; if (UI) UI.log('loops reset'); });
UI.onStop(function () { queue.length = 0; if (UI) UI.log('stopped'); });
UI.onSaveToken(function (v) { chrome.storage.local.set({ 'dshmem-bridge-token': v }, function () { if (UI) UI.log('token saved, reload page'); }); });
}
if (UI) { UI.onStart(function () { if (!canStart) return; started = true; markExistingAsProcessed(); obs.observe(document.documentElement, { childList: true, subtree: true, characterData: true }); UI.setPhase('idle'); UI.setConnected(true); UI.log('started'); }); }
if (!SEL) return;
if (!BRIDGE) return;
if (typeof extractCalls !== "function") return;
var DEBOUNCE_MS = 800;
var processed = new WeakSet();
var seenHashes = new Map();
var HASH_CAP = 50;
var queue = [];
var pumping = false;
var timer = null;
var pending = new Set();
function hashText(s) {
var h = 5381;
var i;
for (i = 0; i < s.length; i++) {
h = ((h << 5) + h + s.charCodeAt(i)) >>> 0;
}
return h.toString(36) + ":" + s.length;
}
function rememberHash(h) {
seenHashes.set(h, true);
if (seenHashes.size > HASH_CAP) {
var first = seenHashes.keys().next().value;
seenHashes.delete(first);
}
}
function markExistingAsProcessed() {
var sels = SEL.MESSAGE_SELECTORS;
for (var i = 0; i < sels.length; i++) {
try {
var all = document.querySelectorAll(sels[i]);
for (var j = 0; j < all.length; j++) {
var el = all[j];
processed.add(el);
try {
var t = SEL.collectText(el);
if (t && t.length > 0) rememberHash(hashText(t));
} catch (e2) {}
}
} catch (e) {}
}
}
function rememberCallsHash(h) {
seenCallsHashes.add(h);
if (seenCallsHashes.size > CALL_HASH_CAP) {
var first = seenCallsHashes.values().next().value;
seenCallsHashes.delete(first);
}
}
function keyOf(el) {
if (!el) return '';
var t = el.textContent || el.innerText || '';
return t.length + ':' + t.slice(-64);
}
function lastVisible(list) {
if (!list) return null;
for (var i = 0; i < list.length; i++) {
try {
var all = document.querySelectorAll(list[i]);
for (var j = all.length - 1; j >= 0; j--) {
var el = all[j];
var r = el.getBoundingClientRect();
if (r.width > 0 || r.height > 0) return el;
}
} catch (e) {}
}
return null;
}
function findLastAssistant() { return lastVisible(SEL && SEL.assistantMarkdown); }
function findInput() { return lastVisible(SEL && SEL.input); }
function findSendButton() { return lastVisible(SEL && SEL.sendButton); }
function sleep(ms) { return new Promise(function (r) { setTimeout(r, ms); }); }
function setNativeValue(el, text) {
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
var proto = el.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
var setter = Object.getOwnPropertyDescriptor(proto, 'value').set;
setter.call(el, text);
el.dispatchEvent(new Event('input', { bubbles: true }));
} else {
el.focus();
var ok = false;
try { ok = document.execCommand('insertText', false, text); } catch (e) { ok = false; }
if (!ok) {
el.textContent = text;
el.dispatchEvent(new InputEvent('beforeinput', { bubbles: true, cancelable: true, inputType: 'insertText', data: text }));
el.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'insertText', data: text }));
}
}
}
function sendText(text) {
var input = findInput();
if (!input) { console.warn('[dsh-memory] input not found'); return Promise.resolve(false); }
var la = findLastAssistant();
if (la) lastSentAssistantKey = keyOf(la);
setNativeValue(input, text);
return sleep(150).then(function () {
var btn = findSendButton();
if (btn && !btn.disabled && btn.getAttribute('aria-disabled') !== 'true') {
btn.click();
} else {
input.focus();
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
}
return sleep(600);
}).then(function () { return true; });
}
function buildResultMessage(results) {
var BT3 = String.fromCharCode(96) + String.fromCharCode(96) + String.fromCharCode(96);
var blocks = results.map(function (r) { return BT3 + 'dsh-result' + String.fromCharCode(10) + JSON.stringify(r) + String.fromCharCode(10) + BT3; }).join(String.fromCharCode(10) + String.fromCharCode(10));
var remind = '[dsh-memory] continue. When done output on its own line: DSH:DONE:summary.';
return '[dsh-memory tool result]' + String.fromCharCode(10) + String.fromCharCode(10) + blocks + String.fromCharCode(10) + String.fromCharCode(10) + remind;
}
function findMessages(node) {
var out = [];
if (!node) return out;
var i;
var j;
for (i = 0; i < SEL.MESSAGE_SELECTORS.length; i++) {
var sel = SEL.MESSAGE_SELECTORS[i];
if (node.matches) {
if (node.matches(sel)) out.push(node);
}
if (node.querySelectorAll) {
var kids = node.querySelectorAll(sel);
for (j = 0; j < kids.length; j++) out.push(kids[j]);
}
}
return out;
}
function handleNode(node) {
if (!node) return;
if (node.nodeType !== 1) return;
var msgs = findMessages(node);
var i;
var k;
for (i = 0; i < msgs.length; i++) {
var m = msgs[i];
if (processed.has(m)) continue;
var text = SEL.collectText(m); if (!started) { processed.add(m); continue; }
if (!text) continue;
var h = hashText(text);
if (seenHashes.has(h)) {
processed.add(m);
continue;
}
var parsed = extractCalls(text, 3);
if (!parsed) continue;
if (!parsed.calls) continue;
if (parsed.calls.length === 0) continue;
var ch = hashText(JSON.stringify(parsed.calls));
if (seenCallsHashes.has(ch)) { processed.add(m); rememberHash(h); continue; }
rememberCallsHash(ch);
processed.add(m);
rememberHash(h);
for (k = 0; k < parsed.calls.length; k++) queue.push(parsed.calls[k]);
if (UI) UI.setPhase('executing');
}
pump();
}
function executeOne(call) {
return BRIDGE.sendCall(call).then(function (res) {
var e = res && !res.ok && res.error;
var needConfirm = e && e.code === "E_NEEDS_CONFIRM" && e.extra && e.extra.confirmToken;
if (needConfirm) {
console.log("[dsh-memory] needs confirm, asking user", call.tool);
if (!UI || typeof UI.showConfirm !== 'function') { return { ok: false, error: { code: 'E_CONFIRM_UNAVAILABLE', message: 'confirm UI not available' } }; } return new Promise(function (resolve) { UI.showConfirm({ title: 'Allow ' + call.tool + '?', detail: JSON.stringify(call.args || {}).slice(0, 300), onAllow: function () { BRIDGE.sendCall({ tool: call.tool, args: call.args, confirmToken: e.extra.confirmToken }).then(resolve); }, onDeny: function () { resolve({ ok: false, error: { code: 'E_USER_DENIED', message: 'denied by user' } }); } }); });
}
return res;
});
}
function pump() {
if (pumping) return;
if (queue.length === 0) return;
pumping = true;
var results = [];
var step = function () {
if (queue.length === 0) {
pumping = false;
if (results.length > 0) {
var payload = buildResultMessage(results);
if (UI) UI.setPhase('sending');
sendText(payload).then(function () { if (UI) UI.setPhase('idle'); }).catch(function (err) { console.warn('[dsh-memory] sendText failed', String(err)); if (UI) UI.setPhase('idle'); });
} else { if (UI) UI.setPhase('idle'); }
return;
}
var call = queue.shift();
executeOne(call).then(function (res) {
if (res) {
results.push(res);
if (res.ok) {
console.log('[dsh-memory] call ok', call.tool, res.durationMs);
if (UI) UI.log('ok ' + call.tool);
} else {
console.warn('[dsh-memory] call failed', call.tool, res.error);
if (UI) UI.log('fail ' + call.tool);
}
} else {
console.warn('[dsh-memory] call failed', call.tool);
if (UI) UI.log('fail ' + call.tool);
}
}).catch(function (err) { console.warn('[dsh-memory] call throw', call.tool, String(err)); }).then(step);
};
step();
}
function flush() {
timer = null;
var nodes = Array.from(pending);
pending.clear();
var i;
for (i = 0; i < nodes.length; i++) handleNode(nodes[i]);
}
function schedule() {
if (timer) clearTimeout(timer);
timer = setTimeout(flush, DEBOUNCE_MS);
}
var obs = new MutationObserver(function (muts) {
var i;
var j;
for (i = 0; i < muts.length; i++) {
var m = muts[i];
if (m.type === "childList") {
for (j = 0; j < m.addedNodes.length; j++) {
var n = m.addedNodes[j];
if (n) {
if (n.nodeType === 1) pending.add(n);
}
}
} else if (m.type === "characterData") {
if (m.target) {
if (m.target.parentNode) pending.add(m.target.parentNode);
}
}
}
if (pending.size > 0) schedule();
});
BRIDGE.isEnabled().then(function (enabled) {
if (!enabled) {
console.warn("[dsh-memory] disabled (dsh-memory-enabled=false), idle");
if (UI) { UI.setPhase('off'); UI.setConnected(false); UI.log('disabled'); }
return;
}
return BRIDGE.hasToken().then(function (has) {
if (!has) {
console.warn("[dsh-memory] no bridge token, idle");
if (UI) { UI.setPhase('off'); UI.setConnected(false); UI.log('no token'); }
return;
}
markExistingAsProcessed();
obs.observe(document.documentElement, {
childList: true,
subtree: true,
characterData: true
});
console.log("[dsh-memory] content.js active");
if (UI) { UI.setPhase('idle'); UI.setConnected(true); canStart = true; UI.log('active'); }
});
});
})();