Update src/ai-repo-commander.user.js
1. Queue Configuration (lines 59-62) QUEUE_MIN_DELAY_MS: 800 - Minimum delay between command executions QUEUE_MAX_PER_MINUTE: 15 - Rate limiting cap QUEUE_MAX_PER_MESSAGE: 5 - Maximum commands per assistant message QUEUE_WAIT_FOR_COMPOSER_MS: 6000 - Timeout for waiting for composer ready state 2. ExecutionQueue Class (lines 830-862) Self-contained queue with rate limiting Automatically drains commands with proper delays Respects per-minute rate limits Provides size change callbacks for UI updates 3. Multi-Command Detection (lines 328-352) extractAllCompleteBlocks() - Finds all @bridge@ blocks in text findAllCommandsInMessage() - Extracts all commands from a message element attachQueueBadge() - Shows visual indicator of queued commands waitForComposerReady() - Waits for safe state before pasting 4. Enhanced History with Per-Command Deduplication (lines 619-685) Extended fingerprinting with optional suffix for multi-command support Each command in a message gets unique tracking (e.g., #1, #2, etc.) 5. Queue Integration in Scanning (lines 1116-1165) Automatically detects and queues multiple commands Shows badge indicating how many commands were queued Respects QUEUE_MAX_PER_MESSAGE limit enqueueCommand() method handles individual command execution 6. Enhanced Emergency Stop (lines 1323-1345) Clears the entire queue when STOP is activated Reports how many commands were cancelled 7. UI Improvements "Clear Queue" button in header showing current queue size Queue settings in Tools & Settings panel Real-time queue size updates 8. Global API (line 1384) window.AI_REPO_QUEUE.clear() - Clear all queued commands window.AI_REPO_QUEUE.size() - Get current queue size window.AI_REPO_QUEUE.cancelOne(predicate) - Cancel specific command The script now handles multiple commands in a single assistant message gracefully, with proper rate limiting, visual feedback, and robust error handling!
This commit is contained in:
parent
3981fc0528
commit
a65a453feb
|
|
@ -1,8 +1,8 @@
|
|||
// ==UserScript==
|
||||
// @name AI Repo Commander
|
||||
// @namespace http://tampermonkey.net/
|
||||
// @version 1.5.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
|
||||
// @version 1.6.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, multi-command queue
|
||||
// @author Your Name
|
||||
// @match https://chat.openai.com/*
|
||||
// @match https://chatgpt.com/*
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
// Timing & API
|
||||
DEBOUNCE_DELAY: 3000,
|
||||
MAX_RETRIES: 2,
|
||||
VERSION: '1.5.2',
|
||||
VERSION: '1.6.0',
|
||||
API_TIMEOUT_MS: 60000,
|
||||
|
||||
PROCESS_EXISTING: false,
|
||||
|
|
@ -72,6 +72,12 @@
|
|||
SCAN_DEBOUNCE_MS: 250,
|
||||
FAST_WARN_MS: 50,
|
||||
SLOW_WARN_MS: 60_000,
|
||||
|
||||
// Queue management
|
||||
QUEUE_MIN_DELAY_MS: 800,
|
||||
QUEUE_MAX_PER_MINUTE: 15,
|
||||
QUEUE_MAX_PER_MESSAGE: 5,
|
||||
QUEUE_WAIT_FOR_COMPOSER_MS: 6000,
|
||||
};
|
||||
|
||||
function loadSavedConfig() {
|
||||
|
|
@ -295,6 +301,7 @@
|
|||
<button class="rc-copy" title="Copy last 50 lines" style="padding:4px 6px;">Copy</button>
|
||||
<button class="rc-pause" title="Pause/resume scanning" style="padding:4px 6px;">Pause</button>
|
||||
<button class="rc-collapse" title="Collapse/expand" style="padding:4px 6px;">▾</button>
|
||||
<button class="rc-queue-clear" title="Clear command queue" style="padding:4px 6px;background:#7c2d12;color:#fff;border:1px solid #991b1b">Clear Queue (0)</button>
|
||||
<button class="rc-stop" title="Stop API calls" style="padding:4px 6px;background:#7f1d1d;color:#fff;border:1px solid #991b1b">STOP API</button>
|
||||
</div>
|
||||
<div class="rc-body rc-body-logs" style="overflow:auto; padding:8px; display:block; flex:1"></div>
|
||||
|
|
@ -338,6 +345,21 @@
|
|||
API_TIMEOUT_MS <input class="rc-num" data-key="API_TIMEOUT_MS" type="number" min="10000" step="5000" style="width:120px;background:#0b1220;color:#e5e7eb;border:1px solid #374151;border-radius:4px;padding:2px 6px;">
|
||||
</label>
|
||||
</div>
|
||||
<div style="grid-column:1 / -1;">
|
||||
<h4 style="margin:8px 0 6px 0;">Queue Settings</h4>
|
||||
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||
QUEUE_MIN_DELAY_MS <input class="rc-num" data-key="QUEUE_MIN_DELAY_MS" type="number" min="0" step="100" style="width:120px;background:#0b1220;color:#e5e7eb;border:1px solid #374151;border-radius:4px;padding:2px 6px;">
|
||||
</label>
|
||||
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||
QUEUE_MAX_PER_MINUTE <input class="rc-num" data-key="QUEUE_MAX_PER_MINUTE" type="number" min="1" step="1" style="width:120px;background:#0b1220;color:#e5e7eb;border:1px solid #374151;border-radius:4px;padding:2px 6px;">
|
||||
</label>
|
||||
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||
QUEUE_MAX_PER_MESSAGE <input class="rc-num" data-key="QUEUE_MAX_PER_MESSAGE" type="number" min="1" step="1" style="width:120px;background:#0b1220;color:#e5e7eb;border:1px solid #374151;border-radius:4px;padding:2px 6px;">
|
||||
</label>
|
||||
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||
QUEUE_WAIT_FOR_COMPOSER_MS <input class="rc-num" data-key="QUEUE_WAIT_FOR_COMPOSER_MS" type="number" min="1000" step="500" style="width:120px;background:#0b1220;color:#e5e7eb;border:1px solid #374151;border-radius:4px;padding:2px 6px;">
|
||||
</label>
|
||||
</div>
|
||||
<div style="grid-column:1 / -1;">
|
||||
<h4 style="margin:8px 0 6px 0;">Bridge Configuration</h4>
|
||||
<label style="display:flex;align-items:center;gap:8px;margin:4px 0;">
|
||||
|
|
@ -402,6 +424,15 @@
|
|||
this.info(`Runtime ${this.cfg.RUNTIME.PAUSED ? 'paused' : 'resumed'}`);
|
||||
});
|
||||
|
||||
// Queue clear button
|
||||
const queueBtn = root.querySelector('.rc-queue-clear');
|
||||
queueBtn.addEventListener('click', (e) => {
|
||||
window.AI_REPO_QUEUE?.clear?.();
|
||||
this.flashBtn(e.currentTarget, 'Cleared');
|
||||
this.toast('Queue cleared');
|
||||
this.warn('Command queue cleared');
|
||||
});
|
||||
|
||||
root.querySelector('.rc-stop').addEventListener('click', (e) => {
|
||||
window.AI_REPO_STOP?.();
|
||||
this.flashBtn(e.currentTarget, 'Stopped');
|
||||
|
|
@ -727,6 +758,63 @@
|
|||
return fingerprint;
|
||||
}
|
||||
|
||||
// ---------------------- Multi-block extraction helpers ----------------------
|
||||
function extractAllCompleteBlocks(text) {
|
||||
const out = [];
|
||||
const re = /^\s*@bridge@[ \t]*\n([\s\S]*?)\n@end@[ \t]*(?:\n|$)/gm;
|
||||
let m;
|
||||
while ((m = re.exec(text)) !== null) {
|
||||
const inner = (m[1] || '').trimEnd();
|
||||
if (inner && /(^|\n)\s*action\s*:/m.test(inner)) out.push(inner);
|
||||
}
|
||||
return out; // array of inner texts (without @bridge@/@end@)
|
||||
}
|
||||
|
||||
function findAllCommandsInMessage(el) {
|
||||
const blocks = el.querySelectorAll('pre code, pre, code');
|
||||
const hits = [];
|
||||
for (const b of blocks) {
|
||||
const txt = (b.textContent || '').trim();
|
||||
const parts = extractAllCompleteBlocks(txt);
|
||||
for (const part of parts) hits.push({ blockElement: b, text: `@bridge@\n${part}\n@end@` });
|
||||
}
|
||||
return hits;
|
||||
}
|
||||
|
||||
// Tiny badge on the message showing how many got queued
|
||||
function attachQueueBadge(el, count) {
|
||||
if (el.querySelector('.ai-rc-queue-badge')) return;
|
||||
const badge = document.createElement('span');
|
||||
badge.className = 'ai-rc-queue-badge';
|
||||
badge.textContent = `${count} command${count>1?'s':''} queued`;
|
||||
badge.style.cssText = `
|
||||
display:inline-block; padding:2px 6px; margin:4px 0;
|
||||
background:#3b82f6; color:#fff; border-radius:4px;
|
||||
font:11px ui-monospace, monospace;`;
|
||||
el.insertBefore(badge, el.firstChild);
|
||||
}
|
||||
|
||||
// Wait until it's safe to paste/submit
|
||||
async function waitForComposerReady({ timeoutMs = CONFIG.QUEUE_WAIT_FOR_COMPOSER_MS, pollMs = 200 } = {}) {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
// basic "assistant still typing" check
|
||||
const lastMsg = Array.from(document.querySelectorAll(MSG_SELECTORS.join(','))).pop();
|
||||
if (lastMsg?.querySelector?.('[aria-busy="true"], .typing-indicator')) {
|
||||
await ExecutionManager.delay(400);
|
||||
continue;
|
||||
}
|
||||
const el = getVisibleInputCandidate();
|
||||
const btn = findSendButton();
|
||||
const btnReady = !btn || (!btn.disabled && btn.getAttribute('aria-disabled') !== 'true');
|
||||
const busy = document.querySelector('[aria-busy="true"], [data-state="loading"], [disabled]');
|
||||
if (el && btnReady && !busy) return true;
|
||||
await ExecutionManager.delay(pollMs);
|
||||
}
|
||||
RC_DEBUG?.warn('Composer not ready within timeout');
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---------------------- Conversation-Aware Element History ----------------------
|
||||
function getConversationId() {
|
||||
const host = location.hostname.replace('chat.openai.com', 'chatgpt.com'); // normalize
|
||||
|
|
@ -887,8 +975,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
hasElement(el) {
|
||||
const fp = fingerprintElement(el);
|
||||
hasElement(el, suffix = '') {
|
||||
let fp = fingerprintElement(el);
|
||||
if (suffix) fp += `#${suffix}`;
|
||||
const result = this.session.has(fp) || (fp in this.cache);
|
||||
|
||||
if (result && CONFIG.DEBUG_LEVEL >= 4) {
|
||||
|
|
@ -902,8 +991,9 @@
|
|||
return result;
|
||||
}
|
||||
|
||||
markElement(el) {
|
||||
const fp = fingerprintElement(el);
|
||||
markElement(el, suffix = '') {
|
||||
let fp = fingerprintElement(el);
|
||||
if (suffix) fp += `#${suffix}`;
|
||||
this.session.add(fp);
|
||||
this.cache[fp] = Date.now();
|
||||
this._save();
|
||||
|
|
@ -920,8 +1010,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
unmarkElement(el) {
|
||||
const fp = fingerprintElement(el);
|
||||
unmarkElement(el, suffix = '') {
|
||||
let fp = fingerprintElement(el);
|
||||
if (suffix) fp += `#${suffix}`;
|
||||
this.session.delete(fp);
|
||||
if (fp in this.cache) {
|
||||
delete this.cache[fp];
|
||||
|
|
@ -1174,6 +1265,13 @@
|
|||
}
|
||||
|
||||
async function pasteAndMaybeSubmit(text) {
|
||||
const ready = await waitForComposerReady({ timeoutMs: CONFIG.QUEUE_WAIT_FOR_COMPOSER_MS });
|
||||
if (!ready) {
|
||||
RC_DEBUG?.warn('Composer not ready; re-queueing paste');
|
||||
execQueue.push(async () => { await pasteAndMaybeSubmit(text); });
|
||||
return false;
|
||||
}
|
||||
|
||||
const pasted = pasteToComposer(text);
|
||||
if (!pasted) return false;
|
||||
|
||||
|
|
@ -1239,8 +1337,6 @@
|
|||
let currentKey = null;
|
||||
let collecting = false;
|
||||
let buf = [];
|
||||
// Kept for reference, but no longer used for collection termination
|
||||
const TOP = ['action','repo','path','content','owner','url','commit_message','branch','ref'];
|
||||
|
||||
for (const raw of lines) {
|
||||
const line = raw.replace(/\r$/, '');
|
||||
|
|
@ -1452,6 +1548,60 @@
|
|||
static delay(ms) { return new Promise(r => setTimeout(r, ms)); }
|
||||
}
|
||||
|
||||
// ---------------------- Execution Queue ----------------------
|
||||
class ExecutionQueue {
|
||||
constructor({ minDelayMs = CONFIG.QUEUE_MIN_DELAY_MS, maxPerMinute = CONFIG.QUEUE_MAX_PER_MINUTE } = {}) {
|
||||
this.q = [];
|
||||
this.running = false;
|
||||
this.minDelayMs = minDelayMs;
|
||||
this.maxPerMinute = maxPerMinute;
|
||||
this.timestamps = [];
|
||||
this.onSizeChange = null;
|
||||
}
|
||||
push(task) {
|
||||
this.q.push(task);
|
||||
this.onSizeChange?.(this.q.length);
|
||||
if (!this.running) this._drain();
|
||||
}
|
||||
clear() {
|
||||
this.q.length = 0;
|
||||
this.onSizeChange?.(0);
|
||||
}
|
||||
cancelOne(predicate) {
|
||||
const i = this.q.findIndex(predicate);
|
||||
if (i >= 0) this.q.splice(i, 1);
|
||||
this.onSizeChange?.(this.q.length);
|
||||
}
|
||||
_withinBudget() {
|
||||
const now = Date.now();
|
||||
this.timestamps = this.timestamps.filter(t => now - t < 60_000);
|
||||
return this.timestamps.length < this.maxPerMinute;
|
||||
}
|
||||
async _drain() {
|
||||
if (this.running) return;
|
||||
this.running = true;
|
||||
const origLen = this.q.length;
|
||||
while (this.q.length) {
|
||||
// rate cap
|
||||
while (!this._withinBudget()) await ExecutionManager.delay(500);
|
||||
const fn = this.q.shift();
|
||||
this.onSizeChange?.(this.q.length);
|
||||
RC_DEBUG?.toast?.(`Executing command ${origLen - this.q.length}/${origLen}`, 800);
|
||||
try { await fn(); } catch { /* error already surfaced */ }
|
||||
this.timestamps.push(Date.now());
|
||||
await ExecutionManager.delay(this.minDelayMs);
|
||||
}
|
||||
this.running = false;
|
||||
}
|
||||
}
|
||||
|
||||
const execQueue = new ExecutionQueue();
|
||||
window.AI_REPO_QUEUE = {
|
||||
clear: () => execQueue.clear(),
|
||||
size: () => execQueue.q.length,
|
||||
cancelOne: (cb) => execQueue.cancelOne(cb),
|
||||
};
|
||||
|
||||
// ---------------------- Bridge Key ----------------------
|
||||
let BRIDGE_KEY = null;
|
||||
|
||||
|
|
@ -1544,6 +1694,14 @@
|
|||
}
|
||||
}
|
||||
this.cleanupIntervalId = setInterval(() => this.cleanupProcessedCommands(), CONFIG.CLEANUP_INTERVAL_MS);
|
||||
|
||||
// Wire up queue size updates to UI
|
||||
if (execQueue) {
|
||||
execQueue.onSizeChange = (n) => {
|
||||
const queueBtn = document.querySelector('.rc-queue-clear');
|
||||
if (queueBtn) queueBtn.textContent = `Clear Queue (${n})`;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
detectPlatform() {
|
||||
|
|
@ -1687,77 +1845,111 @@
|
|||
if (!this.isAssistantMessage(el)) return;
|
||||
if (el.dataset.aiRcProcessed) return;
|
||||
|
||||
const hit = this.findCommandInCodeBlock(el);
|
||||
if (!hit) return;
|
||||
const hits = findAllCommandsInMessage(el);
|
||||
if (!hits.length) return;
|
||||
|
||||
const cmdText = hit.text;
|
||||
const withinColdStart = Date.now() < this.coldStartUntil;
|
||||
const alreadyProcessed = this.history.hasElement(el);
|
||||
|
||||
RC_DEBUG?.trace('Evaluating message', {
|
||||
withinColdStart,
|
||||
alreadyProcessed,
|
||||
preview: cmdText.slice(0, 60)
|
||||
});
|
||||
|
||||
// Skip if cold start (but DON'T mark in history)
|
||||
if (withinColdStart) {
|
||||
if (hits.length === 1) {
|
||||
el.dataset.aiRcProcessed = '1';
|
||||
|
||||
RC_DEBUG?.verbose('Skipping command - page load (cold start)', {
|
||||
fingerprint: fingerprintElement(el).slice(0, 40) + '...',
|
||||
preview: cmdText.slice(0, 80)
|
||||
});
|
||||
|
||||
attachRunAgainUI(el, () => {
|
||||
el.dataset.aiRcProcessed = '1';
|
||||
|
||||
const id = this.getReadableMessageId(el);
|
||||
const hit2 = this.findCommandInCodeBlock(el);
|
||||
if (hit2) {
|
||||
this.trackMessage(el, hit2.text, id);
|
||||
}
|
||||
});
|
||||
|
||||
skipped++;
|
||||
if (this.history.hasElement(el, 1)) {
|
||||
attachRunAgainUI(el, () => this.trackMessage(el, hits[0].text, this.getReadableMessageId(el)));
|
||||
return;
|
||||
}
|
||||
this.history.markElement(el, 1);
|
||||
this.trackMessage(el, hits[0].text, this.getReadableMessageId(el));
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if already processed in this conversation
|
||||
if (alreadyProcessed) {
|
||||
const withinColdStart = Date.now() < this.coldStartUntil;
|
||||
const alreadyAll = hits.every((_, i) => this.history.hasElement(el, i + 1));
|
||||
|
||||
RC_DEBUG?.trace('Evaluating message', {
|
||||
withinColdStart,
|
||||
alreadyAll,
|
||||
commandCount: hits.length
|
||||
});
|
||||
|
||||
// Skip if cold start or already processed (but DON'T mark new ones in history during cold start)
|
||||
if (withinColdStart || alreadyAll) {
|
||||
el.dataset.aiRcProcessed = '1';
|
||||
|
||||
RC_DEBUG?.verbose('Skipping command - already executed in this conversation', {
|
||||
RC_DEBUG?.verbose('Skipping command(s) - ' + (withinColdStart ? 'page load (cold start)' : 'already executed in this conversation'), {
|
||||
fingerprint: fingerprintElement(el).slice(0, 40) + '...',
|
||||
preview: cmdText.slice(0, 80)
|
||||
commandCount: hits.length
|
||||
});
|
||||
|
||||
attachRunAgainUI(el, () => {
|
||||
el.dataset.aiRcProcessed = '1';
|
||||
const id = this.getReadableMessageId(el);
|
||||
const hit2 = this.findCommandInCodeBlock(el);
|
||||
if (hit2) {
|
||||
this.trackMessage(el, hit2.text, id);
|
||||
const hit2 = findAllCommandsInMessage(el);
|
||||
if (hit2.length) {
|
||||
const capped = hit2.slice(0, CONFIG.QUEUE_MAX_PER_MESSAGE);
|
||||
capped.forEach((h, i) => this.enqueueCommand(el, h, i));
|
||||
}
|
||||
});
|
||||
|
||||
skipped++;
|
||||
skipped += hits.length;
|
||||
return;
|
||||
}
|
||||
|
||||
// New message that hasn't been executed → auto-execute once
|
||||
el.dataset.aiRcProcessed = '1';
|
||||
this.history.markElement(el);
|
||||
const capped = hits.slice(0, CONFIG.QUEUE_MAX_PER_MESSAGE);
|
||||
attachQueueBadge(el, capped.length);
|
||||
|
||||
const id = this.getReadableMessageId(el);
|
||||
this.trackMessage(el, cmdText, id);
|
||||
found++;
|
||||
capped.forEach((hit, idx) => {
|
||||
// mark each sub-command immediately to avoid re-exec on reloads
|
||||
this.history.markElement(el, idx + 1);
|
||||
this.enqueueCommand(el, hit, idx);
|
||||
});
|
||||
found += capped.length;
|
||||
});
|
||||
|
||||
if (skipped) RC_DEBUG?.info(`Skipped ${skipped} command(s) - Run Again buttons added`);
|
||||
if (found) RC_DEBUG?.info(`Auto-executing ${found} new command(s)`);
|
||||
}
|
||||
|
||||
enqueueCommand(element, hit, idx) {
|
||||
const messageId = this.getReadableMessageId(element);
|
||||
const subId = `${messageId}#${idx + 1}`;
|
||||
|
||||
// track this sub-command so updateState/attachRetryUI can work
|
||||
this.trackedMessages.set(subId, {
|
||||
element,
|
||||
originalText: hit.text,
|
||||
state: COMMAND_STATES.DETECTED,
|
||||
startTime: Date.now(),
|
||||
lastUpdate: Date.now(),
|
||||
cancelToken: { cancelled: false },
|
||||
});
|
||||
|
||||
execQueue.push(async () => {
|
||||
// optional tiny settle for streaming
|
||||
await ExecutionManager.delay(CONFIG.SETTLE_POLL_MS);
|
||||
|
||||
const allNow = findAllCommandsInMessage(element);
|
||||
const liveForIdx = allNow[idx]?.text;
|
||||
const finalTxt = (liveForIdx && this.isCompleteCommandText(liveForIdx)) ? liveForIdx : hit.text;
|
||||
|
||||
let parsed;
|
||||
try {
|
||||
parsed = CommandParser.parseYAMLCommand(finalTxt);
|
||||
const val = CommandParser.validateStructure(parsed);
|
||||
if (!val.isValid) throw new Error(`Validation failed: ${val.errors.join(', ')}`);
|
||||
} catch (err) {
|
||||
UIFeedback.appendStatus(element, 'ERROR', { action: 'Command', details: err.message });
|
||||
this.attachRetryUI(element, subId);
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateState(subId, COMMAND_STATES.EXECUTING);
|
||||
const res = await ExecutionManager.executeCommand(parsed, element);
|
||||
if (!res || res.success === false) {
|
||||
this.updateState(subId, COMMAND_STATES.ERROR);
|
||||
this.attachRetryUI(element, subId);
|
||||
return;
|
||||
}
|
||||
this.updateState(subId, COMMAND_STATES.COMPLETE);
|
||||
});
|
||||
}
|
||||
isAssistantMessage(el) {
|
||||
if (!CONFIG.ASSISTANT_ONLY) return true;
|
||||
const host = location.hostname;
|
||||
|
|
@ -1842,15 +2034,22 @@
|
|||
|
||||
attachRunAgainUI(element, () => {
|
||||
element.dataset.aiRcProcessed = '1';
|
||||
const hit = this.findCommandInCodeBlock(element);
|
||||
if (hit) {
|
||||
|
||||
// Parse sub-index from messageId like "...#3"
|
||||
const m = /#(\d+)$/.exec(messageId);
|
||||
const wantIdx = m ? Math.max(0, parseInt(m[1], 10) - 1) : 0;
|
||||
|
||||
const all = findAllCommandsInMessage(element);
|
||||
const selected = all[wantIdx]?.text || all[0]?.text;
|
||||
if (selected) {
|
||||
this.trackedMessages.delete(messageId);
|
||||
const newId = this.getReadableMessageId(element);
|
||||
this.trackMessage(element, hit.text, newId);
|
||||
this.trackMessage(element, selected, newId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
updateState(messageId, state) {
|
||||
const msg = this.trackedMessages.get(messageId);
|
||||
if (!msg) return;
|
||||
|
|
@ -2011,6 +2210,10 @@
|
|||
CONFIG.RUNTIME.PAUSED = true;
|
||||
saveConfig(CONFIG);
|
||||
|
||||
const queuedCount = execQueue.q.length;
|
||||
execQueue.clear();
|
||||
RC_DEBUG?.error(`🚨 EMERGENCY STOP: cancelled ${queuedCount} queued command(s)`);
|
||||
|
||||
for (const [id, msg] of this.trackedMessages.entries()) {
|
||||
if (msg.cancelToken) msg.cancelToken.cancelled = true;
|
||||
|
||||
|
|
@ -2161,6 +2364,28 @@ body: |
|
|||
- Fix Y
|
||||
@end@
|
||||
\`\`\`
|
||||
`,
|
||||
multiCommand:
|
||||
`\
|
||||
\`\`\`yaml
|
||||
@bridge@
|
||||
action: get_file
|
||||
repo: test-repo
|
||||
path: file1.txt
|
||||
@end@
|
||||
|
||||
@bridge@
|
||||
action: get_file
|
||||
repo: test-repo
|
||||
path: file2.txt
|
||||
@end@
|
||||
|
||||
@bridge@
|
||||
action: list_files
|
||||
repo: test-repo
|
||||
path: .
|
||||
@end@
|
||||
\`\`\`
|
||||
`
|
||||
};
|
||||
|
||||
|
|
@ -2176,12 +2401,14 @@ body: |
|
|||
test: TEST_COMMANDS,
|
||||
version: CONFIG.VERSION,
|
||||
history: commandMonitor.history,
|
||||
submitComposer
|
||||
submitComposer,
|
||||
queue: execQueue
|
||||
};
|
||||
RC_DEBUG?.info('AI Repo Commander fully initialized');
|
||||
RC_DEBUG?.info('API Enabled:', { value: CONFIG.ENABLE_API });
|
||||
RC_DEBUG?.info('Test commands available in window.AI_REPO_COMMANDER.test');
|
||||
RC_DEBUG?.info('Reset history with Tools → Clear History or window.AI_REPO.clearHistory()');
|
||||
RC_DEBUG?.info('Queue management: window.AI_REPO_QUEUE.clear() / .size() / .cancelOne()');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue