fixed all warnings and the docs
This commit is contained in:
parent
6d6d8a094a
commit
ca94e03289
|
|
@ -765,7 +765,17 @@
|
||||||
return _hash(buf.slice(-2000));
|
return _hash(buf.slice(-2000));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ordinal among messages that share the same (commandHash, prevCtxHash)
|
// Hash the text within this message that appears BEFORE the first command block
|
||||||
|
function _hashIntraMessagePrefix(el) {
|
||||||
|
const t = (el.textContent || '');
|
||||||
|
// Find the first complete @bridge@ block
|
||||||
|
const m = t.match(/@bridge@[\s\S]*?@end@/m);
|
||||||
|
const endIdx = m ? t.indexOf(m[0]) : t.length;
|
||||||
|
// Hash the last 2000 chars before the command block
|
||||||
|
return _hash(_norm(t.slice(Math.max(0, endIdx - 2000), endIdx)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordinal among messages that share the same (commandHash, prevCtxHash, intraHash)
|
||||||
function _ordinalForKey(el, key) {
|
function _ordinalForKey(el, key) {
|
||||||
const list = Array.from(document.querySelectorAll(MSG_SELECTORS.join(',')));
|
const list = Array.from(document.querySelectorAll(MSG_SELECTORS.join(',')));
|
||||||
let n = 0;
|
let n = 0;
|
||||||
|
|
@ -776,7 +786,8 @@
|
||||||
// Compute on the fly only if needed
|
// Compute on the fly only if needed
|
||||||
const ch = _hashCommand(node);
|
const ch = _hashCommand(node);
|
||||||
const ph = _hashPrevContext(node);
|
const ph = _hashPrevContext(node);
|
||||||
return `ch:${ch}|ph:${ph}`;
|
const ih = _hashIntraMessagePrefix(node);
|
||||||
|
return `ch:${ch}|ph:${ph}|ih:${ih}`;
|
||||||
})();
|
})();
|
||||||
if (nodeKey === key) n++;
|
if (nodeKey === key) n++;
|
||||||
if (node === el) return n; // 1-based ordinal
|
if (node === el) return n; // 1-based ordinal
|
||||||
|
|
@ -802,8 +813,9 @@
|
||||||
// Always use content-based fingerprinting for reliability across reloads
|
// Always use content-based fingerprinting for reliability across reloads
|
||||||
const ch = _hashCommand(el);
|
const ch = _hashCommand(el);
|
||||||
const ph = _hashPrevContext(el);
|
const ph = _hashPrevContext(el);
|
||||||
|
const ih = _hashIntraMessagePrefix(el);
|
||||||
const dh = _hash(_domHint(el));
|
const dh = _hash(_domHint(el));
|
||||||
const key = `ch:${ch}|ph:${ph}`;
|
const key = `ch:${ch}|ph:${ph}|ih:${ih}`;
|
||||||
const n = _ordinalForKey(el, key);
|
const n = _ordinalForKey(el, key);
|
||||||
const fingerprint = `${key}|hint:${dh}|n:${n}`;
|
const fingerprint = `${key}|hint:${dh}|n:${n}`;
|
||||||
|
|
||||||
|
|
@ -811,6 +823,7 @@
|
||||||
fingerprint: fingerprint.slice(0, 60) + '...',
|
fingerprint: fingerprint.slice(0, 60) + '...',
|
||||||
commandHash: ch,
|
commandHash: ch,
|
||||||
prevContextHash: ph,
|
prevContextHash: ph,
|
||||||
|
intraMessageHash: ih,
|
||||||
domHint: dh,
|
domHint: dh,
|
||||||
ordinal: n
|
ordinal: n
|
||||||
});
|
});
|
||||||
|
|
@ -818,6 +831,15 @@
|
||||||
return fingerprint;
|
return fingerprint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stable fingerprint: computed once per element, then cached on dataset.
|
||||||
|
// Prevents drift when the DOM/text changes later.
|
||||||
|
function getStableFingerprint(el) {
|
||||||
|
if (el?.dataset?.aiRcStableFp) return el.dataset.aiRcStableFp;
|
||||||
|
const fp = fingerprintElement(el);
|
||||||
|
try { if (el && el.dataset) el.dataset.aiRcStableFp = fp; } catch {}
|
||||||
|
return fp;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------- Multi-block extraction helpers ----------------------
|
// ---------------------- Multi-block extraction helpers ----------------------
|
||||||
function extractAllCompleteBlocks(text) {
|
function extractAllCompleteBlocks(text) {
|
||||||
const out = [];
|
const out = [];
|
||||||
|
|
@ -944,18 +966,24 @@
|
||||||
RC_DEBUG?.verbose('Cluster rescan completed', { scanned, deadline: Date.now() >= deadline });
|
RC_DEBUG?.verbose('Cluster rescan completed', { scanned, deadline: Date.now() >= deadline });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper functions for per-subcommand dataset flags
|
||||||
|
function subDoneKey(i) { return `aiRcSubDone_${i}`; } // i is 1-based
|
||||||
|
function subEnqKey(i) { return `aiRcSubEnq_${i}`; } // i is 1-based
|
||||||
|
|
||||||
// Tiny badge on the message showing how many got queued
|
// Tiny badge on the message showing how many got queued
|
||||||
function attachQueueBadge(el, count) {
|
function attachQueueBadge(el, count) {
|
||||||
if (el.querySelector('.ai-rc-queue-badge')) return;
|
let badge = el.querySelector('.ai-rc-queue-badge');
|
||||||
const badge = document.createElement('span');
|
if (!badge) {
|
||||||
|
badge = document.createElement('span');
|
||||||
badge.className = 'ai-rc-queue-badge';
|
badge.className = 'ai-rc-queue-badge';
|
||||||
badge.textContent = `${count} command${count>1?'s':''} queued`;
|
|
||||||
badge.style.cssText = `
|
badge.style.cssText = `
|
||||||
display:inline-block; padding:2px 6px; margin:4px 0;
|
display:inline-block; padding:2px 6px; margin:4px 0;
|
||||||
background:#3b82f6; color:#fff; border-radius:4px;
|
background:#3b82f6; color:#fff; border-radius:4px;
|
||||||
font:11px ui-monospace, monospace;`;
|
font:11px ui-monospace, monospace;`;
|
||||||
el.insertBefore(badge, el.firstChild);
|
el.insertBefore(badge, el.firstChild);
|
||||||
}
|
}
|
||||||
|
badge.textContent = `${count} command${count>1?'s':''} queued`;
|
||||||
|
}
|
||||||
|
|
||||||
// Wait until it's safe to paste/submit
|
// Wait until it's safe to paste/submit
|
||||||
async function waitForComposerReady({ timeoutMs = CONFIG.QUEUE_WAIT_FOR_COMPOSER_MS, pollMs = 200 } = {}) {
|
async function waitForComposerReady({ timeoutMs = CONFIG.QUEUE_WAIT_FOR_COMPOSER_MS, pollMs = 200 } = {}) {
|
||||||
|
|
@ -1173,7 +1201,7 @@
|
||||||
* @param {string|number} [suffix]
|
* @param {string|number} [suffix]
|
||||||
*/
|
*/
|
||||||
hasElement(el, suffix = '') {
|
hasElement(el, suffix = '') {
|
||||||
let fp = fingerprintElement(el);
|
let fp = getStableFingerprint(el);
|
||||||
if (suffix !== '' && suffix != null) fp += `#${String(suffix)}`;
|
if (suffix !== '' && suffix != null) fp += `#${String(suffix)}`;
|
||||||
const result = this.session.has(fp) || (fp in this.cache);
|
const result = this.session.has(fp) || (fp in this.cache);
|
||||||
|
|
||||||
|
|
@ -1193,12 +1221,15 @@
|
||||||
* @param {string|number} [suffix]
|
* @param {string|number} [suffix]
|
||||||
*/
|
*/
|
||||||
markElement(el, suffix = '') {
|
markElement(el, suffix = '') {
|
||||||
let fp = fingerprintElement(el);
|
let fp = getStableFingerprint(el);
|
||||||
if (suffix !== '' && suffix != null) fp += `#${String(suffix)}`;
|
if (suffix !== '' && suffix != null) fp += `#${String(suffix)}`;
|
||||||
this.session.add(fp);
|
this.session.add(fp);
|
||||||
this.cache[fp] = Date.now();
|
this.cache[fp] = Date.now();
|
||||||
this._save();
|
this._save();
|
||||||
|
|
||||||
|
// Also set hard per-subcommand flag on element (bullet-proof local dedupe)
|
||||||
|
try { if (el && el.dataset && suffix) el.dataset[subDoneKey(Number(suffix))] = '1'; } catch {}
|
||||||
|
|
||||||
RC_DEBUG?.verbose('Marked element as processed', {
|
RC_DEBUG?.verbose('Marked element as processed', {
|
||||||
fingerprint: fp.slice(0, 60) + '...'
|
fingerprint: fp.slice(0, 60) + '...'
|
||||||
});
|
});
|
||||||
|
|
@ -2172,7 +2203,12 @@
|
||||||
// Trigger cluster rescan for chainable commands
|
// Trigger cluster rescan for chainable commands
|
||||||
try {
|
try {
|
||||||
if (shouldTriggerClusterRescan(sourceElement, command.action)) {
|
if (shouldTriggerClusterRescan(sourceElement, command.action)) {
|
||||||
|
if (!sourceElement.dataset.aiRcClusterCoolUntil || Date.now() > Number(sourceElement.dataset.aiRcClusterCoolUntil)) {
|
||||||
|
sourceElement.dataset.aiRcClusterCoolUntil = String(Date.now() + 1500);
|
||||||
await scheduleClusterRescan(sourceElement);
|
await scheduleClusterRescan(sourceElement);
|
||||||
|
} else {
|
||||||
|
RC_DEBUG?.verbose('Cluster rescan suppressed by cooldown');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
RC_DEBUG?.verbose('Cluster rescan failed', { error: String(e) });
|
RC_DEBUG?.verbose('Cluster rescan failed', { error: String(e) });
|
||||||
|
|
@ -2541,62 +2577,99 @@
|
||||||
|
|
||||||
messages.forEach((el) => {
|
messages.forEach((el) => {
|
||||||
if (!this.isAssistantMessage(el)) return;
|
if (!this.isAssistantMessage(el)) return;
|
||||||
if (el.dataset.aiRcProcessed) return;
|
|
||||||
|
|
||||||
|
// Allow re-scan of already-processed messages to catch *new* blocks appended later
|
||||||
const hits = findAllCommandsInMessage(el);
|
const hits = findAllCommandsInMessage(el);
|
||||||
if (!hits.length) return;
|
if (!hits.length) return;
|
||||||
|
|
||||||
if (hits.length === 1) {
|
const capped = hits.slice(0, CONFIG.QUEUE_MAX_PER_MESSAGE);
|
||||||
|
|
||||||
|
// Count how many sub-commands we have already marked for this element
|
||||||
|
// Prefer element flags (local), fallback to history (persistent)
|
||||||
|
let already = 0;
|
||||||
|
for (let i = 0; i < capped.length; i++) {
|
||||||
|
const idx1 = i + 1;
|
||||||
|
const done = el?.dataset?.[subDoneKey(idx1)] === '1' || this.history.hasElement(el, idx1);
|
||||||
|
if (done) already++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case A: first time seeing this message (no aiRcProcessed yet)
|
||||||
|
if (!el.dataset.aiRcProcessed) {
|
||||||
el.dataset.aiRcProcessed = '1';
|
el.dataset.aiRcProcessed = '1';
|
||||||
if (this.history.hasElement(el, 1)) {
|
|
||||||
attachRunAgainUI(el, () => this.trackMessage(el, hits[0].text, this.getReadableMessageId(el)));
|
// If only one block, keep fast path
|
||||||
|
if (capped.length === 1) {
|
||||||
|
if (already > 0) {
|
||||||
|
// Already executed, add Run Again button
|
||||||
|
attachRunAgainUI(el, () => this.trackMessage(el, capped[0].text, this.getReadableMessageId(el)));
|
||||||
|
skipped++;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.history.markElement(el, 1);
|
this.history.markElement(el, 1);
|
||||||
this.trackMessage(el, hits[0].text, this.getReadableMessageId(el));
|
this.trackMessage(el, capped[0].text, this.getReadableMessageId(el));
|
||||||
|
found++;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if within cold start or all already executed
|
||||||
const withinColdStart = Date.now() < this.coldStartUntil;
|
const withinColdStart = Date.now() < this.coldStartUntil;
|
||||||
const alreadyAll = hits.every((_, i) => this.history.hasElement(el, i + 1));
|
const alreadyAll = (already === capped.length);
|
||||||
|
|
||||||
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) {
|
if (withinColdStart || alreadyAll) {
|
||||||
el.dataset.aiRcProcessed = '1';
|
|
||||||
|
|
||||||
RC_DEBUG?.verbose(
|
RC_DEBUG?.verbose(
|
||||||
'Skipping command(s) - ' +
|
'Skipping command(s) - ' +
|
||||||
(withinColdStart ? 'page load (cold start)' : 'already executed in this conversation'),
|
(withinColdStart ? 'page load (cold start)' : 'already executed in this conversation'),
|
||||||
{ fingerprint: fingerprintElement(el).slice(0, 40) + '...', commandCount: hits.length }
|
{ fingerprint: fingerprintElement(el).slice(0, 40) + '...', commandCount: hits.length }
|
||||||
);
|
);
|
||||||
|
|
||||||
attachRunAgainPerCommand(el, hits.slice(0, CONFIG.QUEUE_MAX_PER_MESSAGE), (idx) => {
|
attachRunAgainPerCommand(el, capped, (idx) => {
|
||||||
el.dataset.aiRcProcessed = '1';
|
el.dataset.aiRcProcessed = '1';
|
||||||
const hit2 = findAllCommandsInMessage(el).slice(0, CONFIG.QUEUE_MAX_PER_MESSAGE);
|
const hit2 = findAllCommandsInMessage(el).slice(0, CONFIG.QUEUE_MAX_PER_MESSAGE);
|
||||||
const h = hit2[idx];
|
const h = hit2[idx];
|
||||||
if (h) this.enqueueCommand(el, h, idx);
|
if (h) this.enqueueCommand(el, h, idx);
|
||||||
});
|
});
|
||||||
skipped += hits.length;
|
skipped += capped.length;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// New message that hasn't been executed → auto-execute once
|
// Multi-block: mark & enqueue all we see now
|
||||||
el.dataset.aiRcProcessed = '1';
|
|
||||||
const capped = hits.slice(0, CONFIG.QUEUE_MAX_PER_MESSAGE);
|
|
||||||
attachQueueBadge(el, capped.length);
|
attachQueueBadge(el, capped.length);
|
||||||
|
|
||||||
capped.forEach((hit, idx) => {
|
capped.forEach((hit, idx) => {
|
||||||
// mark each sub-command immediately to avoid re-exec on reloads
|
const subIdx = idx + 1;
|
||||||
this.history.markElement(el, idx + 1);
|
const enqKey = subEnqKey(subIdx);
|
||||||
|
if (el?.dataset?.[enqKey] === '1' || el?.dataset?.[subDoneKey(subIdx)] === '1') return;
|
||||||
|
try { if (el && el.dataset) el.dataset[enqKey] = '1'; } catch {}
|
||||||
|
this.history.markElement(el, subIdx);
|
||||||
this.enqueueCommand(el, hit, idx);
|
this.enqueueCommand(el, hit, idx);
|
||||||
});
|
});
|
||||||
found += capped.length;
|
found += capped.length;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case B: message was already processed; enqueue only the *new* ones
|
||||||
|
if (already < capped.length) {
|
||||||
|
const newlyAdded = capped.slice(already);
|
||||||
|
RC_DEBUG?.info('Detected new command blocks in already-processed message', {
|
||||||
|
alreadyCount: already,
|
||||||
|
newCount: newlyAdded.length,
|
||||||
|
totalCount: capped.length
|
||||||
|
});
|
||||||
|
|
||||||
|
const existingQueued = parseInt(el.dataset.aiRcQueued || '0', 10) || 0;
|
||||||
|
const total = existingQueued + newlyAdded.length;
|
||||||
|
attachQueueBadge(el, total);
|
||||||
|
|
||||||
|
newlyAdded.forEach((hit, idx) => {
|
||||||
|
const subIdx = already + idx + 1; // 1-based
|
||||||
|
const enqKey = subEnqKey(subIdx);
|
||||||
|
if (el?.dataset?.[enqKey] === '1' || el?.dataset?.[subDoneKey(subIdx)] === '1') return;
|
||||||
|
try { if (el && el.dataset) el.dataset[enqKey] = '1'; } catch {}
|
||||||
|
this.history.markElement(el, subIdx); // also sets SubDone via patch #2
|
||||||
|
this.enqueueCommand(el, hit, subIdx - 1);
|
||||||
|
});
|
||||||
|
el.dataset.aiRcQueued = String(total);
|
||||||
|
found += newlyAdded.length;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (skipped) RC_DEBUG?.info(`Skipped ${skipped} command(s) - Run Again buttons added`);
|
if (skipped) RC_DEBUG?.info(`Skipped ${skipped} command(s) - Run Again buttons added`);
|
||||||
|
|
@ -2605,7 +2678,16 @@
|
||||||
|
|
||||||
enqueueCommand(element, hit, idx) {
|
enqueueCommand(element, hit, idx) {
|
||||||
const messageId = this.getReadableMessageId(element);
|
const messageId = this.getReadableMessageId(element);
|
||||||
const subId = `${messageId}#${idx + 1}`;
|
const subIndex1 = (idx + 1);
|
||||||
|
const subId = `${messageId}#${subIndex1}`;
|
||||||
|
|
||||||
|
// Hard guard: never enqueue twice
|
||||||
|
const enqKey = subEnqKey(subIndex1);
|
||||||
|
if (element?.dataset?.[enqKey] === '1' && element?.dataset?.[subDoneKey(subIndex1)] === '1') {
|
||||||
|
RC_DEBUG?.verbose('Skip enqueue (already done)', { subIndex1 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try { if (element && element.dataset) element.dataset[enqKey] = '1'; } catch {}
|
||||||
|
|
||||||
execQueue.push(async () => {
|
execQueue.push(async () => {
|
||||||
// Micro-settle: wait for text to stabilize before parsing
|
// Micro-settle: wait for text to stabilize before parsing
|
||||||
|
|
@ -2613,8 +2695,8 @@
|
||||||
const blockElement = hit.blockElement;
|
const blockElement = hit.blockElement;
|
||||||
if (blockElement) {
|
if (blockElement) {
|
||||||
let lastText = blockElement.textContent || '';
|
let lastText = blockElement.textContent || '';
|
||||||
const maxWait = 400;
|
const maxWait = 700;
|
||||||
const checkInterval = 80;
|
const checkInterval = 70;
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
while (Date.now() - startTime < maxWait) {
|
while (Date.now() - startTime < maxWait) {
|
||||||
|
|
@ -2973,6 +3055,13 @@
|
||||||
|
|
||||||
this.updateState(messageId, COMMAND_STATES.COMPLETE);
|
this.updateState(messageId, COMMAND_STATES.COMPLETE);
|
||||||
|
|
||||||
|
// Mark as done on element (belt-and-suspenders against fingerprint drift)
|
||||||
|
try {
|
||||||
|
const m = /#(\d+)$/.exec(messageId);
|
||||||
|
const subIndex1 = m ? Number(m[1]) : 1;
|
||||||
|
if (message?.element?.dataset) message.element.dataset[subDoneKey(subIndex1)] = '1';
|
||||||
|
} catch {}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const duration = Date.now() - started;
|
const duration = Date.now() - started;
|
||||||
RC_DEBUG?.error(`Command processing error: ${error.message}`, { messageId, duration });
|
RC_DEBUG?.error(`Command processing error: ${error.message}`, { messageId, duration });
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue