diff --git a/src/ai-repo-commander.user.js b/src/ai-repo-commander.user.js index fa7b972..a6a9d52 100644 --- a/src/ai-repo-commander.user.js +++ b/src/ai-repo-commander.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name AI Repo Commander // @namespace http://tampermonkey.net/ -// @version 1.6.0 +// @version 1.6.2 // @description Execute @bridge@ YAML commands from AI assistants (safe & robust): complete-block detection, streaming-settle, persistent dedupe, paste+autosubmit, debug console with Tools/Settings, draggable/collapsible panel, multi-command queue // @author Your Name // @match https://chat.openai.com/* @@ -34,9 +34,10 @@ DEBUG_SHOW_PANEL: true, // Timing & API - DEBOUNCE_DELAY: 3000, + // If you see "debouncing → error" in logs (assistant streams very slowly), + // try bumping DEBOUNCE_DELAY by +1000–2000 and/or SETTLE_CHECK_MS by +400–800. DEBOUNCE_DELAY: 3000, MAX_RETRIES: 2, - VERSION: '1.6.0', + VERSION: '1.6.2', API_TIMEOUT_MS: 60000, PROCESS_EXISTING: false, @@ -60,7 +61,8 @@ SUBMIT_MODE: 'button_first', // Streaming-complete hardening - REQUIRE_TERMINATOR: true, + // SETTLE_CHECK_MS is the "stable window" after last text change; + // SETTLE_POLL_MS is how often we re-check the code block. REQUIRE_TERMINATOR: true, SETTLE_CHECK_MS: 800, SETTLE_POLL_MS: 200, @@ -414,16 +416,27 @@ const pauseBtn = root.querySelector('.rc-pause'); pauseBtn.addEventListener('click', () => { + const wasPaused = this.cfg.RUNTIME.PAUSED; this.cfg.RUNTIME.PAUSED = !this.cfg.RUNTIME.PAUSED; saveConfig(this.cfg); + pauseBtn.textContent = this.cfg.RUNTIME.PAUSED ? 'Resume' : 'Pause'; pauseBtn.style.background = this.cfg.RUNTIME.PAUSED ? '#f59e0b' : ''; pauseBtn.style.color = this.cfg.RUNTIME.PAUSED ? '#111827' : ''; this.flashBtn(pauseBtn, this.cfg.RUNTIME.PAUSED ? 'Paused' : 'Resumed'); this.toast(this.cfg.RUNTIME.PAUSED ? 'Paused scanning' : 'Resumed scanning'); this.info(`Runtime ${this.cfg.RUNTIME.PAUSED ? 'paused' : 'resumed'}`); + + // When RESUMING: start a short cold-start window and mark current hits as processed. + if (wasPaused && !this.cfg.RUNTIME.PAUSED) { + if (commandMonitor) { + commandMonitor.coldStartUntil = Date.now() + (CONFIG.COLD_START_MS || 2000); + } + markExistingHitsAsProcessed(); + } }); + // Queue clear button const queueBtn = root.querySelector('.rc-queue-clear'); queueBtn.addEventListener('click', (e) => { @@ -1062,6 +1075,33 @@ containerEl.appendChild(bar); } + // When resuming from pause, treat like a cold start & mark all currently-visible commands as processed. + // Adds "Run again" buttons so nothing auto-executes. + function markExistingHitsAsProcessed() { + try { + const messages = document.querySelectorAll(MSG_SELECTORS.join(',')); + messages.forEach((el) => { + const hits = findAllCommandsInMessage(el); + if (!hits.length) return; + + el.dataset.aiRcProcessed = '1'; + const capped = hits.slice(0, CONFIG.QUEUE_MAX_PER_MESSAGE); + + capped.forEach((_, idx) => { + commandMonitor?.history?.markElement?.(el, idx + 1); + }); + + attachRunAgainUI(el, () => { + const nowHits = findAllCommandsInMessage(el).slice(0, CONFIG.QUEUE_MAX_PER_MESSAGE); + nowHits.forEach((h, i) => commandMonitor.enqueueCommand(el, h, i)); + }); + }); + RC_DEBUG?.info('Resume-safe guard: marked visible commands as processed & attached Run again buttons'); + } catch (e) { + RC_DEBUG?.warn('Resume-safe guard failed', { error: String(e) }); + } + } + // ---------------------- UI feedback ---------------------- class UIFeedback { static appendStatus(sourceElement, templateType, data) { @@ -1197,8 +1237,16 @@ const isPM = el.classList && el.classList.contains('ProseMirror'); if (isPM) { RC_DEBUG?.verbose('Attempting ProseMirror paste'); + + // Pad with blank lines before/after to preserve ``` fences visually. + const payload2 = `\n${payload.replace(/\n?$/, '\n')}\n`; const escape = (s) => s.replace(/&/g,'&').replace(//g,'>'); - const html = String(payload).split('\n').map(line => line.length ? `
${escape(line)}
` : '${escape(line)}
` : '