264 lines
10 KiB
JavaScript
264 lines
10 KiB
JavaScript
// ==MAIN START==
|
|
// Module: main.js
|
|
// Purpose: Legacy entry point and convenience API exposure.
|
|
// - Initializes observer and optionally scans existing messages
|
|
// - Exposes window.AI_REPO with pause/resume/clearHistory helpers
|
|
// Note: detector.js implements the primary monitoring pipeline; this module
|
|
// remains for compatibility and console convenience.
|
|
(function () {
|
|
'use strict';
|
|
|
|
if (!window.AI_REPO_CONFIG || !window.AI_REPO_LOGGER || !window.AI_REPO_HISTORY || !window.AI_REPO_PARSER || !window.AI_REPO_EXECUTOR) {
|
|
console.error('AI Repo Commander: Core modules not loaded');
|
|
return;
|
|
}
|
|
|
|
const logger = window.AI_REPO_LOGGER;
|
|
const config = window.AI_REPO_CONFIG;
|
|
const history = window.AI_REPO_HISTORY;
|
|
|
|
class AIRepoCommander {
|
|
constructor() {
|
|
this.isInitialized = false;
|
|
this.observer = null;
|
|
this.processed = new WeakSet();
|
|
this.messageSelectors = [
|
|
'[data-message-author-role="assistant"]',
|
|
'.chat-message:not([data-message-author-role="user"])',
|
|
'.message-content'
|
|
];
|
|
}
|
|
|
|
initialize() {
|
|
if (this.isInitialized) {
|
|
logger.warn('Already initialized, skipping');
|
|
return;
|
|
}
|
|
|
|
logger.info('AI Repo Commander initializing', {
|
|
version: config.get('meta.version'),
|
|
debugLevel: config.get('debug.level'),
|
|
apiEnabled: config.get('api.enabled')
|
|
});
|
|
|
|
logger.verbose('Configuration summary', {
|
|
debounceDelay: config.get('execution.debounceDelay'),
|
|
queueMaxPerMin: config.get('queue.maxPerMinute'),
|
|
autoSubmit: config.get('ui.autoSubmit'),
|
|
processExisting: config.get('ui.processExisting')
|
|
});
|
|
|
|
this.startObserver();
|
|
if (config.get('ui.processExisting')) {
|
|
logger.verbose('Will process existing messages on page');
|
|
this.scanExisting();
|
|
}
|
|
this.exposeAPI();
|
|
|
|
this.isInitialized = true;
|
|
logger.info('AI Repo Commander initialized');
|
|
logger.trace('Exposed globals:', Object.keys(window).filter(k => k.startsWith('AI_REPO')));
|
|
}
|
|
|
|
startObserver() {
|
|
this.observer = new MutationObserver((mutations) => {
|
|
if (config.get('runtime.paused')) {
|
|
logger.trace('Mutations ignored (paused)');
|
|
return;
|
|
}
|
|
|
|
let assistantMsgCount = 0;
|
|
for (const m of mutations) {
|
|
if (m.type !== 'childList') continue;
|
|
for (const n of m.addedNodes) {
|
|
if (n.nodeType !== 1) continue;
|
|
if (this.isAssistantMessage(n)) {
|
|
assistantMsgCount++;
|
|
this.processMessage(n);
|
|
}
|
|
const inner = n.querySelectorAll?.(this.messageSelectors.join(',')) || [];
|
|
inner.forEach(el => {
|
|
if (this.isAssistantMessage(el)) {
|
|
assistantMsgCount++;
|
|
this.processMessage(el);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
if (assistantMsgCount > 0) {
|
|
logger.verbose(`Detected ${assistantMsgCount} assistant message(s)`);
|
|
}
|
|
});
|
|
|
|
this.observer.observe(document.body, { childList: true, subtree: true });
|
|
logger.verbose('MutationObserver started, watching document.body');
|
|
}
|
|
|
|
isAssistantMessage(el) {
|
|
return this.messageSelectors.some(sel => el.matches?.(sel));
|
|
}
|
|
|
|
processMessage(el) {
|
|
if (this.processed.has(el)) {
|
|
logger.trace('Message already processed, skipping');
|
|
return;
|
|
}
|
|
|
|
const commands = this.extractCommands(el);
|
|
if (!commands.length) {
|
|
logger.trace('No commands found in message');
|
|
return;
|
|
}
|
|
|
|
logger.verbose(`Found ${commands.length} command block(s) in message`);
|
|
this.processed.add(el);
|
|
|
|
const maxPerMsg = config.get('queue.maxPerMessage');
|
|
const toProcess = commands.slice(0, maxPerMsg);
|
|
|
|
if (commands.length > maxPerMsg) {
|
|
logger.warn(`Message has ${commands.length} commands, limiting to first ${maxPerMsg}`);
|
|
}
|
|
|
|
toProcess.forEach((cmdText, idx) => {
|
|
if (history.isProcessed(el, idx)) {
|
|
logger.verbose(`Command #${idx + 1} already executed, adding retry button`);
|
|
this.addRetryButton(el, cmdText, idx);
|
|
} else {
|
|
logger.verbose(`Queueing command #${idx + 1} for execution`);
|
|
void this.run(el, cmdText, idx);
|
|
}
|
|
});
|
|
}
|
|
|
|
extractCommands(el) {
|
|
const text = el.textContent || '';
|
|
const out = [];
|
|
const re = /@bridge@[\s\S]*?@end@/g;
|
|
let m;
|
|
while ((m = re.exec(text)) !== null) out.push(m[0]);
|
|
return out;
|
|
}
|
|
|
|
async run(el, commandText, index) {
|
|
try {
|
|
logger.trace(`Starting run() for command #${index + 1}`, { preview: commandText.slice(0, 60) + '...' });
|
|
history.markProcessed(el, index);
|
|
|
|
const parsed = window.AI_REPO_PARSER.parse(commandText);
|
|
logger.verbose(`Parsed command #${index + 1}:`, { action: parsed.action, repo: parsed.repo, path: parsed.path });
|
|
|
|
const validation = window.AI_REPO_PARSER.validate(parsed);
|
|
if (!validation.isValid) {
|
|
logger.error('Command validation failed', { errors: validation.errors, command: parsed.action });
|
|
this.addRetryButton(el, commandText, index);
|
|
return;
|
|
}
|
|
|
|
if (validation.example) {
|
|
logger.info('Skipping example command', { action: parsed.action });
|
|
return;
|
|
}
|
|
|
|
const debounce = config.get('execution.debounceDelay') || 0;
|
|
if (debounce > 0) {
|
|
logger.trace(`Debouncing for ${debounce}ms before execution`);
|
|
await this.delay(debounce);
|
|
}
|
|
|
|
const label = `Command ${index + 1}`;
|
|
logger.verbose(`Executing command #${index + 1}: ${parsed.action}`);
|
|
await window.AI_REPO_EXECUTOR.execute(parsed, el, label);
|
|
logger.verbose(`Command #${index + 1} completed successfully`);
|
|
|
|
} catch (e) {
|
|
logger.error('Command execution failed', { error: e.message, stack: e.stack?.slice(0, 200), commandIndex: index });
|
|
this.addRetryButton(el, commandText, index);
|
|
}
|
|
}
|
|
|
|
addRetryButton(el, commandText, idx) {
|
|
const btn = document.createElement('button');
|
|
btn.textContent = `Run Again #${idx + 1}`;
|
|
btn.style.cssText = `
|
|
padding:4px 8px;margin:4px;border:1px solid #374151;border-radius:4px;
|
|
background:#1f2937;color:#e5e7eb;cursor:pointer;
|
|
`;
|
|
btn.addEventListener('click', () => this.run(el, commandText, idx));
|
|
el.appendChild(btn);
|
|
}
|
|
|
|
scanExisting() {
|
|
const nodes = document.querySelectorAll(this.messageSelectors.join(','));
|
|
logger.verbose(`Scanning ${nodes.length} existing message(s) on page`);
|
|
let processed = 0;
|
|
nodes.forEach(el => {
|
|
if (this.isAssistantMessage(el)) {
|
|
processed++;
|
|
this.processMessage(el);
|
|
}
|
|
});
|
|
logger.info(`Scanned ${processed} existing assistant message(s)`);
|
|
}
|
|
|
|
exposeAPI() {
|
|
// Public API (short name)
|
|
window.AI_REPO = {
|
|
version: config.get('meta.version'),
|
|
config: config,
|
|
logger: logger,
|
|
history,
|
|
pause: () => { config.set('runtime.paused', true); logger.info('Paused'); },
|
|
resume: () => { config.set('runtime.paused', false); logger.info('Resumed'); },
|
|
clearHistory: () => { history.clear(); logger.info('History cleared'); }
|
|
};
|
|
|
|
// Emergency STOP function
|
|
window.AI_REPO_STOP = () => {
|
|
config.set('api.enabled', false);
|
|
config.set('runtime.paused', true);
|
|
|
|
const queuedCount = window.AI_REPO_QUEUE?.size?.() || 0;
|
|
window.AI_REPO_QUEUE?.clear?.();
|
|
|
|
logger.error(`🚨 EMERGENCY STOP: cancelled ${queuedCount} queued command(s)`);
|
|
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)); }
|
|
|
|
destroy() {
|
|
this.observer?.disconnect();
|
|
this.processed = new WeakSet();
|
|
this.isInitialized = false;
|
|
logger.info('AI Repo Commander destroyed');
|
|
}
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.AI_REPO_MAIN = new AIRepoCommander();
|
|
window.AI_REPO_MAIN.initialize();
|
|
});
|
|
} else {
|
|
window.AI_REPO_MAIN = new AIRepoCommander();
|
|
window.AI_REPO_MAIN.initialize();
|
|
// Kick off the advanced detector (restores settle/debounce, multi-block, cluster rescan)
|
|
window.AI_REPO_DETECTOR?.start();
|
|
}
|
|
})();
|
|
// ==MAIN END==
|