Files
odysseus/scripts/tool_followup_oracle.mjs
T

31 lines
1.4 KiB
JavaScript

/** Availability is separate from execution and answer correctness. */
export function capabilityAvailable(contract, capability, expectedTools = []) {
const offered = (contract.offered || []).map(name => String(name).replace(/^mcp__email__/, ''));
return capability === null
|| (contract.active_capabilities || contract.capabilities || []).includes(capability)
|| (contract.routing_experiment === 'recent_model_choice'
&& expectedTools.some(tool => offered.includes(tool)));
}
/** Compare source steps in memory; callers retain booleans, never private text. */
export function skillDetailEvidence(output, answer) {
let text = String(output || '');
for (let i = 0; i < 3; i++) {
try {
const parsed = JSON.parse(text);
const inner = parsed.stdout ?? parsed.results ?? parsed.response;
if (typeof inner !== 'string') break;
text = inner;
} catch { break; }
}
const normalize = value => value.replace(/[`*_]/g, '').replace(/\s+/g, ' ').trim().toLowerCase();
const steps = [];
let selected = false;
for (const line of text.split('\n')) {
const heading = line.match(/^#{1,6}\s+(.+)/);
if (heading) { selected = /^(?:procedure|verification)$/i.test(heading[1].trim()); continue; }
if (selected && line.trim()) steps.push(normalize(line.replace(/^\s*(?:\d+[.)]|[-*])\s+/, '')));
}
return {steps: steps.length, covered: steps.length > 0 && steps.every(step => normalize(answer).includes(step))};
}