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.
 
 
 
 
 

386 lines
17 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; var cfgToken = ''; var execMode = 'dom'; var lastSsePid = ''; var domDebugSeen = new Set(); var domDebugCount = 0;
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; started = false; try { obs.disconnect(); } catch (e) {} if (UI) { UI.setPhase('off'); UI.setStartEnabled(true); if (UI.setDot) UI.setDot('stopped'); 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) { if (UI.setStartEnabled) UI.setStartEnabled(true); return; } started = true; markExistingAsProcessed(); obs.observe(document.documentElement, { childList: true, subtree: true, characterData: true }); UI.setPhase('idle'); UI.setConnected(true); if (UI.setDot) UI.setDot('ok'); UI.log('started'); }); }
window.addEventListener('dshmem:metrics', function (e) { try { var mm = JSON.parse(e.detail); if (UI && UI.setMetrics) UI.setMetrics(mm); } catch (err) {} }); if (!SEL) return;
if (!BRIDGE) return;
if (typeof extractCalls !== "function") return;
var DEBOUNCE_MS = 800;
function __uiLog(m) { try { if (UI ? UI.log : null) UI.log(m) } catch (e) { } }
function __debugReport(rec) {
try {
if (!cfgToken) return;
var url = 'http' + ':' + '//127.0.0.1:8766/log/append';
var hh = { 'Content-Type': 'application/json', Authorization: 'Bearer ' + cfgToken };
fetch(url, { method: 'POST', headers: hh, body: JSON.stringify({ file: 'sse-debug.jsonl', lines: [JSON.stringify(rec)] }) }).catch(function () { });
} catch (e) { }
}
function __refreshExecMode() {
try { chrome.storage.local.get('dshmem-bridge-token', function (vv) { if (vv) { cfgToken = vv['dshmem-bridge-token'] ? String(vv['dshmem-bridge-token']) : '' } }) } catch (e) { }
try {
chrome.storage.local.get('dshmem-transport', function (v) {
var raw = v ? v['dshmem-transport'] : null;
var tv = (raw === 'sse') ? 'sse' : 'dom';
if (execMode === tv) return;
execMode = tv;
lastSsePid = '';
seenCallsHashes.clear();
queue.length = 0;
pending.clear();
pumping = false;
if (tv === 'dom') { try { markExistingAsProcessed(); } catch (e) { } }
__uiLog('exec mode: ' + tv);
});
} catch (e) { }
}
try { chrome.storage.onChanged.addListener(function (ch, area) { if (area === 'local') { if (ch['dshmem-transport']) __refreshExecMode(); } }); } catch (e) { }
try { __refreshExecMode(); } catch (e) { }
window.addEventListener('dshmem:sse-answer', function (ev) {
try {
if (!started) return;
if (execMode === 'dom') return;
var d = null; try { d = JSON.parse(ev.detail); } catch (e) { return; }
if (d ? (d.text ? false : true) : true) return;
var pid = d.pid ? String(d.pid) : '';
if (pid.length === 0) return;
if (pid === lastSsePid) { __uiLog('sse dup skipped ' + pid); return; }
lastSsePid = pid;
var parsed = extractCalls(d.text, 10);
try { var __callSum = []; if (parsed ? (parsed.calls ? true : false) : false) { for (var __ci = 0; __ci < parsed.calls.length; __ci++) { var __c = parsed.calls[__ci]; __callSum.push({ i: __ci, tool: (__c ? String(__c.tool) : 'null'), hasArgs: (__c ? (__c.args ? true : false) : false), argsPreview: (__c ? String(JSON.stringify(__c.args || {})).slice(0, 400) : '') }) } } __debugReport({ ev: 'sse-parse', ts: Date.now(), pid: pid, execSource: execMode, consumed: (parsed ? parsed.consumed : -1), textLen: d.text.length, calls: __callSum, parsedCount: (parsed ? parsed.calls.length : 0), text: String(d.text).slice(0, 2000) }) } catch (e7) { }
if (parsed ? (parsed.calls ? (parsed.calls.length > 0) : false) : false) {
var validCount = 0;
for (var k = 0; k < parsed.calls.length; k++) {
var c = parsed.calls[k];
if (c && c.tool === 'invalid') {
try { __debugReport({ ev: 'sse-exec-result', ts: Date.now(), pid: pid, execSource: execMode, tool: 'invalid', ok: false, errorCode: 'E_PARSE_INVALID', errorMsg: String((c.args && c.args._reason) || '') }) } catch (e) { }
__uiLog('sse invalid call: ' + String((c.args && c.args._reason) || ''));
continue;
}
queue.push(c);
validCount++;
}
if (validCount === 0) { if (UI) UI.setPhase('idle'); return; }
if (UI) { if (UI.setDot) UI.setDot('ok-busy'); UI.setPhase('executing'); }
__uiLog('sse exec ' + parsed.calls.length + ' pid ' + pid);
pump();
}
} catch (e) { console.warn('[dsh-memory] sse-answer error', String(e)); }
});
var processed = new WeakSet();
var seenHashes = new Map();
var HASH_CAP = 50;
var queue = [];
var pumping = false;
var timer = null;
var pending = new Set(); var lastPath = location.pathname; function __onPathChange() { var p = location.pathname; if (p === lastPath) return; var __prev = lastPath; lastPath = p; var __fromRoot = (__prev === '/' || __prev === ''); queue.length = 0; pending.clear(); seenHashes.clear(); seenCallsHashes.clear(); pumping = false; lastSsePid = ''; if (!__fromRoot) setTimeout(function () { try { markExistingAsProcessed(); } catch (e) { } }, 200); if (!__fromRoot) setTimeout(function () { try { markExistingAsProcessed(); } catch (e) { } }, 1200); if (UI) { UI.setPhase('idle'); if (UI.log) UI.log('chat changed ' + p); } try { window.dispatchEvent(new CustomEvent('dshmem:chat-change', { detail: p })); } catch (e) {} } window.addEventListener('popstate', __onPathChange); try { var __ps = history.pushState; var __rs = history.replaceState; history.pushState = function () { var r = __ps.apply(this, arguments); setTimeout(__onPathChange, 0); return r; }; history.replaceState = function () { var r = __rs.apply(this, arguments); setTimeout(__onPathChange, 0); return r; }; } catch (e) {} setInterval(__onPathChange, 1000);
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.';
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 (execMode === 'sse') { var __ms = findMessages(node); for (var __mi = 0; __mi < __ms.length; __mi++) processed.add(__ms[__mi]); 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 hasMarker = text.indexOf(String.fromCharCode(10214)) !== -1 || text.indexOf('[DSH') !== -1; if (hasMarker && UI && UI.setDot) UI.setDot('busy'); var parsed = extractCalls(text, 10);
if (!parsed) continue;
if (!parsed.calls) continue;
if (parsed.calls.length === 0) { if (hasMarker) { try { var __dh = hashText(text); if (!domDebugSeen.has(__dh) && domDebugCount < 3) { domDebugSeen.add(__dh); domDebugCount = domDebugCount + 1; __debugReport({ ev: 'dom-parse-fail', ts: Date.now(), execSource: execMode, textLen: text.length, msgText: String(text).slice(0, 2000) }) } } catch (e9) { } } continue; }
var ch = hashText(JSON.stringify(parsed.calls));
if (seenCallsHashes.has(ch)) { if (UI) { if (UI.setDot) UI.setDot('ok'); UI.log('dup call skipped'); } processed.add(m); rememberHash(h); continue; }
rememberCallsHash(ch);
processed.add(m);
rememberHash(h);
for (k = 0; k < parsed.calls.length; k++) { var __dc = parsed.calls[k]; if (__dc && __dc.tool === 'invalid') { try { __debugReport({ ev: 'sse-exec-result', ts: Date.now(), pid: lastSsePid, execSource: execMode, tool: 'invalid', ok: false, errorCode: 'E_PARSE_INVALID', errorMsg: String((__dc.args && __dc.args._reason) || '') }) } catch (e) { } continue; } queue.push(__dc); } if (UI && UI.setDot) UI.setDot('ok-busy');
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');
var anyFail = results.some(function (r) { return !r || !r.ok; }); sendText(payload).then(function () { if (UI) { UI.setPhase('idle'); if (UI.setDot) UI.setDot(anyFail ? 'error' : 'ok'); } }).catch(function (err) { console.warn('[dsh-memory] sendText failed', String(err)); if (UI) { UI.setPhase('idle'); if (UI.setDot) UI.setDot('error'); } });
} else { if (UI) UI.setPhase('idle'); }
return;
}
var call = queue.shift();
executeOne(call).then(function (res) {
try { __debugReport({ ev: 'sse-exec-result', ts: Date.now(), pid: lastSsePid, execSource: execMode, tool: (call ? String(call.tool) : 'null'), ok: (res ? (res.ok ? true : false) : false), errorCode: (res ? (res.error ? String(res.error.code || '') : '') : 'nores'), errorMsg: (res ? (res.error ? String(res.error.message || '').slice(0, 300) : '') : '') }) } catch (e8) { }
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 {
try { console.warn('[dsh-memory] call failed', call.tool, JSON.stringify(res.error)); } catch (e) { }
if (UI) UI.log('fail ' + call.tool + ' ' + JSON.stringify((res && res.error) || 'nores'));
}
} else {
console.warn('[dsh-memory] call failed (no res)', call.tool);
if (UI) UI.log('fail ' + call.tool + ' ' + JSON.stringify((res && res.error) || 'nores'));
}
}).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");
BRIDGE.health().then(function (r) {
if (UI) {
UI.setPhase('off');
UI.setConnected(!!(r && r.ok));
if (!r || !r.ok) UI.log('bridge down: ' + ((r && r.error && r.error.message) || '?'));
else { if (UI.setDot) UI.setDot('off'); UI.log('ready, press Start'); }
}
canStart = true;
}).catch(function (e) {
if (UI) { UI.setPhase('off'); UI.setConnected(false); UI.log('bridge down: ' + String(e)); }
canStart = true;
});
setInterval(function () {
BRIDGE.health().then(function (r) { if (UI && UI.setConnected) UI.setConnected(!!(r && r.ok)); });
}, 30000);
__refreshStats();
});
});
function __refreshStats() { try { var SB = globalThis.STORAGE_BRIDGE; if (!SB || !UI || !UI.setStats) return; SB.authHeaders().then(function (h) {
return fetch(SB.BU + '/memory/state', { headers: h });
}).then(function (r) { return r.json(); }).then(function (j) {
var st = (j && j.ok && j.data) ? j.data : {};
var entries = st.entries || [];
var total = entries.length;
var alwaysN = entries.filter(function (e) { return e.importance === 'always'; }).length;
return SB.authHeaders().then(function (h) { return fetch(SB.BU + '/health', { headers: h }); }).then(function (r) { return r.json(); }).then(function (hj) {
UI.setStats((hj && hj.workspace) || '?', total, alwaysN);
});
}).catch(function (e) { console.warn('[dsh-memory] stats ' + String(e)); UI.setStats('?', 0, 0); }); } catch (e) { console.warn('[dsh-memory] stats ' + String(e)); } }
})();