fixed all warnings and the docs
This commit is contained in:
parent
7a441aee48
commit
6d6d8a094a
|
|
@ -105,22 +105,23 @@
|
|||
const raw = localStorage.getItem(STORAGE_KEYS.cfg);
|
||||
if (!raw) return structuredClone(DEFAULT_CONFIG);
|
||||
const saved = JSON.parse(raw);
|
||||
// Always use current script's VERSION and RUNTIME, not stale values from storage
|
||||
delete saved.VERSION;
|
||||
delete saved.RUNTIME;
|
||||
return { ...DEFAULT_CONFIG, ...saved, RUNTIME: { ...DEFAULT_CONFIG.RUNTIME, ...(saved.RUNTIME || {}) } };
|
||||
} catch {
|
||||
return structuredClone(DEFAULT_CONFIG);
|
||||
}
|
||||
}
|
||||
function saveConfig(cfg) {
|
||||
try { localStorage.setItem(STORAGE_KEYS.cfg, JSON.stringify(cfg)); } catch {}
|
||||
try {
|
||||
const { VERSION, RUNTIME, ...persistable } = cfg;
|
||||
localStorage.setItem(STORAGE_KEYS.cfg, JSON.stringify(persistable));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const CONFIG = loadSavedConfig();
|
||||
|
||||
// Ensure response buffer singleton exists before command execution
|
||||
if (!window.AI_REPO_RESPONSES) {
|
||||
window.AI_REPO_RESPONSES = new ResponseBuffer();
|
||||
}
|
||||
|
||||
// ---------------------- Debug Console ----------------------
|
||||
let RC_DEBUG = null;
|
||||
|
||||
|
|
@ -516,6 +517,7 @@
|
|||
|
||||
const dump = JSON.parse(JSON.stringify(this.cfg));
|
||||
if (dump.BRIDGE_KEY) dump.BRIDGE_KEY = '•'.repeat(8);
|
||||
dump.VERSION = DEFAULT_CONFIG.VERSION;
|
||||
root.querySelector('.rc-json').value = JSON.stringify(dump, null, 2);
|
||||
|
||||
const bridgeKeyInput = root.querySelector('.rc-bridge-key');
|
||||
|
|
@ -615,6 +617,10 @@
|
|||
delete parsed.BRIDGE_KEY;
|
||||
}
|
||||
|
||||
// Prevent overriding ephemeral fields from pasted JSON
|
||||
delete parsed.VERSION;
|
||||
delete parsed.RUNTIME;
|
||||
|
||||
Object.assign(this.cfg, parsed);
|
||||
saveConfig(this.cfg);
|
||||
|
||||
|
|
@ -825,13 +831,32 @@
|
|||
}
|
||||
|
||||
function findAllCommandsInMessage(el) {
|
||||
const blocks = el.querySelectorAll('pre code, pre, code');
|
||||
const hits = [];
|
||||
const seen = new Set();
|
||||
|
||||
// 1) First scan code elements (pre code, pre, code)
|
||||
const blocks = el.querySelectorAll('pre code, pre, code');
|
||||
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@` });
|
||||
for (const part of parts) {
|
||||
const normalized = _norm(part);
|
||||
seen.add(normalized);
|
||||
hits.push({ blockElement: b, text: `@bridge@\n${part}\n@end@` });
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Also scan the entire element's textContent for plain-text blocks
|
||||
const wholeText = _norm(el.textContent || '');
|
||||
const plainParts = extractAllCompleteBlocks(wholeText);
|
||||
for (const part of plainParts) {
|
||||
const normalized = _norm(part);
|
||||
if (!seen.has(normalized)) {
|
||||
seen.add(normalized);
|
||||
hits.push({ blockElement: null, text: `@bridge@\n${part}\n@end@` });
|
||||
}
|
||||
}
|
||||
|
||||
return hits;
|
||||
}
|
||||
|
||||
|
|
@ -943,29 +968,39 @@
|
|||
continue;
|
||||
}
|
||||
const el = getVisibleInputCandidate();
|
||||
// Pre-paste: the send button can be disabled. Don't block on it here.
|
||||
const btn = findSendButton(el);
|
||||
const btnReady = CONFIG.SUBMIT_MODE === 'enter_only'
|
||||
? true
|
||||
: (!!btn && !btn.disabled && btn.getAttribute('aria-disabled') !== 'true');
|
||||
const scope = el?.closest('form, [data-testid="composer"], main, body') || document;
|
||||
|
||||
// 1) Add typing indicator to busy selector
|
||||
// Narrow scope to composer's local container
|
||||
const scope = el?.closest('form, [data-testid="composer"], [data-testid="composer-container"], main, body') || document;
|
||||
|
||||
// 1) Narrow "busy" detection to the scope and ignore hidden spinners
|
||||
const busy = scope.querySelector('[aria-busy="true"], [data-state="loading"], .typing-indicator');
|
||||
|
||||
// 2) Check if composer has unsent content
|
||||
// Debug logging when composer or button not found
|
||||
if (!el || !btn) {
|
||||
RC_DEBUG?.verbose('Composer probe', {
|
||||
foundEl: !!el,
|
||||
elTag: el?.tagName,
|
||||
elClasses: el ? Array.from(el.classList || []).join(' ') : null,
|
||||
hasBtn: !!btn,
|
||||
busyFound: !!busy
|
||||
});
|
||||
}
|
||||
|
||||
// 2) Only block if there is real (non-whitespace) unsent content already present
|
||||
let hasUnsent = false;
|
||||
if (el) {
|
||||
try {
|
||||
const currentText = (el.textContent || el.value || '').trim();
|
||||
if (currentText.startsWith('@bridge@') || currentText.startsWith('### [')) {
|
||||
hasUnsent = true;
|
||||
}
|
||||
hasUnsent = currentText.length > 0 && !/^\s*$/.test(currentText);
|
||||
} catch (e) {
|
||||
RC_DEBUG?.verbose('Failed to check composer content', { error: String(e) });
|
||||
}
|
||||
}
|
||||
|
||||
if (el && btnReady && !busy && !hasUnsent) return true;
|
||||
// Ready to paste as soon as composer exists, not busy, and no unsent text.
|
||||
if (el && !busy && !hasUnsent) return true;
|
||||
await ExecutionManager.delay(pollMs);
|
||||
}
|
||||
RC_DEBUG?.warn('Composer not ready within timeout');
|
||||
|
|
@ -1387,12 +1422,24 @@
|
|||
// ---------------------- Paste + Submit helpers ----------------------
|
||||
function getVisibleInputCandidate() {
|
||||
const candidates = [
|
||||
'.ProseMirror#prompt-textarea',
|
||||
'#prompt-textarea.ProseMirror',
|
||||
// ChatGPT / GPT
|
||||
'#prompt-textarea',
|
||||
'.ProseMirror',
|
||||
'[contenteditable="true"]',
|
||||
'textarea'
|
||||
'.ProseMirror#prompt-textarea',
|
||||
'.ProseMirror[role="textbox"][contenteditable="true"]',
|
||||
'[data-testid="composer"] [contenteditable="true"][role="textbox"]',
|
||||
'main [contenteditable="true"][role="textbox"]',
|
||||
|
||||
// Claude
|
||||
'.chat-message + [contenteditable="true"]',
|
||||
'[contenteditable="true"][data-testid="chat-input"]',
|
||||
|
||||
// Gemini
|
||||
'textarea[data-testid="input-area"]',
|
||||
'[contenteditable="true"][aria-label*="Message"]',
|
||||
|
||||
// Generic fallbacks
|
||||
'textarea',
|
||||
'[contenteditable="true"]'
|
||||
];
|
||||
for (const sel of candidates) {
|
||||
const el = document.querySelector(sel);
|
||||
|
|
@ -1406,24 +1453,98 @@
|
|||
}
|
||||
|
||||
function findSendButton(scopeEl) {
|
||||
const scope = scopeEl?.closest('form, [data-testid="composer"], main') || document;
|
||||
// Try multiple scope strategies
|
||||
const formScope = scopeEl?.closest('form');
|
||||
const composerScope = scopeEl?.closest('[data-testid="composer"]');
|
||||
const mainScope = scopeEl?.closest('main');
|
||||
const scope = formScope || composerScope || mainScope || document;
|
||||
|
||||
// Debug: Log scoping information
|
||||
RC_DEBUG?.verbose('findSendButton: scope resolution', {
|
||||
hasScopeEl: !!scopeEl,
|
||||
scopeElTag: scopeEl?.tagName,
|
||||
formScope: !!formScope,
|
||||
composerScope: !!composerScope,
|
||||
mainScope: !!mainScope,
|
||||
usingDocument: scope === document
|
||||
});
|
||||
|
||||
const selectors = [
|
||||
'button[data-testid="send-button"]',
|
||||
'button#composer-submit-button',
|
||||
'[id="composer-submit-button"]',
|
||||
'button.composer-submit-btn',
|
||||
'button[data-testid="composer-send-button"]',
|
||||
'button[aria-label="Send"]',
|
||||
'button[aria-label*="Send prompt"]',
|
||||
'button[aria-label*="Send message"]',
|
||||
'button[aria-label*="Send"]',
|
||||
'button[aria-label*="send"]',
|
||||
'button[aria-label*="Submit"]',
|
||||
'button[aria-label*="submit"]',
|
||||
'form button[type="submit"]'
|
||||
// Some pages omit type=submit; keep generic button-in-form last
|
||||
'form button'
|
||||
];
|
||||
|
||||
// First pass: Try scoped search
|
||||
for (const s of selectors) {
|
||||
const btn = scope.querySelector(s);
|
||||
if (!btn) continue;
|
||||
if (btn) {
|
||||
const style = window.getComputedStyle(btn);
|
||||
const disabled = btn.disabled || btn.getAttribute('aria-disabled') === 'true';
|
||||
if (style.display === 'none' || style.visibility === 'hidden') continue;
|
||||
if (btn.offsetParent === null && style.position !== 'fixed') continue;
|
||||
if (!disabled) return btn;
|
||||
const hidden = style.display === 'none' || style.visibility === 'hidden';
|
||||
const notRendered = btn.offsetParent === null && style.position !== 'fixed';
|
||||
|
||||
RC_DEBUG?.verbose('findSendButton: found candidate (scoped)', {
|
||||
selector: s,
|
||||
id: btn.id,
|
||||
disabled,
|
||||
hidden,
|
||||
notRendered,
|
||||
willReturn: !disabled && !hidden && !notRendered
|
||||
});
|
||||
|
||||
if (!disabled && !hidden && !notRendered) return btn;
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: Fallback to global search with detailed logging
|
||||
RC_DEBUG?.verbose('findSendButton: no button found in scope, trying global search');
|
||||
for (const s of selectors) {
|
||||
const btn = document.querySelector(s);
|
||||
if (btn) {
|
||||
const style = window.getComputedStyle(btn);
|
||||
const disabled = btn.disabled || btn.getAttribute('aria-disabled') === 'true';
|
||||
const hidden = style.display === 'none' || style.visibility === 'hidden';
|
||||
const notRendered = btn.offsetParent === null && style.position !== 'fixed';
|
||||
|
||||
RC_DEBUG?.verbose('findSendButton: found candidate (global)', {
|
||||
selector: s,
|
||||
id: btn.id,
|
||||
disabled,
|
||||
hidden,
|
||||
notRendered,
|
||||
inScope: scope.contains(btn),
|
||||
willReturn: !disabled && !hidden && !notRendered
|
||||
});
|
||||
|
||||
if (!disabled && !hidden && !notRendered) return btn;
|
||||
}
|
||||
}
|
||||
|
||||
// Final fallback: XPath for exact button location (works if structure hasn't drifted)
|
||||
try {
|
||||
const xp = '/html/body/div[1]/div/div/div[2]/main/div/div/div[2]/div[1]/div/div[2]/form/div[2]/div/div[3]/div/button';
|
||||
const node = document.evaluate(xp, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
||||
if (node instanceof HTMLButtonElement) {
|
||||
RC_DEBUG?.verbose('findSendButton: found via XPath fallback', { id: node.id });
|
||||
return node;
|
||||
}
|
||||
} catch (e) {
|
||||
RC_DEBUG?.verbose('findSendButton: XPath fallback failed', { error: String(e) });
|
||||
}
|
||||
|
||||
RC_DEBUG?.warn('findSendButton: no valid button found anywhere');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -1790,6 +1911,114 @@
|
|||
|
||||
}
|
||||
|
||||
// ---------------------- ResponseBuffer (helper functions and class) ----------------------
|
||||
function chunkByLines(s, limit) {
|
||||
const out = [];
|
||||
let start = 0;
|
||||
while (start < s.length) {
|
||||
const endSoft = s.lastIndexOf('\n', Math.min(start + limit, s.length));
|
||||
const end = endSoft > start ? endSoft + 1 : Math.min(start + limit, s.length);
|
||||
out.push(s.slice(start, end));
|
||||
start = end;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function isSingleFencedBlock(s) {
|
||||
return /^```[^\n]*\n[\s\S]*\n```$/.test(s.trim());
|
||||
}
|
||||
|
||||
function splitRespectingCodeFence(text, limit) {
|
||||
const trimmed = text.trim();
|
||||
if (!isSingleFencedBlock(trimmed)) {
|
||||
// Not a single fence → just line-friendly chunking
|
||||
return chunkByLines(text, limit);
|
||||
}
|
||||
// Extract inner payload & language hint
|
||||
const m = /^```([^\n]*)\n([\s\S]*)\n```$/.exec(trimmed);
|
||||
const lang = (m?.[1] || 'text').trim();
|
||||
const inner = m?.[2] ?? '';
|
||||
const chunks = chunkByLines(inner, limit - 16 - lang.length); // budget for fences
|
||||
return chunks.map(c => '```' + lang + '\n' + c.replace(/\n?$/, '\n') + '```');
|
||||
}
|
||||
|
||||
class ResponseBuffer {
|
||||
constructor() {
|
||||
this.pending = []; // { label, content }
|
||||
this.timer = null;
|
||||
this.flushing = false;
|
||||
}
|
||||
|
||||
push(item) {
|
||||
if (!item || !item.content) return;
|
||||
this.pending.push(item);
|
||||
this.scheduleFlush();
|
||||
}
|
||||
|
||||
scheduleFlush() {
|
||||
if (this.timer) clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => this.flush(), CONFIG.RESPONSE_BUFFER_FLUSH_DELAY_MS || 500);
|
||||
}
|
||||
|
||||
buildCombined() {
|
||||
const parts = [];
|
||||
for (const { label, content } of this.pending) {
|
||||
if (CONFIG.RESPONSE_BUFFER_SECTION_HEADINGS && label) {
|
||||
parts.push(`### ${label}\n`);
|
||||
}
|
||||
parts.push(String(content).trimEnd());
|
||||
parts.push(''); // blank line between sections
|
||||
}
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
async flush() {
|
||||
if (this.flushing) return;
|
||||
if (!this.pending.length) return;
|
||||
this.flushing = true;
|
||||
|
||||
const toPaste = this.buildCombined();
|
||||
this.pending.length = 0; // clear
|
||||
|
||||
try {
|
||||
const limit = CONFIG.MAX_PASTE_CHARS || 250_000;
|
||||
|
||||
if (CONFIG.SPLIT_LONG_RESPONSES && toPaste.length > limit) {
|
||||
const chunks = splitRespectingCodeFence(toPaste, limit);
|
||||
|
||||
RC_DEBUG?.warn(`Splitting long response into ${chunks.length} message(s)`, {
|
||||
totalChars: toPaste.length, perChunkLimit: limit
|
||||
});
|
||||
|
||||
chunks.forEach((chunk, i) => {
|
||||
const header = CONFIG.RESPONSE_BUFFER_SECTION_HEADINGS
|
||||
? `### Part ${i+1}/${chunks.length}\n`
|
||||
: '';
|
||||
const payload = header + chunk;
|
||||
|
||||
execQueue.push(async () => {
|
||||
await pasteAndMaybeSubmit(payload);
|
||||
});
|
||||
});
|
||||
|
||||
return; // done: queued as multiple messages
|
||||
}
|
||||
|
||||
// Normal single-message path
|
||||
execQueue.push(async () => {
|
||||
await pasteAndMaybeSubmit(toPaste);
|
||||
});
|
||||
|
||||
} finally {
|
||||
this.flushing = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Initialize singleton
|
||||
window.AI_REPO_RESPONSES = new ResponseBuffer();
|
||||
|
||||
// ---------------------- Execution ----------------------
|
||||
class ExecutionManager {
|
||||
static async executeCommand(command, sourceElement, renderKey = '', label = '') {
|
||||
|
|
@ -1918,7 +2147,7 @@
|
|||
if (command.action === 'get_file') {
|
||||
const body = this._extractGetFileBody(data);
|
||||
if (typeof body === 'string' && body.length) {
|
||||
(window.AI_REPO_RESPONSES || new ResponseBuffer()).push({ label, content: body });
|
||||
window.AI_REPO_RESPONSES.push({ label, content: body });
|
||||
} else {
|
||||
GM_notification({ title: 'AI Repo Commander', text: 'get_file succeeded, but no content to paste.', timeout: 4000 });
|
||||
}
|
||||
|
|
@ -1928,10 +2157,10 @@
|
|||
const files = this._extractFilesArray(data);
|
||||
if (files && files.length) {
|
||||
const listing = this._formatFilesListing(files);
|
||||
(window.AI_REPO_RESPONSES || new ResponseBuffer()).push({ label, content: listing });
|
||||
window.AI_REPO_RESPONSES.push({ label, content: listing });
|
||||
} else {
|
||||
const fallback = '```json\n' + JSON.stringify(data, null, 2) + '\n```';
|
||||
(window.AI_REPO_RESPONSES || new ResponseBuffer()).push({ label, content: fallback });
|
||||
window.AI_REPO_RESPONSES.push({ label, content: fallback });
|
||||
GM_notification({
|
||||
title: 'AI Repo Commander',
|
||||
text: 'list_files succeeded, but response had no obvious files array. Pasted raw JSON.',
|
||||
|
|
@ -2020,114 +2249,6 @@
|
|||
cancelOne: (cb) => execQueue.cancelOne(cb),
|
||||
};
|
||||
|
||||
function chunkByLines(s, limit) {
|
||||
const out = [];
|
||||
let start = 0;
|
||||
while (start < s.length) {
|
||||
const endSoft = s.lastIndexOf('\n', Math.min(start + limit, s.length));
|
||||
const end = endSoft > start ? endSoft + 1 : Math.min(start + limit, s.length);
|
||||
out.push(s.slice(start, end));
|
||||
start = end;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function isSingleFencedBlock(s) {
|
||||
return /^```[^\n]*\n[\s\S]*\n```$/.test(s.trim());
|
||||
}
|
||||
|
||||
function splitRespectingCodeFence(text, limit) {
|
||||
const trimmed = text.trim();
|
||||
if (!isSingleFencedBlock(trimmed)) {
|
||||
// Not a single fence → just line-friendly chunking
|
||||
return chunkByLines(text, limit);
|
||||
}
|
||||
// Extract inner payload & language hint
|
||||
const m = /^```([^\n]*)\n([\s\S]*)\n```$/.exec(trimmed);
|
||||
const lang = (m?.[1] || 'text').trim();
|
||||
const inner = m?.[2] ?? '';
|
||||
const chunks = chunkByLines(inner, limit - 16 - lang.length); // budget for fences
|
||||
return chunks.map(c => '```' + lang + '\n' + c.replace(/\n?$/, '\n') + '```');
|
||||
}
|
||||
|
||||
// ---------------------- ResponseBuffer ----------------------
|
||||
class ResponseBuffer {
|
||||
constructor() {
|
||||
this.pending = []; // { label, content }
|
||||
this.timer = null;
|
||||
this.flushing = false;
|
||||
}
|
||||
|
||||
push(item) {
|
||||
if (!item || !item.content) return;
|
||||
this.pending.push(item);
|
||||
this.scheduleFlush();
|
||||
}
|
||||
|
||||
scheduleFlush() {
|
||||
if (this.timer) clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => this.flush(), CONFIG.RESPONSE_BUFFER_FLUSH_DELAY_MS || 500);
|
||||
}
|
||||
|
||||
buildCombined() {
|
||||
const parts = [];
|
||||
for (const { label, content } of this.pending) {
|
||||
if (CONFIG.RESPONSE_BUFFER_SECTION_HEADINGS && label) {
|
||||
parts.push(`### ${label}\n`);
|
||||
}
|
||||
parts.push(String(content).trimEnd());
|
||||
parts.push(''); // blank line between sections
|
||||
}
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
async flush() {
|
||||
if (this.flushing) return;
|
||||
if (!this.pending.length) return;
|
||||
this.flushing = true;
|
||||
|
||||
const toPaste = this.buildCombined();
|
||||
this.pending.length = 0; // clear
|
||||
|
||||
try {
|
||||
const limit = CONFIG.MAX_PASTE_CHARS || 250_000;
|
||||
|
||||
if (CONFIG.SPLIT_LONG_RESPONSES && toPaste.length > limit) {
|
||||
const chunks = splitRespectingCodeFence(toPaste, limit);
|
||||
|
||||
RC_DEBUG?.warn(`Splitting long response into ${chunks.length} message(s)`, {
|
||||
totalChars: toPaste.length, perChunkLimit: limit
|
||||
});
|
||||
|
||||
chunks.forEach((chunk, i) => {
|
||||
const header = CONFIG.RESPONSE_BUFFER_SECTION_HEADINGS
|
||||
? `### Part ${i+1}/${chunks.length}\n`
|
||||
: '';
|
||||
const payload = header + chunk;
|
||||
|
||||
execQueue.push(async () => {
|
||||
await pasteAndMaybeSubmit(payload);
|
||||
});
|
||||
});
|
||||
|
||||
return; // done: queued as multiple messages
|
||||
}
|
||||
|
||||
// Normal single-message path
|
||||
execQueue.push(async () => {
|
||||
await pasteAndMaybeSubmit(toPaste);
|
||||
});
|
||||
|
||||
} finally {
|
||||
this.flushing = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
window.AI_REPO_RESPONSES = new ResponseBuffer(); // optional debug handle
|
||||
|
||||
// ---------------------- Bridge Key ----------------------
|
||||
let BRIDGE_KEY = null;
|
||||
|
||||
|
|
@ -2176,6 +2297,37 @@
|
|||
}
|
||||
};
|
||||
|
||||
// Helper function to find command text in an element (code blocks or plain text)
|
||||
function findCommandTextInElement(el) {
|
||||
// Helper to check if text is a complete command
|
||||
const isComplete = (txt) => {
|
||||
if (!CONFIG.REQUIRE_TERMINATOR) return /(^|\n)\s*@bridge@\b/m.test(txt) && /(^|\n)\s*action\s*:/m.test(txt);
|
||||
return /(^|\n)\s*@bridge@\b/m.test(txt)
|
||||
&& /(^|\n)\s*action\s*:/m.test(txt)
|
||||
&& /@end@\s*$/m.test(txt);
|
||||
};
|
||||
|
||||
// 1) First try to find in code blocks
|
||||
const blocks = el.querySelectorAll('pre code, pre, code');
|
||||
for (const b of blocks) {
|
||||
const txt = (b.textContent || '').trim();
|
||||
if (isComplete(txt)) {
|
||||
return { blockElement: b, text: txt };
|
||||
}
|
||||
}
|
||||
|
||||
// 2) If not found in code blocks, check raw message text
|
||||
const wholeText = _norm(el.textContent || '');
|
||||
const parts = extractAllCompleteBlocks(wholeText);
|
||||
if (parts.length > 0) {
|
||||
const part = parts[0];
|
||||
return { blockElement: null, text: `@bridge@\n${part}\n@end@` };
|
||||
}
|
||||
|
||||
// 3) No complete command found
|
||||
return null;
|
||||
}
|
||||
|
||||
// ---------------------- Monitor (with streaming "settle" & complete-block check) ----------------------
|
||||
class CommandMonitor {
|
||||
constructor() {
|
||||
|
|
@ -2375,18 +2527,7 @@
|
|||
}
|
||||
|
||||
findCommandInCodeBlock(el) {
|
||||
const blocks = el.querySelectorAll('pre code, pre, code');
|
||||
// 🔍 LOG: What we found
|
||||
RC_DEBUG?.trace('🔍 DOM: Searching for command block', {
|
||||
blocksFound: blocks.length
|
||||
});
|
||||
for (const b of blocks) {
|
||||
const txt = (b.textContent || '').trim();
|
||||
if (this.isCompleteCommandText(txt)) {
|
||||
return { blockElement: b, text: txt };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return findCommandTextInElement(el);
|
||||
}
|
||||
|
||||
scanMessages() {
|
||||
|
|
@ -2937,7 +3078,7 @@
|
|||
// ---------------------- Test commands ----------------------
|
||||
const TEST_COMMANDS = {
|
||||
validUpdate:
|
||||
`\
|
||||
`\
|
||||
\`\`\`yaml
|
||||
@bridge@
|
||||
action: update_file
|
||||
|
|
@ -2952,7 +3093,7 @@ content: |
|
|||
\`\`\`
|
||||
`,
|
||||
getFile:
|
||||
`\
|
||||
`\
|
||||
\`\`\`yaml
|
||||
@bridge@
|
||||
action: get_file
|
||||
|
|
@ -2962,7 +3103,7 @@ path: README.md
|
|||
\`\`\`
|
||||
`,
|
||||
listFiles:
|
||||
`\
|
||||
`\
|
||||
\`\`\`yaml
|
||||
@bridge@
|
||||
action: list_files
|
||||
|
|
@ -2972,7 +3113,7 @@ path: .
|
|||
\`\`\`
|
||||
`,
|
||||
createBranch:
|
||||
`\
|
||||
`\
|
||||
\`\`\`yaml
|
||||
@bridge@
|
||||
action: create_branch
|
||||
|
|
@ -2983,7 +3124,7 @@ source_branch: main
|
|||
\`\`\`
|
||||
`,
|
||||
createPR:
|
||||
`\
|
||||
`\
|
||||
\`\`\`yaml
|
||||
@bridge@
|
||||
action: create_pr
|
||||
|
|
@ -2999,7 +3140,7 @@ body: |
|
|||
\`\`\`
|
||||
`,
|
||||
createIssue:
|
||||
`\
|
||||
`\
|
||||
\`\`\`yaml
|
||||
@bridge@
|
||||
action: create_issue
|
||||
|
|
@ -3014,7 +3155,7 @@ body: |
|
|||
\`\`\`
|
||||
`,
|
||||
createTag:
|
||||
`\
|
||||
`\
|
||||
\`\`\`yaml
|
||||
@bridge@
|
||||
action: create_tag
|
||||
|
|
@ -3026,7 +3167,7 @@ message: Release version 1.0.0
|
|||
\`\`\`
|
||||
`,
|
||||
createRelease:
|
||||
`\
|
||||
`\
|
||||
\`\`\`yaml
|
||||
@bridge@
|
||||
action: create_release
|
||||
|
|
@ -3045,7 +3186,7 @@ body: |
|
|||
\`\`\`
|
||||
`,
|
||||
multiCommand:
|
||||
`\
|
||||
`\
|
||||
\`\`\`yaml
|
||||
@bridge@
|
||||
action: get_file
|
||||
|
|
|
|||
Loading…
Reference in New Issue