Files
openclaw-backups/archive/inactive-skills/evolver/src/gep/selector.js

135 lines
4.6 KiB
JavaScript

function matchPatternToSignals(pattern, signals) {
if (!pattern || !signals || signals.length === 0) return false;
const p = String(pattern);
const sig = signals.map(s => String(s));
const regexLike = p.length >= 2 && p.startsWith('/') && p.lastIndexOf('/') > 0;
if (regexLike) {
const lastSlash = p.lastIndexOf('/');
const body = p.slice(1, lastSlash);
const flags = p.slice(lastSlash + 1);
try {
const re = new RegExp(body, flags || 'i');
return sig.some(s => re.test(s));
} catch (e) {
// fallback to substring
}
}
const needle = p.toLowerCase();
return sig.some(s => s.toLowerCase().includes(needle));
}
function scoreGene(gene, signals) {
if (!gene || gene.type !== 'Gene') return 0;
const patterns = Array.isArray(gene.signals_match) ? gene.signals_match : [];
if (patterns.length === 0) return 0;
let score = 0;
for (const pat of patterns) {
if (matchPatternToSignals(pat, signals)) score += 1;
}
return score;
}
function selectGene(genes, signals, opts) {
const bannedGeneIds = opts && opts.bannedGeneIds ? opts.bannedGeneIds : new Set();
const driftEnabled = !!(opts && opts.driftEnabled);
const preferredGeneId = opts && typeof opts.preferredGeneId === 'string' ? opts.preferredGeneId : null;
const scored = genes
.map(g => ({ gene: g, score: scoreGene(g, signals) }))
.filter(x => x.score > 0)
.sort((a, b) => b.score - a.score);
if (scored.length === 0) return { selected: null, alternatives: [] };
// Memory graph preference: only override when the preferred gene is already a match candidate.
if (preferredGeneId) {
const preferred = scored.find(x => x.gene && x.gene.id === preferredGeneId);
if (preferred && (driftEnabled || !bannedGeneIds.has(preferredGeneId))) {
const rest = scored.filter(x => x.gene && x.gene.id !== preferredGeneId);
const filteredRest = driftEnabled ? rest : rest.filter(x => x.gene && !bannedGeneIds.has(x.gene.id));
return {
selected: preferred.gene,
alternatives: filteredRest.slice(0, 4).map(x => x.gene),
};
}
}
// Low-efficiency suppression: do not repeat low-confidence paths unless drift is explicit.
const filtered = driftEnabled ? scored : scored.filter(x => x.gene && !bannedGeneIds.has(x.gene.id));
if (filtered.length === 0) return { selected: null, alternatives: scored.slice(0, 4).map(x => x.gene) };
return {
selected: filtered[0].gene,
alternatives: filtered.slice(1, 4).map(x => x.gene),
};
}
function selectCapsule(capsules, signals) {
const scored = (capsules || [])
.map(c => {
const triggers = Array.isArray(c.trigger) ? c.trigger : [];
const score = triggers.reduce((acc, t) => (matchPatternToSignals(t, signals) ? acc + 1 : acc), 0);
return { capsule: c, score };
})
.filter(x => x.score > 0)
.sort((a, b) => b.score - a.score);
return scored.length ? scored[0].capsule : null;
}
function selectGeneAndCapsule({ genes, capsules, signals, memoryAdvice, driftEnabled }) {
const bannedGeneIds =
memoryAdvice && memoryAdvice.bannedGeneIds instanceof Set ? memoryAdvice.bannedGeneIds : new Set();
const preferredGeneId = memoryAdvice && memoryAdvice.preferredGeneId ? memoryAdvice.preferredGeneId : null;
const { selected, alternatives } = selectGene(genes, signals, {
bannedGeneIds,
preferredGeneId,
driftEnabled: !!driftEnabled,
});
const capsule = selectCapsule(capsules, signals);
const selector = buildSelectorDecision({
gene: selected,
capsule,
signals,
alternatives,
memoryAdvice,
driftEnabled,
});
return {
selectedGene: selected,
capsuleCandidates: capsule ? [capsule] : [],
selector,
};
}
function buildSelectorDecision({ gene, capsule, signals, alternatives, memoryAdvice, driftEnabled }) {
const reason = [];
if (gene) reason.push('signals match gene.signals_match');
if (capsule) reason.push('capsule trigger matches signals');
if (!gene) reason.push('no matching gene found; new gene may be required');
if (signals && signals.length) reason.push(`signals: ${signals.join(', ')}`);
if (memoryAdvice && Array.isArray(memoryAdvice.explanation) && memoryAdvice.explanation.length) {
reason.push(`memory_graph: ${memoryAdvice.explanation.join(' | ')}`);
}
if (driftEnabled) {
reason.push('random_drift_override: true');
}
return {
selected: gene ? gene.id : null,
reason,
alternatives: Array.isArray(alternatives) ? alternatives.map(g => g.id) : [],
};
}
module.exports = {
selectGeneAndCapsule,
selectGene,
selectCapsule,
buildSelectorDecision,
};