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:
rob 2025-10-07 19:47:49 +00:00
parent bd53e289cf
commit 5acd23f595
1 changed files with 76 additions and 10 deletions

View File

@ -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);