Update src/ai-repo-commander.user.js
No auto-unmarking on any failure — failed commands stay marked (no surprise retries). Manual retry helpers: AI_REPO_RETRY_COMMAND_TEXT(text) — unmarks by raw command text. AI_REPO_RETRY_MESSAGE(messageId) — unmarks and immediately reprocesses that message (handy in practice). Everything else from the debug/UX hardening remains intact.
This commit is contained in:
parent
bd53e289cf
commit
5acd23f595
|
|
@ -1,8 +1,8 @@
|
||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name AI Repo Commander
|
// @name AI Repo Commander
|
||||||
// @namespace http://tampermonkey.net/
|
// @namespace http://tampermonkey.net/
|
||||||
// @version 1.3.2
|
// @version 1.3.4
|
||||||
// @description Execute ^%$bridge YAML commands from AI assistants in code blocks with persistent dedupe, robust paste, optional auto-submit, and a built-in debug console
|
// @description Execute ^%$bridge YAML commands from AI assistants in code blocks with persistent dedupe, robust paste, optional auto-submit, and a built-in debug console (safe manual-retry only)
|
||||||
// @author Your Name
|
// @author Your Name
|
||||||
// @match https://chat.openai.com/*
|
// @match https://chat.openai.com/*
|
||||||
// @match https://chatgpt.com/*
|
// @match https://chatgpt.com/*
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
DEBUG_SHOW_PANEL: true, // Show floating debug console UI
|
DEBUG_SHOW_PANEL: true, // Show floating debug console UI
|
||||||
DEBOUNCE_DELAY: 5000, // Bot typing protection
|
DEBOUNCE_DELAY: 5000, // Bot typing protection
|
||||||
MAX_RETRIES: 2, // Retry attempts (=> up to MAX_RETRIES+1 total tries)
|
MAX_RETRIES: 2, // Retry attempts (=> up to MAX_RETRIES+1 total tries)
|
||||||
VERSION: '1.3.2',
|
VERSION: '1.3.4',
|
||||||
|
|
||||||
PROCESS_EXISTING: false, // If false, only process *new* messages (no initial rescan)
|
PROCESS_EXISTING: false, // If false, only process *new* messages (no initial rescan)
|
||||||
ASSISTANT_ONLY: true, // Process assistant messages by default (core use case)
|
ASSISTANT_ONLY: true, // Process assistant messages by default (core use case)
|
||||||
|
|
@ -61,6 +61,16 @@
|
||||||
this.loopCounts = new Map();
|
this.loopCounts = new Map();
|
||||||
this.startedAt = Date.now();
|
this.startedAt = Date.now();
|
||||||
this.panel = null;
|
this.panel = null;
|
||||||
|
|
||||||
|
// loop-counts periodic cleanup to avoid unbounded growth
|
||||||
|
this.loopCleanupInterval = setInterval(() => {
|
||||||
|
if (Date.now() - this.startedAt > this.cfg.DEBUG_WATCH_MS * 2) {
|
||||||
|
this.loopCounts.clear();
|
||||||
|
this.startedAt = Date.now();
|
||||||
|
this.verbose('Cleaned loop counters');
|
||||||
|
}
|
||||||
|
}, this.cfg.DEBUG_WATCH_MS);
|
||||||
|
|
||||||
if (cfg.DEBUG_SHOW_PANEL) this.mount();
|
if (cfg.DEBUG_SHOW_PANEL) this.mount();
|
||||||
this.info(`Debug console ready (level=${cfg.DEBUG_LEVEL})`);
|
this.info(`Debug console ready (level=${cfg.DEBUG_LEVEL})`);
|
||||||
}
|
}
|
||||||
|
|
@ -229,6 +239,11 @@
|
||||||
while (body.children.length > this.cfg.DEBUG_MAX_LINES) body.firstChild.remove();
|
while (body.children.length > this.cfg.DEBUG_MAX_LINES) body.firstChild.remove();
|
||||||
body.scrollTop = body.scrollHeight;
|
body.scrollTop = body.scrollHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
try { clearInterval(this.loopCleanupInterval); } catch {}
|
||||||
|
if (this.panel && this.panel.parentNode) this.panel.parentNode.removeChild(this.panel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------- Platform selectors ----------------------
|
// ---------------------- Platform selectors ----------------------
|
||||||
|
|
@ -303,6 +318,11 @@
|
||||||
db[this._hash(text)] = Date.now();
|
db[this._hash(text)] = Date.now();
|
||||||
this._save(db);
|
this._save(db);
|
||||||
}
|
}
|
||||||
|
unmark(text) { // manual retry only
|
||||||
|
const db = this._load();
|
||||||
|
const k = this._hash(text);
|
||||||
|
if (k in db) { delete db[k]; this._save(db); }
|
||||||
|
}
|
||||||
cleanup() {
|
cleanup() {
|
||||||
const db = this._load();
|
const db = this._load();
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
@ -819,7 +839,7 @@
|
||||||
});
|
});
|
||||||
this.observer.observe(document.body, { childList: true, subtree: true });
|
this.observer.observe(document.body, { childList: true, subtree: true });
|
||||||
|
|
||||||
// Respect PROCESS_EXISTING on initial scan
|
// Respect PROCESS_EXISTING on initial scan (explicitly log skip)
|
||||||
if (CONFIG.PROCESS_EXISTING) {
|
if (CONFIG.PROCESS_EXISTING) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
RC_DEBUG?.info('Initial scan after page load (PROCESS_EXISTING=true)');
|
RC_DEBUG?.info('Initial scan after page load (PROCESS_EXISTING=true)');
|
||||||
|
|
@ -925,7 +945,19 @@
|
||||||
const message = this.trackedMessages.get(messageId);
|
const message = this.trackedMessages.get(messageId);
|
||||||
if (!message) { RC_DEBUG?.error('Message not found', { messageId }); return; }
|
if (!message) { RC_DEBUG?.error('Message not found', { messageId }); return; }
|
||||||
|
|
||||||
let parsed = CommandParser.parseYAMLCommand(message.originalText);
|
// Parsing wrapped for clearer error classification
|
||||||
|
let parsed;
|
||||||
|
try {
|
||||||
|
parsed = CommandParser.parseYAMLCommand(message.originalText);
|
||||||
|
} catch (err) {
|
||||||
|
RC_DEBUG?.error(`Command parsing failed: ${err.message}`, { messageId });
|
||||||
|
this.updateState(messageId, COMMAND_STATES.ERROR);
|
||||||
|
// Ignore UI error for common partial/invalid cases
|
||||||
|
if (/No valid command block|Missing required field:|YAML parsing error/i.test(err.message)) return;
|
||||||
|
UIFeedback.appendStatus(message.element, 'ERROR', { action: 'Command', details: err.message });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.updateState(messageId, COMMAND_STATES.VALIDATING);
|
this.updateState(messageId, COMMAND_STATES.VALIDATING);
|
||||||
|
|
||||||
let validation = CommandParser.validateStructure(parsed);
|
let validation = CommandParser.validateStructure(parsed);
|
||||||
|
|
@ -951,21 +983,29 @@
|
||||||
if (!validation.isValid) throw new Error(`Final validation failed: ${validation.errors.join(', ')}`);
|
if (!validation.isValid) throw new Error(`Final validation failed: ${validation.errors.join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// **HARDENED**: pre-mark to avoid duplicate runs if DOM churns
|
// Pre-mark to avoid duplicate runs if DOM churns — stays marked even on failure
|
||||||
this.history.mark(message.originalText);
|
this.history.mark(message.originalText);
|
||||||
|
|
||||||
this.updateState(messageId, COMMAND_STATES.EXECUTING);
|
this.updateState(messageId, COMMAND_STATES.EXECUTING);
|
||||||
await ExecutionManager.executeCommand(parsed, message.element);
|
const result = await ExecutionManager.executeCommand(parsed, message.element);
|
||||||
|
|
||||||
|
// If execution reported failure, mark ERROR but do not unmark (no auto-retries).
|
||||||
|
if (!result || result.success === false) {
|
||||||
|
RC_DEBUG?.warn('Execution reported failure; command remains marked (no auto-retry)', { messageId });
|
||||||
|
this.updateState(messageId, COMMAND_STATES.ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const duration = Date.now() - started;
|
const duration = Date.now() - started;
|
||||||
if (duration < 100) RC_DEBUG?.warn('Command completed suspiciously fast', { messageId, duration });
|
if (duration < 50) RC_DEBUG?.warn('Command completed very fast', { messageId, duration });
|
||||||
if (duration > 30000) RC_DEBUG?.warn('Command took unusually long', { messageId, duration });
|
if (duration > 60000) RC_DEBUG?.warn('Command took very long', { messageId, duration });
|
||||||
|
|
||||||
this.updateState(messageId, COMMAND_STATES.COMPLETE);
|
this.updateState(messageId, COMMAND_STATES.COMPLETE);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const duration = Date.now() - started;
|
const duration = Date.now() - started;
|
||||||
RC_DEBUG?.error(`Command processing error: ${error.message}`, { messageId, duration });
|
RC_DEBUG?.error(`Command processing error: ${error.message}`, { messageId, duration });
|
||||||
|
// DO NOT unmark — failed commands remain marked to prevent surprise retries
|
||||||
this.updateState(messageId, COMMAND_STATES.ERROR);
|
this.updateState(messageId, COMMAND_STATES.ERROR);
|
||||||
const message = this.trackedMessages.get(messageId);
|
const message = this.trackedMessages.get(messageId);
|
||||||
// Silent ignore for non-commands/partials to avoid noisy inline errors
|
// Silent ignore for non-commands/partials to avoid noisy inline errors
|
||||||
|
|
@ -998,6 +1038,7 @@
|
||||||
clearInterval(this.cleanupIntervalId);
|
clearInterval(this.cleanupIntervalId);
|
||||||
this.cleanupIntervalId = null;
|
this.cleanupIntervalId = null;
|
||||||
}
|
}
|
||||||
|
RC_DEBUG?.destroy?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupEmergencyStop() {
|
setupEmergencyStop() {
|
||||||
|
|
@ -1025,6 +1066,32 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------- Manual retry helpers ----------------------
|
||||||
|
let commandMonitor; // forward ref
|
||||||
|
|
||||||
|
window.AI_REPO_RETRY_COMMAND_TEXT = (text) => {
|
||||||
|
try {
|
||||||
|
commandMonitor?.history?.unmark?.(text);
|
||||||
|
RC_DEBUG?.info('Command unmarked for manual retry (by text)', { preview: String(text).slice(0,120) });
|
||||||
|
} catch (e) {
|
||||||
|
RC_DEBUG?.error('Failed to unmark command by text', { error: String(e) });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.AI_REPO_RETRY_MESSAGE = (messageId) => {
|
||||||
|
try {
|
||||||
|
const msg = commandMonitor?.trackedMessages?.get(messageId);
|
||||||
|
if (!msg) { RC_DEBUG?.warn('Message not found for retry', { messageId }); return; }
|
||||||
|
commandMonitor.history.unmark(msg.originalText);
|
||||||
|
RC_DEBUG?.info('Message unmarked; reprocessing now', { messageId });
|
||||||
|
// re-run directly (ignores PROCESS_EXISTING and aiRcProcessed flag)
|
||||||
|
commandMonitor.updateState(messageId, COMMAND_STATES.PARSING);
|
||||||
|
commandMonitor.processCommand(messageId);
|
||||||
|
} catch (e) {
|
||||||
|
RC_DEBUG?.error('Failed to retry message', { messageId, error: String(e) });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ---------------------- Test commands (unchanged) ----------------------
|
// ---------------------- Test commands (unchanged) ----------------------
|
||||||
const TEST_COMMANDS = {
|
const TEST_COMMANDS = {
|
||||||
validUpdate:
|
validUpdate:
|
||||||
|
|
@ -1063,7 +1130,6 @@ path: .
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---------------------- Init ----------------------
|
// ---------------------- Init ----------------------
|
||||||
let commandMonitor;
|
|
||||||
function initializeRepoCommander() {
|
function initializeRepoCommander() {
|
||||||
if (!RC_DEBUG) RC_DEBUG = new DebugConsole(CONFIG);
|
if (!RC_DEBUG) RC_DEBUG = new DebugConsole(CONFIG);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue