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==
|
||||
// @name AI Repo Commander
|
||||
// @namespace http://tampermonkey.net/
|
||||
// @version 1.3.2
|
||||
// @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
|
||||
// @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 (safe manual-retry only)
|
||||
// @author Your Name
|
||||
// @match https://chat.openai.com/*
|
||||
// @match https://chatgpt.com/*
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
DEBUG_SHOW_PANEL: true, // Show floating debug console UI
|
||||
DEBOUNCE_DELAY: 5000, // Bot typing protection
|
||||
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)
|
||||
ASSISTANT_ONLY: true, // Process assistant messages by default (core use case)
|
||||
|
|
@ -61,6 +61,16 @@
|
|||
this.loopCounts = new Map();
|
||||
this.startedAt = Date.now();
|
||||
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();
|
||||
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();
|
||||
body.scrollTop = body.scrollHeight;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
try { clearInterval(this.loopCleanupInterval); } catch {}
|
||||
if (this.panel && this.panel.parentNode) this.panel.parentNode.removeChild(this.panel);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------- Platform selectors ----------------------
|
||||
|
|
@ -303,6 +318,11 @@
|
|||
db[this._hash(text)] = Date.now();
|
||||
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() {
|
||||
const db = this._load();
|
||||
const now = Date.now();
|
||||
|
|
@ -819,7 +839,7 @@
|
|||
});
|
||||
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) {
|
||||
setTimeout(() => {
|
||||
RC_DEBUG?.info('Initial scan after page load (PROCESS_EXISTING=true)');
|
||||
|
|
@ -925,7 +945,19 @@
|
|||
const message = this.trackedMessages.get(messageId);
|
||||
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);
|
||||
|
||||
let validation = CommandParser.validateStructure(parsed);
|
||||
|
|
@ -951,21 +983,29 @@
|
|||
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.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;
|
||||
if (duration < 100) RC_DEBUG?.warn('Command completed suspiciously fast', { messageId, duration });
|
||||
if (duration > 30000) RC_DEBUG?.warn('Command took unusually long', { messageId, duration });
|
||||
if (duration < 50) RC_DEBUG?.warn('Command completed very fast', { messageId, duration });
|
||||
if (duration > 60000) RC_DEBUG?.warn('Command took very long', { messageId, duration });
|
||||
|
||||
this.updateState(messageId, COMMAND_STATES.COMPLETE);
|
||||
|
||||
} catch (error) {
|
||||
const duration = Date.now() - started;
|
||||
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);
|
||||
const message = this.trackedMessages.get(messageId);
|
||||
// Silent ignore for non-commands/partials to avoid noisy inline errors
|
||||
|
|
@ -998,6 +1038,7 @@
|
|||
clearInterval(this.cleanupIntervalId);
|
||||
this.cleanupIntervalId = null;
|
||||
}
|
||||
RC_DEBUG?.destroy?.();
|
||||
}
|
||||
|
||||
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) ----------------------
|
||||
const TEST_COMMANDS = {
|
||||
validUpdate:
|
||||
|
|
@ -1063,7 +1130,6 @@ path: .
|
|||
};
|
||||
|
||||
// ---------------------- Init ----------------------
|
||||
let commandMonitor;
|
||||
function initializeRepoCommander() {
|
||||
if (!RC_DEBUG) RC_DEBUG = new DebugConsole(CONFIG);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue