diff --git a/src/ai-repo-commander.user.js b/src/ai-repo-commander.user.js
index 33712a4..59bbbf4 100644
--- a/src/ai-repo-commander.user.js
+++ b/src/ai-repo-commander.user.js
@@ -1,8 +1,8 @@
// ==UserScript==
// @name AI Repo Commander
// @namespace http://tampermonkey.net/
-// @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)
+// @version 1.4.0
+// @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
// @author Your Name
// @match https://chat.openai.com/*
// @match https://chatgpt.com/*
@@ -17,40 +17,68 @@
(function () {
'use strict';
- // ---------------------- Config ----------------------
- const CONFIG = {
- ENABLE_API: true, // Master kill switch (STOP API flips this to false)
- DEBUG_MODE: true, // Global on/off for debug logging
- DEBUG_LEVEL: 2, // 0=off, 1=errors, 2=info, 3=verbose, 4=trace
- DEBUG_WATCH_MS: 120000, // Only log tight loop spam for the first 2 minutes
- DEBUG_MAX_LINES: 400, // In-memory + panel lines
- 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.4',
+ // ---------------------- Storage keys ----------------------
+ const STORAGE_KEYS = {
+ history: 'ai_repo_commander_executed',
+ cfg: 'ai_repo_commander_cfg',
+ panel: 'ai_repo_commander_panel_state'
+ };
- PROCESS_EXISTING: false, // If false, only process *new* messages (no initial rescan)
- ASSISTANT_ONLY: true, // Process assistant messages by default (core use case)
+ // ---------------------- Config (with persistence) ----------------------
+ const DEFAULT_CONFIG = {
+ ENABLE_API: true,
+ DEBUG_MODE: true,
+ DEBUG_LEVEL: 2, // 0=off, 1=errors, 2=info, 3=verbose, 4=trace
+ DEBUG_WATCH_MS: 120000,
+ DEBUG_MAX_LINES: 400,
+ DEBUG_SHOW_PANEL: true,
+ DEBOUNCE_DELAY: 5000, // bot-typing protection
+ MAX_RETRIES: 2,
+ VERSION: '1.4.0',
+
+ PROCESS_EXISTING: false,
+ ASSISTANT_ONLY: true,
// Persistent dedupe window
DEDUPE_TTL_MS: 30 * 24 * 60 * 60 * 1000, // 30 days
// Housekeeping
- CLEANUP_AFTER_MS: 30000, // Drop COMPLETE/ERROR entries after 30s
- CLEANUP_INTERVAL_MS: 60000, // Sweep cadence
+ CLEANUP_AFTER_MS: 30000,
+ CLEANUP_INTERVAL_MS: 60000,
// Paste + submit behavior
- APPEND_TRAILING_NEWLINE: true, // Add '\n' after pasted text
- AUTO_SUBMIT: true, // Try to submit after pasting content
- POST_PASTE_DELAY_MS: 250, // Delay before submit to let editors settle
- SUBMIT_MODE: 'button_first', // 'button_first' | 'enter_only' | 'smart'
+ APPEND_TRAILING_NEWLINE: true,
+ AUTO_SUBMIT: true,
+ POST_PASTE_DELAY_MS: 250,
+ SUBMIT_MODE: 'button_first', // 'button_first' | 'enter_only' | 'smart'
- // Runtime toggles (live-updated by the debug panel)
- RUNTIME: {
- PAUSED: false, // Pause scanning + execution via panel
- }
+ // Streaming-complete hardening (DeepSeek #2)
+ REQUIRE_TERMINATOR: true, // require trailing '---' line
+ SETTLE_CHECK_MS: 1500, // time to see the block remain unchanged
+ SETTLE_POLL_MS: 300, // poll interval for stability
+
+ // Runtime toggles
+ RUNTIME: { PAUSED: false }
};
+ function loadSavedConfig() {
+ try {
+ const raw = localStorage.getItem(STORAGE_KEYS.cfg);
+ if (!raw) return structuredClone(DEFAULT_CONFIG);
+ const saved = JSON.parse(raw);
+ // shallow merge; keep nested RUNTIME
+ const merged = { ...DEFAULT_CONFIG, ...saved, RUNTIME: { ...DEFAULT_CONFIG.RUNTIME, ...(saved.RUNTIME || {}) } };
+ return merged;
+ } catch {
+ return structuredClone(DEFAULT_CONFIG);
+ }
+ }
+ function saveConfig(cfg) {
+ try { localStorage.setItem(STORAGE_KEYS.cfg, JSON.stringify(cfg)); } catch {}
+ }
+
+ const CONFIG = loadSavedConfig();
+
// ---------------------- Debug Console ----------------------
let RC_DEBUG = null;
@@ -61,8 +89,12 @@
this.loopCounts = new Map();
this.startedAt = Date.now();
this.panel = null;
+ this.bodyLogs = null;
+ this.bodyTools = null;
+ this.collapsed = false;
+ this.drag = { active: false, dx: 0, dy: 0 };
+ this.panelState = this._loadPanelState();
- // 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();
@@ -74,7 +106,21 @@
if (cfg.DEBUG_SHOW_PANEL) this.mount();
this.info(`Debug console ready (level=${cfg.DEBUG_LEVEL})`);
}
- // Levels: 1=ERROR, 2=WARN, 3=INFO, 4=VERB, 5=TRACE
+
+ _loadPanelState() {
+ try {
+ return JSON.parse(localStorage.getItem(STORAGE_KEYS.panel) || '{}');
+ } catch { return {}; }
+ }
+ _savePanelState(partial) {
+ try {
+ const merged = { ...(this.panelState || {}), ...(partial || {}) };
+ this.panelState = merged;
+ localStorage.setItem(STORAGE_KEYS.panel, JSON.stringify(merged));
+ } catch {}
+ }
+
+ // Levels
error(msg, data) { this._log(1, 'ERROR', msg, data); }
warn(msg, data) { this._log(2, 'WARN', msg, data); }
info(msg, data) { this._log(3, 'INFO', msg, data); }
@@ -89,7 +135,6 @@
nowIso() { return new Date().toISOString(); }
withinWatch() { return Date.now() - this.startedAt <= this.cfg.DEBUG_WATCH_MS; }
- // Loop/ticker messages → suppress after 10 repeats or after WATCH window
logLoop(kind, msg) {
const k = `${kind}:${msg}`;
const cur = this.loopCounts.get(k) || 0;
@@ -97,7 +142,6 @@
if (cur >= 10) return;
this.loopCounts.set(k, cur + 1);
const suffix = (cur + 1) > 1 ? ` (${cur + 1}x)` : '';
- // default to INFO (visible at level 2+)
if (kind === 'ERROR') this.error(`${msg}${suffix}`);
else if (kind === 'WARN') this.warn(`${msg}${suffix}`);
else this.info(`${msg}${suffix}`);
@@ -134,8 +178,9 @@
}
setLevel(n) {
- const lv = Math.max(0, Math.min(4, n)); // clamp 0..4
+ const lv = Math.max(0, Math.min(4, n));
this.cfg.DEBUG_LEVEL = lv;
+ saveConfig(this.cfg);
this.info(`Log level => ${lv}`);
}
@@ -155,8 +200,6 @@
_log(numericLevel, levelName, msg, data) {
if (!this.cfg.DEBUG_MODE) return;
-
- // Threshold map: 0=off, 1=ERROR, 2=+WARN+INFO, 3=+VERB, 4=+TRACE
const thresholdMap = { 0: 0, 1: 1, 2: 3, 3: 4, 4: 5 };
const threshold = thresholdMap[this.cfg.DEBUG_LEVEL] ?? 0;
if (numericLevel > threshold) return;
@@ -164,34 +207,34 @@
const entry = { ts: this.nowIso(), level: levelName, msg: String(msg), data: this._sanitize(data) };
this.buf.push(entry);
if (this.buf.length > this.cfg.DEBUG_MAX_LINES) this.buf.splice(0, this.buf.length - this.cfg.DEBUG_MAX_LINES);
-
- // Keep console quiet unless verbose+ is enabled
if (this.cfg.DEBUG_LEVEL >= 3) {
const prefix = `[AI RC]`;
if (entry.data != null) console.log(prefix, entry.level, entry.msg, entry.data);
else console.log(prefix, entry.level, entry.msg);
}
-
if (this.panel) this._renderRow(entry);
}
mount() {
- if (!document.body) {
- setTimeout(() => this.mount(), 100);
- return;
- }
+ if (!document.body) { setTimeout(() => this.mount(), 100); return; }
+
const root = document.createElement('div');
root.style.cssText = `
- position: fixed; right: 16px; bottom: 16px; z-index: 2147483647;
- width: 420px; max-height: 45vh; display: flex; flex-direction: column;
+ position: fixed; ${this.panelState.left!==undefined ? `left:${this.panelState.left}px; top:${this.panelState.top}px;` : 'right:16px; bottom:16px;'}
+ z-index: 2147483647;
+ width: 460px; max-height: 55vh; display: flex; flex-direction: column;
background: rgba(20,20,24,0.92); border:1px solid #3b3b46; border-radius: 8px;
color:#e5e7eb; font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
box-shadow: 0 16px 40px rgba(0,0,0,0.55); backdrop-filter: blur(4px);
`;
root.innerHTML = `
-
-
AI Repo Commander — Debug
-