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>
126 lines
3.8 KiB
JavaScript
126 lines
3.8 KiB
JavaScript
import { searchCalendar } from './adapters/calendar.mjs';
|
|
import { searchChat } from './adapters/chat.mjs';
|
|
import { searchMeinput } from './adapters/meinput.mjs';
|
|
import { buildContextPlan } from './context-planner.mjs';
|
|
import { dedupeTimelineItems } from './dedupe.mjs';
|
|
import { rankTimelineItems } from './rank.mjs';
|
|
|
|
const SOURCE_HANDLERS = {
|
|
calendar: searchCalendar,
|
|
meinput: searchMeinput,
|
|
chat: searchChat,
|
|
};
|
|
|
|
/**
|
|
* @param {import('mysql2/promise').Pool | null} pool
|
|
* @param {{ plan: object, userId: string, sessionId?: string, limit?: number }} opts
|
|
*/
|
|
export async function executeTemporalRecall(pool, opts) {
|
|
const started = Date.now();
|
|
const plan = opts.plan;
|
|
const userId = opts.userId;
|
|
const limit = Math.min(200, Math.max(1, Number(opts.limit ?? plan.output?.max_items ?? 50)));
|
|
const skipBelow = 0.3;
|
|
|
|
const sourcesQueried = [];
|
|
const tasks = (plan.retrievals ?? [])
|
|
.filter((r) => (r.weight ?? 0) >= (r.skip_below ?? skipBelow))
|
|
.map(async (retrieval) => {
|
|
const handler = SOURCE_HANDLERS[retrieval.source];
|
|
if (!handler) return [];
|
|
sourcesQueried.push(retrieval.source);
|
|
const ctx = {
|
|
userId,
|
|
pool,
|
|
retrieval,
|
|
time: plan.time,
|
|
temporalMode: plan.temporal_mode,
|
|
sessionId: opts.sessionId,
|
|
};
|
|
if (retrieval.source === 'chat') return searchChat(pool, ctx);
|
|
return handler(ctx);
|
|
});
|
|
|
|
const batches = await Promise.all(tasks);
|
|
const raw = batches.flat();
|
|
const ranked = rankTimelineItems(raw, plan);
|
|
const deduped = plan.output?.dedupe !== false ? dedupeTimelineItems(ranked) : ranked;
|
|
const returned = deduped.slice(0, limit);
|
|
|
|
let groups = null;
|
|
if (plan.temporal_mode === 'AMBIGUOUS' && plan.output?.timeline) {
|
|
const mentionStart = new Date(plan.time.mention_range.start).getTime();
|
|
const mentionEnd = new Date(plan.time.mention_range.end).getTime();
|
|
const occurred = [];
|
|
const mentioned = [];
|
|
for (const item of returned) {
|
|
const obs = new Date(item.observed_time).getTime();
|
|
if (item.event_time) {
|
|
const ev = new Date(item.event_time).getTime();
|
|
if (ev >= mentionStart && ev < mentionEnd) occurred.push(item);
|
|
else mentioned.push(item);
|
|
} else if (obs >= mentionStart && obs < mentionEnd) {
|
|
mentioned.push(item);
|
|
} else {
|
|
occurred.push(item);
|
|
}
|
|
}
|
|
groups = [
|
|
{ label: 'occurred_in_range', items: occurred },
|
|
{ label: 'mentioned_or_planned', items: mentioned },
|
|
];
|
|
}
|
|
|
|
return {
|
|
query_type: plan.query_type,
|
|
temporal_mode: plan.temporal_mode,
|
|
time_range: plan.time.mention_range,
|
|
groups,
|
|
items: groups ? [] : returned,
|
|
stats: {
|
|
sources_queried: sourcesQueried,
|
|
raw_count: raw.length,
|
|
deduped_count: deduped.length,
|
|
returned_count: returned.length,
|
|
elapsed_ms: Date.now() - started,
|
|
},
|
|
plan,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {import('mysql2/promise').Pool | null} pool
|
|
* @param {{ query?: string, plan?: object, user_id: string, now?: Date, session_id?: string, limit?: number }} input
|
|
*/
|
|
export async function queryTemporalRecall(pool, input) {
|
|
const userId = input.user_id;
|
|
const plan =
|
|
input.plan ??
|
|
buildContextPlan({
|
|
query: input.query ?? '',
|
|
user_id: userId,
|
|
now: input.now,
|
|
});
|
|
|
|
if (plan.context_needs?.temporal_recall === 'SKIP') {
|
|
return {
|
|
query_type: plan.query_type,
|
|
temporal_mode: plan.temporal_mode,
|
|
time_range: plan.time?.mention_range ?? null,
|
|
groups: null,
|
|
items: [],
|
|
stats: { sources_queried: [], raw_count: 0, deduped_count: 0, returned_count: 0, elapsed_ms: 0 },
|
|
plan,
|
|
};
|
|
}
|
|
|
|
return executeTemporalRecall(pool, {
|
|
plan,
|
|
userId,
|
|
sessionId: input.session_id,
|
|
limit: input.limit,
|
|
});
|
|
}
|
|
|
|
export { buildContextPlan };
|