fixed all warnings and the docs
This commit is contained in:
parent
6d6d8a094a
commit
ca94e03289
|
|
@ -765,7 +765,17 @@
|
|||
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) {
|
||||
const list = Array.from(document.querySelectorAll(MSG_SELECTORS.join(',')));
|
||||
let n = 0;
|
||||
|
|
@ -776,7 +786,8 @@
|
|||
// Compute on the fly only if needed
|
||||
const ch = _hashCommand(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 (node === el) return n; // 1-based ordinal
|
||||
|
|
@ -802,8 +813,9 @@
|
|||
// Always use content-based fingerprinting for reliability across reloads
|
||||
const ch = _hashCommand(el);
|
||||
const ph = _hashPrevContext(el);
|
||||
const ih = _hashIntraMessagePrefix(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 fingerprint = `${key}|hint:${dh}|n:${n}`;
|
||||
|
||||
|
|
@ -811,6 +823,7 @@
|
|||
fingerprint: fingerprint.slice(0, 60) + '...',
|
||||
commandHash: ch,
|
||||
prevContextHash: ph,
|
||||
intraMessageHash: ih,
|
||||
domHint: dh,
|
||||
ordinal: n
|
||||
});
|
||||
|
|
@ -818,6 +831,15 @@
|
|||
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 ----------------------
|
||||
function extractAllCompleteBlocks(text) {
|
||||
const out = [];
|
||||
|
|
@ -944,17 +966,23 @@
|
|||
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
|
||||
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 = `
|
||||
let badge = el.querySelector('.ai-rc-queue-badge');
|
||||
if (!badge) {
|
||||
badge = document.createElement('span');
|
||||
badge.className = 'ai-rc-queue-badge';
|
||||
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);
|
||||
el.insertBefore(badge, el.firstChild);
|
||||
}
|
||||
badge.textContent = `${count} command${count>1?'s':''} queued`;
|
||||
}
|
||||
|
||||
// Wait until it's safe to paste/submit
|
||||
|
|
@ -1173,7 +1201,7 @@
|
|||
* @param {string|number} [suffix]
|
||||
*/
|
||||
hasElement(el, suffix = '') {
|
||||
let fp = fingerprintElement(el);
|
||||
let fp = getStableFingerprint(el);
|
||||
if (suffix !== '' && suffix != null) fp += `#${String(suffix)}`;
|
||||
const result = this.session.has(fp) || (fp in this.cache);
|
||||
|
||||
|
|
@ -1193,12 +1221,15 @@
|
|||
* @param {string|number} [suffix]
|
||||
*/
|
||||
markElement(el, suffix = '') {
|
||||
let fp = fingerprintElement(el);
|
||||
let fp = getStableFingerprint(el);
|
||||
if (suffix !== '' && suffix != null) fp += `#${String(suffix)}`;
|
||||
this.session.add(fp);
|
||||
this.cache[fp] = Date.now();
|
||||
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', {
|
||||
fingerprint: fp.slice(0, 60) + '...'
|
||||
});
|
||||
|
|
@ -2172,7 +2203,12 @@
|
|||
// Trigger cluster rescan for chainable commands
|
||||
try {
|
||||
if (shouldTriggerClusterRescan(sourceElement, command.action)) {
|
||||
await scheduleClusterRescan(sourceElement);
|
||||
if (!sourceElement.dataset.aiRcClusterCoolUntil || Date.now() > Number(sourceElement.dataset.aiRcClusterCoolUntil)) {
|
||||
sourceElement.dataset.aiRcClusterCoolUntil = String(Date.now() + 1500);
|
||||
await scheduleClusterRescan(sourceElement);
|
||||
} else {
|
||||
RC_DEBUG?.verbose('Cluster rescan suppressed by cooldown');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
RC_DEBUG?.verbose('Cluster rescan failed', { error: String(e) });
|
||||
|
|
@ -2541,62 +2577,99 @@
|
|||
|
||||
messages.forEach((el) => {
|
||||
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);
|
||||
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';
|
||||
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;
|
||||
}
|
||||
this.history.markElement(el, 1);
|
||||
this.trackMessage(el, capped[0].text, this.getReadableMessageId(el));
|
||||
found++;
|
||||
return;
|
||||
}
|
||||
this.history.markElement(el, 1);
|
||||
this.trackMessage(el, hits[0].text, this.getReadableMessageId(el));
|
||||
return;
|
||||
}
|
||||
|
||||
const withinColdStart = Date.now() < this.coldStartUntil;
|
||||
const alreadyAll = hits.every((_, i) => this.history.hasElement(el, i + 1));
|
||||
// Check if within cold start or all already executed
|
||||
const withinColdStart = Date.now() < this.coldStartUntil;
|
||||
const alreadyAll = (already === capped.length);
|
||||
|
||||
RC_DEBUG?.trace('Evaluating message', {
|
||||
withinColdStart,
|
||||
alreadyAll,
|
||||
commandCount: hits.length
|
||||
});
|
||||
if (withinColdStart || alreadyAll) {
|
||||
RC_DEBUG?.verbose(
|
||||
'Skipping command(s) - ' +
|
||||
(withinColdStart ? 'page load (cold start)' : 'already executed in this conversation'),
|
||||
{ fingerprint: fingerprintElement(el).slice(0, 40) + '...', 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';
|
||||
attachRunAgainPerCommand(el, capped, (idx) => {
|
||||
el.dataset.aiRcProcessed = '1';
|
||||
const hit2 = findAllCommandsInMessage(el).slice(0, CONFIG.QUEUE_MAX_PER_MESSAGE);
|
||||
const h = hit2[idx];
|
||||
if (h) this.enqueueCommand(el, h, idx);
|
||||
});
|
||||
skipped += capped.length;
|
||||
return;
|
||||
}
|
||||
|
||||
RC_DEBUG?.verbose(
|
||||
'Skipping command(s) - ' +
|
||||
(withinColdStart ? 'page load (cold start)' : 'already executed in this conversation'),
|
||||
{ fingerprint: fingerprintElement(el).slice(0, 40) + '...', commandCount: hits.length }
|
||||
);
|
||||
|
||||
attachRunAgainPerCommand(el, hits.slice(0, CONFIG.QUEUE_MAX_PER_MESSAGE), (idx) => {
|
||||
el.dataset.aiRcProcessed = '1';
|
||||
const hit2 = findAllCommandsInMessage(el).slice(0, CONFIG.QUEUE_MAX_PER_MESSAGE);
|
||||
const h = hit2[idx];
|
||||
if (h) this.enqueueCommand(el, h, idx);
|
||||
// Multi-block: mark & enqueue all we see now
|
||||
attachQueueBadge(el, capped.length);
|
||||
capped.forEach((hit, idx) => {
|
||||
const subIdx = 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);
|
||||
});
|
||||
skipped += hits.length;
|
||||
found += capped.length;
|
||||
return;
|
||||
}
|
||||
|
||||
// New message that hasn't been executed → auto-execute once
|
||||
el.dataset.aiRcProcessed = '1';
|
||||
const capped = hits.slice(0, CONFIG.QUEUE_MAX_PER_MESSAGE);
|
||||
attachQueueBadge(el, capped.length);
|
||||
// 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
|
||||
});
|
||||
|
||||
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;
|
||||
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`);
|
||||
|
|
@ -2605,7 +2678,16 @@
|
|||
|
||||
enqueueCommand(element, hit, idx) {
|
||||
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 () => {
|
||||
// Micro-settle: wait for text to stabilize before parsing
|
||||
|
|
@ -2613,8 +2695,8 @@
|
|||
const blockElement = hit.blockElement;
|
||||
if (blockElement) {
|
||||
let lastText = blockElement.textContent || '';
|
||||
const maxWait = 400;
|
||||
const checkInterval = 80;
|
||||
const maxWait = 700;
|
||||
const checkInterval = 70;
|
||||
const startTime = Date.now();
|
||||
|
||||
while (Date.now() - startTime < maxWait) {
|
||||
|
|
@ -2973,6 +3055,13 @@
|
|||
|
||||
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) {
|
||||
const duration = Date.now() - started;
|
||||
RC_DEBUG?.error(`Command processing error: ${error.message}`, { messageId, duration });
|
||||
|
|
|
|||
Loading…
Reference in New Issue