Harden Scattermind bridge envelope import

This commit is contained in:
OpenClaw Bot
2026-05-27 15:40:55 +02:00
parent 1a829e05af
commit e3cff7266c
2 changed files with 138 additions and 32 deletions
+94 -31
View File
@@ -486,11 +486,61 @@ function objectFrom(value) {
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
}
function bridgeEnvelopeFrom(input = {}) {
const body = objectFrom(input);
return objectFrom(
body.rankerInput
|| body.ranker_input
|| body.rankerHandoff
|| body.ranker_handoff
|| body.rankReady
|| body.rank_ready
|| body.bridge
|| body.bridgePayload
|| body.bridge_payload
);
}
function featureSetFrom(input = {}) {
const body = objectFrom(input);
const envelope = bridgeEnvelopeFrom(body);
return objectFrom(
body.featureSet
|| body.feature_set
|| body.candidateSet
|| body.candidate_set
|| body.candidateFeatureSet
|| body.candidate_feature_set
|| body.rankReadyFeatureSet
|| body.rank_ready_feature_set
|| envelope.featureSet
|| envelope.feature_set
|| envelope.candidateSet
|| envelope.candidate_set
|| envelope.candidateFeatureSet
|| envelope.candidate_feature_set
|| envelope.rankReadyFeatureSet
|| envelope.rank_ready_feature_set
);
}
function looksLikeRankPayload(value = {}) {
return Boolean(
value.schema
|| value.featureSet
|| value.feature_set
|| value.candidateSet
|| value.candidate_set
|| value.candidateFeatureSet
|| value.candidate_feature_set
|| value.rankerInput
|| value.ranker_input
|| value.rankerHandoff
|| value.ranker_handoff
|| value.rankReady
|| value.rank_ready
|| value.bridgePayload
|| value.bridge_payload
|| value.snapshot
|| value.conceptMap
|| value.concept_map
@@ -589,19 +639,20 @@ function expandEmbeddedRankPayload(body = {}) {
}
function cleanProvenance(input = {}) {
const featureSet = objectFrom(input.featureSet || input.feature_set);
const artifact = objectFrom(input.artifact || featureSet.artifact);
const conceptMap = objectFrom(input.conceptMap || input.concept_map || featureSet.conceptMap || featureSet.concept_map || artifact.conceptMap || artifact.concept_map);
const snapshot = objectFrom(input.snapshot || featureSet.snapshot || artifact.snapshot || conceptMap.snapshot);
const source = objectFrom(input.source || featureSet.source || artifact.source);
const sourceSummary = input.sourceSummary || input.source_summary || input.opening_reflection || input.restated_idea || artifact.sourceSummary || artifact.source_summary || artifact.opening_reflection || snapshot.sourceSummary || snapshot.source_summary || snapshot.restated_idea || conceptMap.sourceSummary || conceptMap.source_summary || conceptMap.opening_reflection || conceptMap.restated_idea || '';
const envelope = bridgeEnvelopeFrom(input);
const featureSet = featureSetFrom(input);
const artifact = objectFrom(input.artifact || envelope.artifact || featureSet.artifact);
const conceptMap = objectFrom(input.conceptMap || input.concept_map || envelope.conceptMap || envelope.concept_map || featureSet.conceptMap || featureSet.concept_map || artifact.conceptMap || artifact.concept_map);
const snapshot = objectFrom(input.snapshot || envelope.snapshot || featureSet.snapshot || artifact.snapshot || conceptMap.snapshot);
const source = objectFrom(input.source || envelope.source || featureSet.source || artifact.source);
const sourceSummary = input.sourceSummary || input.source_summary || input.opening_reflection || input.restated_idea || envelope.sourceSummary || envelope.source_summary || envelope.opening_reflection || envelope.restated_idea || artifact.sourceSummary || artifact.source_summary || artifact.opening_reflection || snapshot.sourceSummary || snapshot.source_summary || snapshot.restated_idea || conceptMap.sourceSummary || conceptMap.source_summary || conceptMap.opening_reflection || conceptMap.restated_idea || '';
return {
schema: cleanText(input.schema || featureSet.schema || artifact.schema || input.type || 'prioritix-feature-set-v1', 80),
source: cleanText(input.sourceName || input.source_name || featureSet.sourceName || featureSet.source_name || source.name || artifact.sourceName || artifact.source_name || 'Scattermind', 80),
artifactId: cleanText(input.artifactId || input.artifact_id || input.sourceArtifactId || input.source_artifact_id || input.referenceCode || input.reference_code || artifact.id || source.artifactId || source.artifact_id || conceptMap.artifactId || conceptMap.artifact_id || conceptMap.id || conceptMap.referenceCode || conceptMap.reference_code || snapshot.artifactId || snapshot.artifact_id || snapshot.id || '', 120),
snapshotTitle: cleanText(input.snapshotTitle || input.snapshot_title || input.working_name || input.workingName || artifact.snapshotTitle || artifact.snapshot_title || snapshot.title || snapshot.name || conceptMap.snapshotTitle || conceptMap.snapshot_title || conceptMap.working_name || conceptMap.workingName || input.ideaTitle || input.idea_title || '', 160),
conceptMapId: cleanText(input.conceptMapId || input.concept_map_id || artifact.conceptMapId || artifact.concept_map_id || conceptMap.id || conceptMap.artifactId || conceptMap.artifact_id || input.referenceCode || input.reference_code || conceptMap.referenceCode || conceptMap.reference_code || '', 120),
originalPrompt: cleanMultiline(input.originalPrompt || input.original_prompt || input.initialPrompt || input.initial_prompt || input.ideaText || input.idea_text || input.prompt || artifact.originalPrompt || artifact.original_prompt || source.originalPrompt || source.original_prompt || snapshot.originalPrompt || snapshot.original_prompt || snapshot.prompt || conceptMap.originalPrompt || conceptMap.original_prompt || conceptMap.ideaText || conceptMap.idea_text || input.idea || '', 1200),
schema: cleanText(input.schema || envelope.schema || featureSet.schema || artifact.schema || input.type || envelope.type || 'prioritix-feature-set-v1', 80),
source: cleanText(input.sourceName || input.source_name || envelope.sourceName || envelope.source_name || featureSet.sourceName || featureSet.source_name || source.name || artifact.sourceName || artifact.source_name || 'Scattermind', 80),
artifactId: cleanText(input.artifactId || input.artifact_id || input.sourceArtifactId || input.source_artifact_id || input.referenceCode || input.reference_code || envelope.artifactId || envelope.artifact_id || envelope.sourceArtifactId || envelope.source_artifact_id || envelope.referenceCode || envelope.reference_code || artifact.id || source.artifactId || source.artifact_id || conceptMap.artifactId || conceptMap.artifact_id || conceptMap.id || conceptMap.referenceCode || conceptMap.reference_code || snapshot.artifactId || snapshot.artifact_id || snapshot.id || '', 120),
snapshotTitle: cleanText(input.snapshotTitle || input.snapshot_title || input.working_name || input.workingName || envelope.snapshotTitle || envelope.snapshot_title || envelope.working_name || envelope.workingName || artifact.snapshotTitle || artifact.snapshot_title || snapshot.title || snapshot.name || conceptMap.snapshotTitle || conceptMap.snapshot_title || conceptMap.working_name || conceptMap.workingName || input.ideaTitle || input.idea_title || envelope.ideaTitle || envelope.idea_title || '', 160),
conceptMapId: cleanText(input.conceptMapId || input.concept_map_id || envelope.conceptMapId || envelope.concept_map_id || artifact.conceptMapId || artifact.concept_map_id || conceptMap.id || conceptMap.artifactId || conceptMap.artifact_id || input.referenceCode || input.reference_code || envelope.referenceCode || envelope.reference_code || conceptMap.referenceCode || conceptMap.reference_code || '', 120),
originalPrompt: cleanMultiline(input.originalPrompt || input.original_prompt || input.initialPrompt || input.initial_prompt || input.ideaText || input.idea_text || input.prompt || envelope.originalPrompt || envelope.original_prompt || envelope.initialPrompt || envelope.initial_prompt || envelope.ideaText || envelope.idea_text || envelope.prompt || artifact.originalPrompt || artifact.original_prompt || source.originalPrompt || source.original_prompt || snapshot.originalPrompt || snapshot.original_prompt || snapshot.prompt || conceptMap.originalPrompt || conceptMap.original_prompt || conceptMap.ideaText || conceptMap.idea_text || input.idea || envelope.idea || '', 1200),
sourceSummary: cleanMultiline(sourceSummary, 1200),
};
}
@@ -643,11 +694,12 @@ function contextGuardrailText(value = '') {
}
function cleanDecisionContext(input = {}) {
const featureSet = objectFrom(input.featureSet || input.feature_set);
const artifact = objectFrom(input.artifact || featureSet.artifact);
const conceptMap = objectFrom(input.conceptMap || input.concept_map || featureSet.conceptMap || featureSet.concept_map || artifact.conceptMap || artifact.concept_map);
const snapshot = objectFrom(input.snapshot || featureSet.snapshot || artifact.snapshot || conceptMap.snapshot);
const conceptMapLenses = objectFrom(conceptMap.lenses || input.lenses || featureSet.lenses);
const envelope = bridgeEnvelopeFrom(input);
const featureSet = featureSetFrom(input);
const artifact = objectFrom(input.artifact || envelope.artifact || featureSet.artifact);
const conceptMap = objectFrom(input.conceptMap || input.concept_map || envelope.conceptMap || envelope.concept_map || featureSet.conceptMap || featureSet.concept_map || artifact.conceptMap || artifact.concept_map);
const snapshot = objectFrom(input.snapshot || envelope.snapshot || featureSet.snapshot || artifact.snapshot || conceptMap.snapshot);
const conceptMapLenses = objectFrom(conceptMap.lenses || input.lenses || envelope.lenses || featureSet.lenses);
const riskLens = conceptMapLenses.risk || conceptMapLenses.risks || conceptMapLenses.boundaries || conceptMapLenses.notYet;
const audienceLens = conceptMapLenses.audience || conceptMapLenses.who || conceptMapLenses.customer || conceptMapLenses.users;
const constraintsLens = conceptMapLenses.constraints || conceptMapLenses.boundaries || conceptMapLenses.scope;
@@ -655,11 +707,13 @@ function cleanDecisionContext(input = {}) {
const structuredContext = objectFrom(input.context);
const contextSources = [
input.decisionContext,
envelope.decisionContext,
featureSet.decisionContext,
artifact.decisionContext,
conceptMap.decisionContext,
snapshot.decisionContext,
structuredContext,
envelope.context,
conceptMap.context,
snapshot.context,
featureSet.context,
@@ -667,6 +721,7 @@ function cleanDecisionContext(input = {}) {
];
const textContextGuardrails = guardrailsFromContextText([
contextGuardrailText(input.context || ''),
contextGuardrailText(envelope.context || ''),
contextGuardrailText(featureSet.context || ''),
contextGuardrailText(artifact.context || ''),
contextGuardrailText(snapshot.context || ''),
@@ -677,20 +732,20 @@ function cleanDecisionContext(input = {}) {
snapshot.risk || snapshot.whatNotToBuildYet || snapshot.notYet || '',
].filter(Boolean).join('\n'));
return {
targetAudience: cleanText(input.targetAudience || input.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),
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 || featureSet.constraints || snapshot.constraints || conceptMap.constraints, 8, 180),
...cleanFlexibleTextList(input.constraints || envelope.constraints || featureSet.constraints || snapshot.constraints || conceptMap.constraints, 8, 180),
...collectContextList(contextSources, ['constraints', 'constraint', 'boundaries', 'scope'], 8),
...cleanSentenceList(lensContent(constraintsLens), 8, 180),
...textContextGuardrails.constraints,
], 8),
nonGoals: uniqueList([
...cleanFlexibleTextList(input.nonGoals || input.non_goals || input.avoid || featureSet.nonGoals || featureSet.non_goals || featureSet.avoid || snapshot.nonGoals || snapshot.non_goals || snapshot.avoid || conceptMap.nonGoals || conceptMap.non_goals || conceptMap.avoid, 8, 180),
...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,
], 8),
assumptions: uniqueList([
...cleanFlexibleTextList(input.assumptions || featureSet.assumptions || snapshot.assumptions || conceptMap.assumptions, 6, 180),
...cleanFlexibleTextList(input.assumptions || envelope.assumptions || featureSet.assumptions || snapshot.assumptions || conceptMap.assumptions, 6, 180),
...collectContextList(contextSources, ['assumptions', 'assumption', 'unknowns', 'openQuestions'], 6),
...cleanSentenceList(lensContent(assumptionsLens), 6, 180),
], 6),
@@ -865,16 +920,19 @@ function optionsFromBuildOrderText(text = '', sourceSection = 'concept-map.lense
}
function optionsFromBody(body = {}) {
const featureSet = objectFrom(body.featureSet || body.feature_set);
const artifact = objectFrom(body.artifact || featureSet.artifact);
const conceptMap = objectFrom(body.conceptMap || body.concept_map || featureSet.conceptMap || featureSet.concept_map || artifact.conceptMap || artifact.concept_map);
const snapshot = objectFrom(body.snapshot || featureSet.snapshot || artifact.snapshot || conceptMap.snapshot);
const conceptMapLenses = objectFrom(conceptMap.lenses || body.lenses || featureSet.lenses);
const envelope = bridgeEnvelopeFrom(body);
const featureSet = featureSetFrom(body);
const artifact = objectFrom(body.artifact || envelope.artifact || featureSet.artifact);
const conceptMap = objectFrom(body.conceptMap || body.concept_map || envelope.conceptMap || envelope.concept_map || featureSet.conceptMap || featureSet.concept_map || artifact.conceptMap || artifact.concept_map);
const snapshot = objectFrom(body.snapshot || envelope.snapshot || featureSet.snapshot || artifact.snapshot || conceptMap.snapshot);
const conceptMapLenses = objectFrom(conceptMap.lenses || body.lenses || envelope.lenses || featureSet.lenses);
const buildOrderLens = objectFrom(conceptMapLenses.channel || conceptMapLenses.buildOrder || conceptMap.buildOrder);
const directCandidateGroup = compactCandidateGroup([
{ items: body.features, sourceSection: 'features' },
{ items: envelope.features, sourceSection: 'ranker-input.features' },
{ items: featureSet.features, sourceSection: 'feature-set.features' },
{ items: body.actions, sourceSection: 'actions' },
{ items: envelope.actions, sourceSection: 'ranker-input.actions' },
{ items: featureSet.actions, sourceSection: 'feature-set.actions' },
{ items: body.nextActions || body.next_actions || body.nextSteps || body.next_steps || body.recommendedNextSteps || body.recommended_next_steps, sourceSection: 'nextActions' },
{ items: featureSet.nextActions || featureSet.next_actions || featureSet.nextSteps || featureSet.next_steps || featureSet.recommendedNextSteps || featureSet.recommended_next_steps, sourceSection: 'feature-set.nextActions' },
@@ -888,6 +946,9 @@ function optionsFromBody(body = {}) {
{ items: featureSet.experiments, sourceSection: 'feature-set.experiments', defaultLane: 'validate-next' },
{ items: featureSet.validationTests || featureSet.validation_tests, sourceSection: 'feature-set.experiments', defaultLane: 'validate-next' },
{ items: featureSet.proofTests || featureSet.proof_tests, sourceSection: 'feature-set.experiments', defaultLane: 'validate-next' },
{ items: featureSet.validateNext || featureSet.validate_next || featureSet.validate || featureSet.validation, sourceSection: 'feature-set.validateNext', defaultLane: 'validate-next' },
{ items: featureSet.deferred || featureSet.defer || featureSet.later, sourceSection: 'feature-set.deferred', defaultLane: 'defer' },
{ items: featureSet.parkingLot || featureSet.parking_lot || featureSet.park || featureSet.parked, sourceSection: 'feature-set.parkingLot', defaultLane: 'park' },
]);
const conceptMapCandidateGroup = compactCandidateGroup([
{ items: conceptMap.nextActions || conceptMap.next_actions || conceptMap.nextSteps || conceptMap.next_steps || conceptMap.recommendedNextSteps || conceptMap.recommended_next_steps, sourceSection: 'concept-map.nextActions' },
@@ -919,6 +980,7 @@ function optionsFromBody(body = {}) {
...snapshotCandidateGroup,
...conceptMapCandidateGroup,
...buildOrderSectionGroup(body.buildOrder || body.build_order, 'buildOrder'),
...buildOrderSectionGroup(envelope.buildOrder || envelope.build_order, 'ranker-input.buildOrder'),
...buildOrderSectionGroup(featureSet.buildOrder || featureSet.build_order, 'feature-set.buildOrder'),
...buildOrderSectionGroup(snapshot.buildOrder || snapshot.build_order, 'snapshot.buildOrder'),
...buildOrderSectionGroup(conceptMap.buildOrder || conceptMap.build_order, 'concept-map.buildOrder'),
@@ -943,8 +1005,8 @@ function optionsFromBody(body = {}) {
if (Array.isArray(body.options)) {
return normalizeOptionIds(body.options.slice(0, 24).map((item, index) => normalizeFeatureOption(item, index, 'option', 'options')).filter(item => item.title));
}
const fallbackText = body.optionsText || featureSet.optionsText || conceptMap.optionsText || body.idea || body.ideaText || '';
const fallbackSourceSection = body.optionsText || featureSet.optionsText || conceptMap.optionsText
const fallbackText = body.optionsText || envelope.optionsText || featureSet.optionsText || conceptMap.optionsText || body.idea || body.ideaText || envelope.idea || envelope.ideaText || '';
const fallbackSourceSection = body.optionsText || envelope.optionsText || featureSet.optionsText || conceptMap.optionsText
? 'optionsText'
: body.idea
? 'idea'
@@ -1402,9 +1464,10 @@ function createHandoffContract({ ranked, provenance, decisionContext }) {
app.post('/api/rank-feedback', (req, res) => {
const body = expandEmbeddedRankPayload(req.body || {});
const idea = cleanMultiline(body?.idea || body?.opening_reflection || body?.restated_idea || '', 3000);
const context = cleanContextText(body?.context || '');
const modeId = cleanText(body?.mode || 'progress', 40);
const envelope = bridgeEnvelopeFrom(body);
const idea = cleanMultiline(body?.idea || body?.ideaText || body?.idea_text || body?.opening_reflection || body?.restated_idea || envelope.idea || envelope.ideaText || envelope.idea_text || envelope.opening_reflection || envelope.restated_idea || '', 3000);
const context = cleanContextText(body?.context || envelope.context || '');
const modeId = cleanText(body?.mode || envelope.mode || 'progress', 40);
const mode = judgementModes[modeId] || judgementModes.progress;
const provenance = cleanProvenance(body || {});
const decisionContext = cleanDecisionContext(body || {});