Harden rank feedback duplicate ids

This commit is contained in:
OpenClaw Bot
2026-05-26 23:07:08 +02:00
parent 7fd2430914
commit 2f49155ed8
3 changed files with 54 additions and 5 deletions
+25 -3
View File
@@ -477,6 +477,25 @@ function normalizeFeatureOption(item, index, fallbackId = 'feature', defaultSour
};
}
function normalizeOptionIds(options = []) {
const seen = new Map();
return options.map((option, index) => {
const baseId = cleanText(option.id || `option-${index + 1}`, 80) || `option-${index + 1}`;
const count = seen.get(baseId) || 0;
seen.set(baseId, count + 1);
if (count === 0) return { ...option, id: baseId };
return {
...option,
id: cleanText(`${baseId}-${count + 1}`, 80),
provenance: {
...(option.provenance || {}),
originalId: baseId,
idNormalized: true,
},
};
});
}
function candidateArrayFrom(...entries) {
return entries.find(entry => Array.isArray(entry?.items)) || null;
}
@@ -500,12 +519,12 @@ function optionsFromBody(body = {}) {
);
if (rawCandidates) {
const fallbackId = rawCandidates.sourceSection.toLowerCase().includes('action') ? 'action' : 'feature';
return rawCandidates.items.slice(0, 24).map((item, index) => normalizeFeatureOption(item, index, fallbackId, rawCandidates.sourceSection)).filter(item => item.title);
return normalizeOptionIds(rawCandidates.items.slice(0, 24).map((item, index) => normalizeFeatureOption(item, index, fallbackId, rawCandidates.sourceSection)).filter(item => item.title));
}
if (Array.isArray(body.options)) {
return body.options.slice(0, 24).map((item, index) => normalizeFeatureOption(item, index, 'option', 'options')).filter(item => item.title);
return normalizeOptionIds(body.options.slice(0, 24).map((item, index) => normalizeFeatureOption(item, index, 'option', 'options')).filter(item => item.title));
}
return parseOptionsFromText(body.optionsText || featureSet.optionsText || conceptMap.optionsText || '');
return normalizeOptionIds(parseOptionsFromText(body.optionsText || featureSet.optionsText || conceptMap.optionsText || ''));
}
function scoreOption(option, mode, context = '', decisionContext = {}) {
@@ -664,6 +683,7 @@ function createHandoffContract({ ranked, provenance, decisionContext }) {
const itemTrace = ranked.map(item => {
if (!item.provenance?.sourceSection) warnings.push(`missing source section for ${item.id}`);
if (item.provenance?.idNormalized) warnings.push(`duplicate source id ${item.provenance.originalId} normalized to ${item.id}`);
if (!item.factors?.evidenceNeeded && ['do', 'test'].includes(item.lane?.id)) warnings.push(`missing evidence needed for active item ${item.id}`);
if (item.metrics?.nonGoalConflicts?.length && ['do', 'test'].includes(item.lane?.id)) warnings.push(`active item ${item.id} conflicts with source non-goals: ${item.metrics.nonGoalConflicts.join('; ')}`);
return {
@@ -672,6 +692,8 @@ function createHandoffContract({ ranked, provenance, decisionContext }) {
lane: item.lane?.id || 'defer',
sourceSection: item.provenance?.sourceSection || '',
sourceId: item.provenance?.sourceId || '',
originalId: item.provenance?.originalId || '',
idNormalized: Boolean(item.provenance?.idNormalized),
evidenceNeeded: item.factors?.evidenceNeeded || '',
nonGoalConflicts: item.metrics?.nonGoalConflicts || [],
};