Accept pasted Scattermind concept maps

This commit is contained in:
OpenClaw Bot
2026-05-27 00:19:33 +02:00
parent 771b5e7c02
commit 4b3fb9e7d9
5 changed files with 122 additions and 18 deletions
+61 -11
View File
@@ -468,6 +468,55 @@ function objectFrom(value) {
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
}
function looksLikeRankPayload(value = {}) {
return Boolean(
value.schema
|| value.featureSet
|| value.conceptMap
|| value.lenses
|| value.reference_code
|| value.referenceCode
|| value.artifactId
|| value.sourceArtifactId
|| value.ideaText
|| Array.isArray(value.features)
|| Array.isArray(value.actions)
|| Array.isArray(value.nextMoves)
|| Array.isArray(value.candidates)
);
}
function parseEmbeddedRankPayload(value = '') {
const text = cleanMultiline(value, 12000);
if (!text.startsWith('{') || !text.endsWith('}')) return null;
try {
const parsed = JSON.parse(text);
return looksLikeRankPayload(parsed) ? parsed : null;
} catch {
return null;
}
}
function expandEmbeddedRankPayload(body = {}) {
const original = objectFrom(body);
for (const key of ['payload', 'rankPayload', 'scattermindPayload', 'conceptMapJson', 'idea', 'ideaText', 'optionsText']) {
const embedded = typeof original[key] === 'string'
? parseEmbeddedRankPayload(original[key])
: looksLikeRankPayload(original[key])
? original[key]
: null;
if (!embedded) continue;
const expanded = { ...original, ...embedded };
if (key === 'idea' && !embedded.idea && !embedded.ideaText) expanded.idea = '';
if (key === 'optionsText' && !embedded.optionsText) expanded.optionsText = '';
if (original.mode && !embedded.mode) expanded.mode = original.mode;
if (original.context && !embedded.context) expanded.context = original.context;
expanded._embeddedPayloadSource = key;
return expanded;
}
return original;
}
function cleanProvenance(input = {}) {
const featureSet = objectFrom(input.featureSet);
const artifact = objectFrom(input.artifact || featureSet.artifact);
@@ -519,10 +568,10 @@ function cleanDecisionContext(input = {}) {
const artifact = objectFrom(input.artifact || featureSet.artifact);
const conceptMap = objectFrom(input.conceptMap || featureSet.conceptMap || artifact.conceptMap);
const conceptMapLenses = objectFrom(conceptMap.lenses || input.lenses || featureSet.lenses);
const riskLens = objectFrom(conceptMapLenses.risk || conceptMapLenses.risks || conceptMapLenses.boundaries || conceptMapLenses.notYet);
const audienceLens = objectFrom(conceptMapLenses.audience || conceptMapLenses.who || conceptMapLenses.customer || conceptMapLenses.users);
const constraintsLens = objectFrom(conceptMapLenses.constraints || conceptMapLenses.boundaries || conceptMapLenses.scope);
const assumptionsLens = objectFrom(conceptMapLenses.assumptions || conceptMapLenses.unknowns || conceptMapLenses.openQuestions);
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;
const assumptionsLens = conceptMapLenses.assumptions || conceptMapLenses.unknowns || conceptMapLenses.openQuestions;
const structuredContext = objectFrom(input.context);
const contextSources = [
input.decisionContext,
@@ -1050,13 +1099,14 @@ function createHandoffContract({ ranked, provenance, decisionContext }) {
}
app.post('/api/rank-feedback', (req, res) => {
const idea = cleanMultiline(req.body?.idea || '', 3000);
const context = cleanContextText(req.body?.context || '');
const modeId = cleanText(req.body?.mode || 'progress', 40);
const body = expandEmbeddedRankPayload(req.body || {});
const idea = cleanMultiline(body?.idea || '', 3000);
const context = cleanContextText(body?.context || '');
const modeId = cleanText(body?.mode || 'progress', 40);
const mode = judgementModes[modeId] || judgementModes.progress;
const provenance = cleanProvenance(req.body || {});
const decisionContext = cleanDecisionContext(req.body || {});
let options = optionsFromBody(req.body || {});
const provenance = cleanProvenance(body || {});
const decisionContext = cleanDecisionContext(body || {});
let options = optionsFromBody(body || {});
if (options.length < 2) return res.status(400).json({ error: 'Paste at least two options, features, ideas, or next moves to rank.' });
const bridgeContext = `${idea}\n${context}\n${provenance.snapshotTitle}\n${provenance.schema}\n${decisionContext.targetAudience}\n${decisionContext.constraints.join('\n')}\n${decisionContext.assumptions.join('\n')}`;
options = options.map(option => ({ ...option, metrics: scoreOption(option, mode, bridgeContext, decisionContext) }))
@@ -1081,7 +1131,7 @@ app.post('/api/rank-feedback', (req, res) => {
res.json({
ok: true,
mode: { id: modeId in judgementModes ? modeId : 'progress', label: mode.label },
input: { idea, context, optionCount: options.length, provenance, decisionContext },
input: { idea, context, optionCount: options.length, provenance, decisionContext, embeddedPayloadSource: body._embeddedPayloadSource || '' },
ranked: options,
brief,
rankConfidence: rankConfidenceFor(options),