Add route guardrails to Ranker handoff

This commit is contained in:
OpenClaw Bot
2026-05-27 19:09:29 +02:00
parent 39287ea2e3
commit bff9b1e7c3
3 changed files with 77 additions and 2 deletions
+41
View File
@@ -829,6 +829,27 @@ function guardrailTextItems(value = [], maxItems = 8) {
return cleanFlexibleTextList(value, maxItems, 260).filter(item => /\b(avoid|no|do not|don't|dont|must not|never|non-goal|non goal|not yet|out of scope|defer|hold for later|set aside|probably noise|park|do not let this become|don't let this become|not a dashboard|dashboard swamp)\b/i.test(item));
}
function inferIdeaRouteFromText(value = '') {
const text = String(value || '').toLowerCase();
if (/\b(game|games|gaming|player|players|playtest|prototype|steam|itch|console|pc|deck|roguelike|roguelite|rpg|shooter|bullet\s*hell|bullet-heaven|survival|enemy|enemies|weapon|weapons|level|arena|controller|joystick|driving|car|vehicle|combat|fps|platformer|metroidvania|puzzle game)\b/.test(text)) return 'game';
if (/\b(service|agency|consulting|freelance|offer|client|local|shop|restaurant|webshop|sales|appointment)\b/.test(text)) return 'service_business';
if (/\b(book|novel|story|comic|film|music|art|creative|writing|podcast|newsletter|course|zine)\b/.test(text)) return 'creative_project';
if (/\b(community|members|audience|creator|content|social|discord|forum|club|fans|followers)\b/.test(text)) return 'content_community';
if (/\b(physical|hardware|device|toy|wearable|material|manufactur|packaging|retail|kitchen|furniture|sensor)\b/.test(text)) return 'physical_product';
if (/\b(internal|ops|admin|backoffice|team tool|employee|staff|inventory|support queue|process)\b/.test(text)) return 'internal_tool';
if (/\b(saas|app|software|dashboard|workflow|api|plugin|extension|tool|users?|accounts?|subscription|crm|automation|webapp|mobile app)\b/.test(text)) return 'saas_app';
return '';
}
function routeGuardrailNonGoals(route = '') {
if (route === 'game') {
return [
'Avoid accounts, dashboards, workspaces, collaboration, CRM, admin panels, onboarding, saved boards, and subscription tiers unless the source explicitly asks for business software.',
];
}
return [];
}
function cleanDecisionContext(input = {}) {
const envelope = bridgeEnvelopeFrom(input);
const featureSet = featureSetFrom(input);
@@ -873,7 +894,24 @@ function cleanDecisionContext(input = {}) {
...guardrailTextItems(featureSet.threads_to_hold || featureSet.threadsToHold || featureSet.actionThreads || featureSet.action_threads, 8),
...guardrailTextItems(input.threads_to_hold || input.threadsToHold || input.actionThreads || input.action_threads, 8),
].filter(Boolean).join('\n'));
const ideaRoute = cleanText(input.ideaType || input.idea_type || input.category || input.route || envelope.ideaType || envelope.idea_type || featureSet.ideaType || featureSet.idea_type || artifact.ideaType || artifact.idea_type || conceptMap.ideaType || conceptMap.idea_type || snapshot.ideaType || snapshot.idea_type || inferIdeaRouteFromText([
input.idea,
input.ideaText,
input.idea_text,
input.context,
input.opening_reflection,
input.restated_idea,
envelope.idea,
envelope.ideaText,
envelope.context,
conceptMap.opening_reflection,
conceptMap.restated_idea,
snapshot.restated_idea,
lensContent(conceptMapLenses.shape),
lensContent(conceptMapLenses.channel),
].filter(Boolean).join('\n')), 60);
return {
ideaRoute,
targetAudience: cleanText(input.targetAudience || input.target_audience || envelope.targetAudience || envelope.target_audience || featureSet.targetAudience || featureSet.target_audience || snapshot.targetAudience || snapshot.target_audience || firstContextText(contextSources, ['targetAudience', 'target_audience', 'audience', 'who', 'whoItHelps', 'who_it_helps', 'customer', 'users']) || conceptMap.targetAudience || conceptMap.target_audience || lensContent(audienceLens), 180),
constraints: uniqueList([
...cleanFlexibleTextList(input.constraints || envelope.constraints || featureSet.constraints || snapshot.constraints || conceptMap.constraints, 8, 180),
@@ -885,6 +923,7 @@ function cleanDecisionContext(input = {}) {
...cleanFlexibleTextList(input.nonGoals || input.non_goals || input.avoid || envelope.nonGoals || envelope.non_goals || envelope.avoid || featureSet.nonGoals || featureSet.non_goals || featureSet.avoid || snapshot.nonGoals || snapshot.non_goals || snapshot.avoid || conceptMap.nonGoals || conceptMap.non_goals || conceptMap.avoid, 8, 180),
...collectContextList(contextSources, ['nonGoals', 'non_goals', 'nonGoal', 'non_goal', 'avoid', 'notYet', 'not_yet', 'doNotBuild', 'do_not_build'], 8),
...textContextGuardrails.nonGoals,
...routeGuardrailNonGoals(ideaRoute),
], 8),
assumptions: uniqueList([
...cleanFlexibleTextList(input.assumptions || envelope.assumptions || featureSet.assumptions || snapshot.assumptions || conceptMap.assumptions, 6, 180),
@@ -1786,6 +1825,7 @@ function copyableHandoffText({ ranked = [], provenance = {}, decisionContext = {
? `Source summary: ${cleanText(provenance.sourceSummary, 240)}`
: '';
const contextLines = [
decisionContext?.ideaRoute ? `Idea route: ${decisionContext.ideaRoute}` : '',
decisionContext?.targetAudience ? `Audience: ${decisionContext.targetAudience}` : '',
...(decisionContext?.constraints || []).slice(0, 3).map(item => `Constraint: ${item}`),
...(decisionContext?.nonGoals || []).slice(0, 3).map(item => `Non-goal: ${item}`),
@@ -1877,6 +1917,7 @@ function createHandoffContract({ ranked, provenance, decisionContext }) {
requiresSourceTrace: expectsSourceTrace,
},
decisionContext: {
ideaRoute: decisionContext?.ideaRoute || '',
targetAudience: decisionContext?.targetAudience || '',
constraints: decisionContext?.constraints || [],
nonGoals: decisionContext?.nonGoals || [],