212ff3ff80
Introduce UMS ingest/snapshot pipeline, Context Planner with multi-source recall, runtime context injection, canonical user mapping, and session snapshot loading on auth/me. Co-authored-by: Cursor <cursoragent@cursor.com>
92 lines
2.7 KiB
JavaScript
92 lines
2.7 KiB
JavaScript
const SOURCE_QUALITY = {
|
|
calendar: 0.95,
|
|
chat: 0.85,
|
|
meinput: 0.82,
|
|
memory_v2: 0.78,
|
|
};
|
|
|
|
function parseMs(value) {
|
|
if (!value) return null;
|
|
const ms = new Date(value).getTime();
|
|
return Number.isNaN(ms) ? null : ms;
|
|
}
|
|
|
|
function temporalMatch(item, plan) {
|
|
const mode = plan.temporal_mode ?? 'AMBIGUOUS';
|
|
const mentionStart = parseMs(plan.time?.mention_range?.start);
|
|
const mentionEnd = parseMs(plan.time?.mention_range?.end);
|
|
const eventStart = parseMs(plan.time?.event_range?.start);
|
|
const eventEnd = parseMs(plan.time?.event_range?.end);
|
|
const observed = parseMs(item.observed_time);
|
|
const event = parseMs(item.event_time);
|
|
|
|
const inMention =
|
|
observed !== null && mentionStart !== null && mentionEnd !== null
|
|
? observed >= mentionStart && observed < mentionEnd
|
|
: 0.5;
|
|
const inEvent =
|
|
event !== null && eventStart !== null && eventEnd !== null
|
|
? event >= eventStart && event < eventEnd
|
|
: inMention;
|
|
|
|
switch (mode) {
|
|
case 'OCCURRED_IN':
|
|
return event !== null ? (inEvent ? 1 : 0.2) : inMention * 0.85;
|
|
case 'MENTIONED_IN':
|
|
case 'CREATED_IN':
|
|
return inMention ? 1 : 0.25;
|
|
case 'PLANNED_IN':
|
|
case 'DUE_IN':
|
|
return event !== null ? (inEvent ? 1 : 0.3) : inMention * 0.7;
|
|
default:
|
|
return Math.max(inMention, inEvent * 0.9);
|
|
}
|
|
}
|
|
|
|
function semanticMatch(item, expandedQueries = []) {
|
|
if (!expandedQueries?.length) return 0.75;
|
|
const text = `${item.title ?? ''} ${item.content ?? ''}`;
|
|
let hits = 0;
|
|
for (const q of expandedQueries) {
|
|
if (q && text.includes(q)) hits += 1;
|
|
}
|
|
return Math.min(1, 0.45 + hits * 0.12);
|
|
}
|
|
|
|
/**
|
|
* @param {object} item
|
|
* @param {object} plan
|
|
* @param {string[]} expandedQueries
|
|
*/
|
|
export function computeRecallScore(item, plan, expandedQueries = []) {
|
|
const source_quality = SOURCE_QUALITY[item.source] ?? 0.7;
|
|
const temporal_match = temporalMatch(item, plan);
|
|
const semantic_match = semanticMatch(item, expandedQueries);
|
|
const importance = Number(item.importance ?? 0.5);
|
|
const confidence = Number(item.confidence ?? 0.8);
|
|
const recall_score =
|
|
source_quality * temporal_match * semantic_match * importance * confidence;
|
|
return Number(Math.min(1, recall_score).toFixed(4));
|
|
}
|
|
|
|
/**
|
|
* @param {object[]} items
|
|
* @param {object} plan
|
|
*/
|
|
export function rankTimelineItems(items, plan) {
|
|
const retrievalQueries = Object.fromEntries(
|
|
(plan.retrievals ?? []).map((r) => [r.source, r.expanded_queries ?? []]),
|
|
);
|
|
return items
|
|
.map((item) => ({
|
|
...item,
|
|
recall_score: computeRecallScore(
|
|
item,
|
|
plan,
|
|
retrievalQueries[item.source] ?? [],
|
|
),
|
|
}))
|
|
.filter((item) => item.recall_score >= (plan.filters?.importance_min ?? 0.35) * 0.55)
|
|
.sort((a, b) => b.recall_score - a.recall_score);
|
|
}
|