Expose bridge handoff status in Ranker results

This commit is contained in:
OpenClaw Bot
2026-05-27 01:08:27 +02:00
parent bf451fad3e
commit b69d7e5386
2 changed files with 67 additions and 3 deletions
+66 -3
View File
@@ -52,10 +52,14 @@ function laneClass(lane) {
} }
function parsePastedJsonPayload(value) { function parsePastedJsonPayload(value) {
const text = String(value || '').trim(); const text = String(value || '').trim()
if (!text.startsWith('{') || !text.endsWith('}')) return null; .replace(/^```(?:json)?\s*/i, '')
.replace(/```$/i, '')
.trim();
const jsonText = text.startsWith('{') && text.endsWith('}') ? text : extractFirstJsonObject(text);
if (!jsonText) return null;
try { try {
const parsed = JSON.parse(text); const parsed = JSON.parse(jsonText);
const looksLikeBridgePayload = parsed && typeof parsed === 'object' && !Array.isArray(parsed) && ( const looksLikeBridgePayload = parsed && typeof parsed === 'object' && !Array.isArray(parsed) && (
parsed.schema || parsed.featureSet || parsed.conceptMap || parsed.lenses || parsed.reference_code || parsed.referenceCode || parsed.artifactId || parsed.ideaText parsed.schema || parsed.featureSet || parsed.conceptMap || parsed.lenses || parsed.reference_code || parsed.referenceCode || parsed.artifactId || parsed.ideaText
); );
@@ -65,6 +69,36 @@ function parsePastedJsonPayload(value) {
} }
} }
function extractFirstJsonObject(text = '') {
const start = text.indexOf('{');
if (start < 0) return '';
let depth = 0;
let inString = false;
let escaped = false;
for (let index = start; index < text.length; index += 1) {
const char = text[index];
if (escaped) {
escaped = false;
continue;
}
if (char === '\\' && inString) {
escaped = true;
continue;
}
if (char === '"') {
inString = !inString;
continue;
}
if (inString) continue;
if (char === '{') depth += 1;
if (char === '}') {
depth -= 1;
if (depth === 0) return text.slice(start, index + 1);
}
}
return '';
}
function payloadFromForm(formPayload) { function payloadFromForm(formPayload) {
const ideaJson = parsePastedJsonPayload(formPayload.idea); const ideaJson = parsePastedJsonPayload(formPayload.idea);
const optionsJson = parsePastedJsonPayload(formPayload.optionsText); const optionsJson = parsePastedJsonPayload(formPayload.optionsText);
@@ -94,6 +128,33 @@ function renderPills(items = []) {
return `<div class="signal-pills">${items.map((item) => `<span>${escapeHtml(item)}</span>`).join('')}</div>`; return `<div class="signal-pills">${items.map((item) => `<span>${escapeHtml(item)}</span>`).join('')}</div>`;
} }
function renderSourceTrace(sourceTrace = {}) {
const hasTrace = sourceTrace.sourceSection || sourceTrace.sourceId || sourceTrace.sourceQuote;
if (!hasTrace) return '';
const label = [sourceTrace.sourceTitle, sourceTrace.sourceId || sourceTrace.sourceSection].filter(Boolean).join(' · ');
return `
<div class="source-trace">
<span>Source trace</span>
${label ? `<b>${escapeHtml(label)}</b>` : ''}
${sourceTrace.sourceQuote ? `<p>${escapeHtml(sourceTrace.sourceQuote)}</p>` : ''}
</div>
`;
}
function renderHandoffStatus(handoff = {}) {
const readiness = handoff.readiness || {};
if (!readiness.status) return '';
const warnings = handoff.warnings || [];
return `
<article class="brief-card handoff-card status-${escapeHtml(readiness.status)}">
<span>Bridge handoff readiness</span>
<p><b>${escapeHtml(readiness.label || readiness.status)}</b> — ${escapeHtml(readiness.summary || '')}</p>
${(readiness.nextChecks || []).length ? `<ol>${readiness.nextChecks.map((step) => `<li>${escapeHtml(step)}</li>`).join('')}</ol>` : ''}
${warnings.length ? `<div class="handoff-warnings">${warnings.map((warning) => `<code>${escapeHtml(warning)}</code>`).join('')}</div>` : ''}
</article>
`;
}
function renderRankCard(item) { function renderRankCard(item) {
return ` return `
<article class="rank-card ${laneClass(item.lane)}"> <article class="rank-card ${laneClass(item.lane)}">
@@ -198,6 +259,7 @@ function renderResults(data) {
<div><span>Proof to collect</span><p>${escapeHtml(glance.evidenceQuestion || 'Name the evidence that would change the ranking.')}</p></div> <div><span>Proof to collect</span><p>${escapeHtml(glance.evidenceQuestion || 'Name the evidence that would change the ranking.')}</p></div>
<div><span>Trap to avoid</span><p>${escapeHtml(glance.biggestTrap || brief.caution || 'Do not treat first-pass judgement as final truth.')}</p></div> <div><span>Trap to avoid</span><p>${escapeHtml(glance.biggestTrap || brief.caution || 'Do not treat first-pass judgement as final truth.')}</p></div>
</section> </section>
${renderSourceTrace(glance.sourceTrace || {})}
<div class="result-actions" aria-label="Copy result actions"> <div class="result-actions" aria-label="Copy result actions">
<button class="button ghost" type="button" id="copyBrief">Copy decision brief</button> <button class="button ghost" type="button" id="copyBrief">Copy decision brief</button>
@@ -218,6 +280,7 @@ function renderResults(data) {
<span>Rank confidence</span> <span>Rank confidence</span>
<p><b>${escapeHtml(data.rankConfidence?.level || 'First pass')}</b> — ${escapeHtml(data.rankConfidence?.reason || brief.caution || '')}</p> <p><b>${escapeHtml(data.rankConfidence?.level || 'First pass')}</b> — ${escapeHtml(data.rankConfidence?.reason || brief.caution || '')}</p>
</article> </article>
${renderHandoffStatus(data.handoff || {})}
${(brief.whatWouldChangeRanking || []).length ? ` ${(brief.whatWouldChangeRanking || []).length ? `
<article class="brief-card next-card"> <article class="brief-card next-card">
<span>What would change the order</span> <span>What would change the order</span>
+1
View File
@@ -39,3 +39,4 @@ button,input,textarea{font:inherit} button{cursor:pointer} a{color:inherit;text-
.lane-column .rank-card{box-shadow:none;margin:0;border-width:1.5px}.lane-column .rank-card h3{font-size:clamp(20px,2vw,28px)}.lane-column .metrics{grid-template-columns:1fr}.signal-pills{margin:10px 0}.signal-pills span{background:#f0e8d9;font-size:10px;padding:5px 8px}.action-strip{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:14px 0}.action-strip>div{border:1.5px solid var(--hair);background:#fff6e6;padding:10px}.action-strip span{color:var(--blue2);font-size:10px}.action-strip p{margin:0;font-size:13px;line-height:1.35}.reflection-room{margin-top:24px}.expert-card{position:relative}.expert-card::before{content:"“";position:absolute;right:14px;top:0;font-size:72px;line-height:1;color:rgba(36,92,255,.16);font-weight:1000} .lane-column .rank-card{box-shadow:none;margin:0;border-width:1.5px}.lane-column .rank-card h3{font-size:clamp(20px,2vw,28px)}.lane-column .metrics{grid-template-columns:1fr}.signal-pills{margin:10px 0}.signal-pills span{background:#f0e8d9;font-size:10px;padding:5px 8px}.action-strip{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:14px 0}.action-strip>div{border:1.5px solid var(--hair);background:#fff6e6;padding:10px}.action-strip span{color:var(--blue2);font-size:10px}.action-strip p{margin:0;font-size:13px;line-height:1.35}.reflection-room{margin-top:24px}.expert-card{position:relative}.expert-card::before{content:"“";position:absolute;right:14px;top:0;font-size:72px;line-height:1;color:rgba(36,92,255,.16);font-weight:1000}
@media (max-width:1100px){.lane-board{grid-template-columns:repeat(2,minmax(0,1fr))}.quick-glance{grid-template-columns:repeat(2,minmax(0,1fr))}.action-strip{grid-template-columns:1fr}} @media (max-width:1100px){.lane-board{grid-template-columns:repeat(2,minmax(0,1fr))}.quick-glance{grid-template-columns:repeat(2,minmax(0,1fr))}.action-strip{grid-template-columns:1fr}}
@media (max-width:700px){.lane-board,.quick-glance{grid-template-columns:1fr}.memo-head::after{position:static;display:inline-block;margin-top:14px;transform:none}.result-actions .button{width:100%;justify-content:center}.quick-glance{box-shadow:6px 6px 0 var(--ink)}.quick-glance>div{min-height:auto}} @media (max-width:700px){.lane-board,.quick-glance{grid-template-columns:1fr}.memo-head::after{position:static;display:inline-block;margin-top:14px;transform:none}.result-actions .button{width:100%;justify-content:center}.quick-glance{box-shadow:6px 6px 0 var(--ink)}.quick-glance>div{min-height:auto}}
.source-trace{margin:-4px 0 22px;padding:14px 16px;border:2px dashed var(--ink);background:#fff6e5;color:var(--ink2);box-shadow:5px 5px 0 rgba(21,19,15,.16)}.source-trace span,.handoff-card>span{display:block;margin-bottom:8px;color:var(--blue2);text-transform:uppercase;letter-spacing:.12em;font-size:11px;font-weight:1000}.source-trace b{display:block;margin-bottom:6px;font-size:14px}.source-trace p{margin:0;color:var(--muted);line-height:1.45}.handoff-card{border-left:8px solid var(--green)}.handoff-card.status-usable-with-warnings{border-left-color:var(--amber)}.handoff-card.status-needs-source-context,.handoff-card.status-blocked{border-left-color:var(--red)}.handoff-warnings{display:grid;gap:6px;margin-top:12px}.handoff-warnings code{display:block;white-space:normal;border:1px solid var(--hair);background:#f3eee4;padding:7px 9px;font-size:12px;color:var(--ink2)}