included more features

This commit is contained in:
rob 2025-10-15 22:28:31 -03:00
parent c3fb382127
commit 9049531298
6 changed files with 116 additions and 18 deletions

View File

@ -1,7 +1,30 @@
// ==COMMAND EXECUTOR START== // ==COMMAND EXECUTOR START==
/* global GM_xmlhttpRequest */ /* global GM_xmlhttpRequest */
/* global GM_notification */
(function () { (function () {
/**
* @typedef {Object} RepoCommand
* @property {string} action
* @property {string} [repo]
* @property {string} [owner]
* @property {string} [path]
* @property {string} [content]
* @property {string} [commit_message]
* @property {string} [url]
* @property {boolean} [example]
*/
/**
* @typedef {Object} FileEntry
* @property {string} [path]
* @property {string} [name]
*/
class CommandExecutor { class CommandExecutor {
/**
* @param {RepoCommand} command
* @param {Element} sourceElement
* @param {string} [label]
*/
static async execute(command, sourceElement, label = '') { static async execute(command, sourceElement, label = '') {
const log = window.AI_REPO_LOGGER; const log = window.AI_REPO_LOGGER;
const cfg = window.AI_REPO_CONFIG; const cfg = window.AI_REPO_CONFIG;
@ -97,6 +120,7 @@
} }
static _handleListFiles(data, label) { static _handleListFiles(data, label) {
/** @type {Array<string|FileEntry>} */
const files = data?.files ?? data?.result?.files; const files = data?.files ?? data?.result?.files;
if (!Array.isArray(files)) return; if (!Array.isArray(files)) return;
const listing = '```text\n' + files.map(f => (typeof f === 'string' ? f : (f?.path || f?.name || JSON.stringify(f)))).join('\n') + '\n```'; const listing = '```text\n' + files.map(f => (typeof f === 'string' ? f : (f?.path || f?.name || JSON.stringify(f)))).join('\n') + '\n```';

View File

@ -11,12 +11,12 @@
} }
function isAssistantMsg(el) { function isAssistantMsg(el) {
const sels = [ const selectors = [
'[data-message-author-role="assistant"]', '[data-message-author-role="assistant"]',
'.chat-message:not([data-message-author-role="user"])', '.chat-message:not([data-message-author-role="user"])',
'.message-content' '.message-content'
]; ];
return sels.some(s => el.matches?.(s) || el.querySelector?.(s)); return selectors.some(s => el.matches?.(s) || el.querySelector?.(s));
} }
async function settleText(el, initial, windowMs, pollMs) { async function settleText(el, initial, windowMs, pollMs) {
@ -47,7 +47,7 @@
for (const m of mutations) { for (const m of mutations) {
if (m.type === 'childList') { if (m.type === 'childList') {
for (const n of m.addedNodes) { for (const n of m.addedNodes) {
if (n.nodeType === 1 && isAssistantMsg(n)) { this._handle(n); should = true; } if (n.nodeType === 1 && isAssistantMsg(n)) { void this._handle(n); should = true; }
} }
} }
if (m.type === 'characterData') { if (m.type === 'characterData') {
@ -60,7 +60,7 @@
this.observer.observe(document.body, { childList: true, subtree: true, characterData: true, attributes: true }); this.observer.observe(document.body, { childList: true, subtree: true, characterData: true, attributes: true });
if (cfg().get('ui.processExisting')) { if (cfg().get('ui.processExisting')) {
document.querySelectorAll('[data-message-author-role], .chat-message, .message-content') document.querySelectorAll('[data-message-author-role], .chat-message, .message-content')
.forEach(el => isAssistantMsg(el) && this._handle(el)); .forEach(el => isAssistantMsg(el) && void this._handle(el));
} }
log().info('Detector started'); log().info('Detector started');
} }
@ -98,7 +98,12 @@
try { try {
const parsed = window.AI_REPO_PARSER.parse(commandText); const parsed = window.AI_REPO_PARSER.parse(commandText);
const v = window.AI_REPO_PARSER.validate(parsed); const v = window.AI_REPO_PARSER.validate(parsed);
if (!v.isValid) throw new Error(`Validation failed: ${v.errors.join(', ')}`); if (!v.isValid) {
// Handle validation failures inline instead of throwing; avoid local throw/catch
try { log().warn?.('Validation failed', { errors: v.errors }); } catch {}
this._addRunAgain(el, commandText, idx);
return;
}
if (v.example) { log().info('Example command skipped'); return; } if (v.example) { log().info('Example command skipped'); return; }
await window.AI_REPO_EXECUTOR.execute(parsed, el, `[${idx + 1}] ${parsed.action}`); await window.AI_REPO_EXECUTOR.execute(parsed, el, `[${idx + 1}] ${parsed.action}`);
} catch (e) { } catch (e) {
@ -120,7 +125,7 @@
let scanned = 0; let cur = anchor.nextElementSibling; let scanned = 0; let cur = anchor.nextElementSibling;
while (cur && scanned < this.clusterLookahead) { while (cur && scanned < this.clusterLookahead) {
if (!isAssistantMsg(cur)) break; if (!isAssistantMsg(cur)) break;
if (!this.processed.has(cur)) this._handle(cur); if (!this.processed.has(cur)) void this._handle(cur);
scanned++; cur = cur.nextElementSibling; scanned++; cur = cur.nextElementSibling;
} }
} }

View File

@ -1,7 +1,14 @@
// ==FINGERPRINT (drop-in utility) == // ==FINGERPRINT (drop-in utility) ==
(function(){ (function(){
const MSG_SELECTORS = [
'[data-message-author-role="assistant"]',
'.chat-message:not([data-message-author-role="user"])',
'.message-content'
];
function norm(s){ return (s||'').replace(/\r/g,'').replace(/\u200b/g,'').replace(/[ \t]+\n/g,'\n').trim(); } function norm(s){ return (s||'').replace(/\r/g,'').replace(/\u200b/g,'').replace(/[ \t]+\n/g,'\n').trim(); }
function hash(s){ let h=5381; for(let i=0;i<s.length;i++) h=((h<<5)+h)^s.charCodeAt(i); return (h>>>0).toString(36); } function hash(s){ let h=5381; for(let i=0;i<s.length;i++) h=((h<<5)+h)^s.charCodeAt(i); return (h>>>0).toString(36); }
function commandLikeText(el){ function commandLikeText(el){
const blocks = el.querySelectorAll('pre code, pre, code'); const blocks = el.querySelectorAll('pre code, pre, code');
for (const b of blocks) { for (const b of blocks) {
@ -10,8 +17,9 @@
} }
return norm((el.textContent || '').slice(0, 2000)); return norm((el.textContent || '').slice(0, 2000));
} }
function prevContextHash(el) { function prevContextHash(el) {
const list = Array.from(document.querySelectorAll('[data-message-author-role], .chat-message, .message-content')); const list = Array.from(document.querySelectorAll(MSG_SELECTORS.join(',')));
const idx = list.indexOf(el); if (idx <= 0) return '0'; const idx = list.indexOf(el); if (idx <= 0) return '0';
let rem = 2000, buf = ''; let rem = 2000, buf = '';
for (let i=idx-1; i>=0 && rem>0; i--){ for (let i=idx-1; i>=0 && rem>0; i--){
@ -20,16 +28,57 @@
} }
return hash(buf.slice(-2000)); return hash(buf.slice(-2000));
} }
function intraPrefixHash(el){ function intraPrefixHash(el){
const t = el.textContent || ''; const t = el.textContent || '';
const m = t.match(/@bridge@[\s\S]*?@end@/m); const m = t.match(/@bridge@[\s\S]*?@end@/m);
const endIdx = m ? t.indexOf(m[0]) : t.length; const endIdx = m ? t.indexOf(m[0]) : t.length;
return hash(norm(t.slice(Math.max(0, endIdx - 2000), endIdx))); return hash(norm(t.slice(Math.max(0, endIdx - 2000), endIdx)));
} }
window.AI_REPO_FINGERPRINT = function(el){
function domHint(node) {
if (!node) return '';
const id = node.id || '';
const cls = (node.className && typeof node.className === 'string') ? node.className.split(' ')[0] : '';
return `${node.tagName || ''}#${id}.${cls}`.slice(0, 40);
}
function ordinalForKey(el, key) {
const list = Array.from(document.querySelectorAll(MSG_SELECTORS.join(',')));
let n = 0;
for (const node of list) {
const nodeKey = node === el ? key : (() => {
const ch = hash(commandLikeText(node).slice(0, 2000));
const ph = prevContextHash(node);
const ih = intraPrefixHash(node);
return `ch:${ch}|ph:${ph}|ih:${ih}`;
})();
if (nodeKey === key) {
if (node === el) return n;
n++;
}
}
return n;
}
function fingerprintElement(el){
const ch = hash(commandLikeText(el).slice(0, 2000)); const ch = hash(commandLikeText(el).slice(0, 2000));
const ph = prevContextHash(el); const ph = prevContextHash(el);
const ih = intraPrefixHash(el); const ih = intraPrefixHash(el);
return `ch:${ch}|ph:${ph}|ih:${ih}`; const dh = hash(domHint(el));
}; const key = `ch:${ch}|ph:${ph}|ih:${ih}`;
const n = ordinalForKey(el, key);
return `${key}|hint:${dh}|n:${n}`;
}
function getStableFingerprint(el) {
if (el?.dataset?.aiRcStableFp) return el.dataset.aiRcStableFp;
const fp = fingerprintElement(el);
try { if (el && el.dataset) el.dataset.aiRcStableFp = fp; } catch {}
return fp;
}
// Expose both for backward compatibility
window.AI_REPO_FINGERPRINT = fingerprintElement;
window.AI_REPO_STABLE_FINGERPRINT = getStableFingerprint;
})(); })();

View File

@ -69,7 +69,7 @@
if (history.isProcessed(el, idx)) { if (history.isProcessed(el, idx)) {
this.addRetryButton(el, cmdText, idx); this.addRetryButton(el, cmdText, idx);
} else { } else {
this.run(el, cmdText, idx); void this.run(el, cmdText, idx);
} }
}); });
} }
@ -126,12 +126,13 @@
} }
exposeAPI() { exposeAPI() {
window.AI_REPO_COMMANDER = { // Public API (short name)
window.AI_REPO = {
version: config.get('meta.version'), version: config.get('meta.version'),
config, config: config,
logger, logger: logger,
history, history,
pause: () => { config.set('runtime.paused', true); logger.info('Paused'); }, pause: () => { config.set('runtime.paused', true); logger.info('Paused'); },
resume: () => { config.set('runtime.paused', false); logger.info('Resumed'); }, resume: () => { config.set('runtime.paused', false); logger.info('Resumed'); },
clearHistory: () => { history.clear(); logger.info('History cleared'); } clearHistory: () => { history.clear(); logger.info('History cleared'); }
}; };
@ -147,9 +148,21 @@
logger.error(`🚨 EMERGENCY STOP: cancelled ${queuedCount} queued command(s)`); logger.error(`🚨 EMERGENCY STOP: cancelled ${queuedCount} queued command(s)`);
logger.error('API disabled and scanning paused'); logger.error('API disabled and scanning paused');
}; };
// Bridge key setter
window.AI_REPO_SET_KEY = function(k) {
if (typeof k === 'string' && k.trim()) {
config.set('api.bridgeKey', k.trim());
logger.info('Bridge key updated');
return true;
}
logger.warn('Invalid bridge key');
return false;
};
} }
delay(ms) { return new Promise(r => setTimeout(r, ms)); } delay(ms) { return new Promise(r => setTimeout(r, ms)); }
destroy() { destroy() {
this.observer?.disconnect(); this.observer?.disconnect();
this.processed = new WeakSet(); this.processed = new WeakSet();

View File

@ -34,7 +34,12 @@
} }
_fingerprint(el, idx) { _fingerprint(el, idx) {
const base = window.AI_REPO_FINGERPRINT ? window.AI_REPO_FINGERPRINT(el) : this._hash((el.textContent||'').slice(0,1000)); // Use stable fingerprinting if available (caches on element.dataset)
const base = window.AI_REPO_STABLE_FINGERPRINT
? window.AI_REPO_STABLE_FINGERPRINT(el)
: (window.AI_REPO_FINGERPRINT
? window.AI_REPO_FINGERPRINT(el)
: this._hash((el.textContent||'').slice(0,1000)));
return `${base}|idx:${idx}`; return `${base}|idx:${idx}`;
} }

View File

@ -9,18 +9,20 @@
// @match https://claude.ai/* // @match https://claude.ai/*
// @match https://gemini.google.com/* // @match https://gemini.google.com/*
// @grant GM_xmlhttpRequest // @grant GM_xmlhttpRequest
// @grant GM_notification
// @grant GM_setClipboard // @grant GM_setClipboard
// @connect n8n.brrd.tech // @connect n8n.brrd.tech
// @connect * // @connect *
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/config.js // @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/config.js
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/logger.js // @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/logger.js
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/fingerprint-strong.js
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/storage.js // @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/storage.js
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/command-parser.js // @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/command-parser.js
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/command-executor.js
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/main.js
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/queue.js // @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/queue.js
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/command-executor.js
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/response-buffer.js // @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/response-buffer.js
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/paste-submit.js // @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/paste-submit.js
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/detector.js // @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/detector.js
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/debug-panel.js // @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/debug-panel.js
// @require https://gitea.brrd.tech/rob/AI-Repo-Commander/raw/branch/refactor-structure/src/main.js
// ==/UserScript== // ==/UserScript==