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>
159 lines
5.0 KiB
JavaScript
159 lines
5.0 KiB
JavaScript
const TZ_OFFSET_MIN = Number(process.env.TEMPORAL_RECALL_TZ_OFFSET_MIN ?? 480);
|
|
|
|
function pad2(n) {
|
|
return String(n).padStart(2, '0');
|
|
}
|
|
|
|
/** @param {Date} d */
|
|
function toLocalParts(d) {
|
|
const shifted = new Date(d.getTime() + TZ_OFFSET_MIN * 60_000);
|
|
return {
|
|
year: shifted.getUTCFullYear(),
|
|
month: shifted.getUTCMonth(),
|
|
day: shifted.getUTCDate(),
|
|
dow: shifted.getUTCDay(),
|
|
hour: shifted.getUTCHours(),
|
|
};
|
|
}
|
|
|
|
/** @param {{ year: number, month: number, day: number }} p @param {number} h @param {number} m */
|
|
function partsToIso(p, h = 0, m = 0) {
|
|
const utcMs =
|
|
Date.UTC(p.year, p.month, p.day, h, m, 0, 0) - TZ_OFFSET_MIN * 60_000;
|
|
return new Date(utcMs).toISOString();
|
|
}
|
|
|
|
/** @param {Date} anchor */
|
|
function startOfDay(anchor) {
|
|
const p = toLocalParts(anchor);
|
|
return new Date(partsToIso(p, 0, 0));
|
|
}
|
|
|
|
/** @param {Date} anchor */
|
|
function endOfDay(anchor) {
|
|
const p = toLocalParts(anchor);
|
|
const start = Date.UTC(p.year, p.month, p.day + 1, 0, 0, 0, 0) - TZ_OFFSET_MIN * 60_000;
|
|
return new Date(start);
|
|
}
|
|
|
|
/** @param {Date} anchor @param {number} deltaDays */
|
|
function addDays(anchor, deltaDays) {
|
|
const p = toLocalParts(anchor);
|
|
const ms = Date.UTC(p.year, p.month, p.day + deltaDays, 0, 0, 0, 0) - TZ_OFFSET_MIN * 60_000;
|
|
return new Date(ms);
|
|
}
|
|
|
|
/** @param {Date} anchor */
|
|
function startOfWeek(anchor) {
|
|
const p = toLocalParts(anchor);
|
|
const mondayOffset = p.dow === 0 ? -6 : 1 - p.dow;
|
|
const ms =
|
|
Date.UTC(p.year, p.month, p.day + mondayOffset, 0, 0, 0, 0) -
|
|
TZ_OFFSET_MIN * 60_000;
|
|
return new Date(ms);
|
|
}
|
|
|
|
/** @param {Date} anchor */
|
|
function endOfWeek(anchor) {
|
|
const start = startOfWeek(anchor);
|
|
return addDays(start, 7);
|
|
}
|
|
|
|
/** @param {Date} anchor */
|
|
function startOfMonth(anchor) {
|
|
const p = toLocalParts(anchor);
|
|
return new Date(Date.UTC(p.year, p.month, 1, 0, 0, 0, 0) - TZ_OFFSET_MIN * 60_000);
|
|
}
|
|
|
|
/** @param {Date} anchor */
|
|
function endOfMonth(anchor) {
|
|
const p = toLocalParts(anchor);
|
|
return new Date(Date.UTC(p.year, p.month + 1, 1, 0, 0, 0, 0) - TZ_OFFSET_MIN * 60_000);
|
|
}
|
|
|
|
/**
|
|
* @param {string} query
|
|
* @param {Date} [now]
|
|
* @returns {{ mention_range: { start: string, end: string }, event_range: { start: string, end: string } | null, relative_label: string | null, rule_hits: string[] }}
|
|
*/
|
|
export function parseTimeScope(query, now = new Date()) {
|
|
const text = String(query ?? '');
|
|
const rule_hits = [];
|
|
let relative_label = null;
|
|
let rangeStart = null;
|
|
let rangeEnd = null;
|
|
|
|
const patterns = [
|
|
{ re: /前天/, label: 'day_before_yesterday', apply: () => {
|
|
const d = addDays(now, -2);
|
|
return [startOfDay(d), endOfDay(d)];
|
|
}},
|
|
{ re: /昨天|昨日/, label: 'yesterday', apply: () => {
|
|
const d = addDays(now, -1);
|
|
return [startOfDay(d), endOfDay(d)];
|
|
}},
|
|
{ re: /今天|今日/, label: 'today', apply: () => [startOfDay(now), endOfDay(now)] },
|
|
{ re: /明天|明日/, label: 'tomorrow', apply: () => {
|
|
const d = addDays(now, 1);
|
|
return [startOfDay(d), endOfDay(d)];
|
|
}},
|
|
{ re: /后天/, label: 'day_after_tomorrow', apply: () => {
|
|
const d = addDays(now, 2);
|
|
return [startOfDay(d), endOfDay(d)];
|
|
}},
|
|
{ re: /上周|上星期|上个星期/, label: 'last_week', apply: () => {
|
|
const thisWeek = startOfWeek(now);
|
|
const lastStart = addDays(thisWeek, -7);
|
|
return [lastStart, thisWeek];
|
|
}},
|
|
{ re: /这周|本周|这个星期|这星期/, label: 'this_week', apply: () => [startOfWeek(now), endOfWeek(now)] },
|
|
{ re: /上个月/, label: 'last_month', apply: () => {
|
|
const thisMonth = startOfMonth(now);
|
|
const p = toLocalParts(thisMonth);
|
|
const lastStart = new Date(Date.UTC(p.year, p.month - 1, 1, 0, 0, 0, 0) - TZ_OFFSET_MIN * 60_000);
|
|
return [lastStart, thisMonth];
|
|
}},
|
|
{ re: /这个月|本月/, label: 'this_month', apply: () => [startOfMonth(now), endOfMonth(now)] },
|
|
{ re: /最近(\d+)天/, label: 'recent_days', apply: (m) => {
|
|
const days = Number(m[1]);
|
|
return [addDays(now, -days), endOfDay(now)];
|
|
}},
|
|
{ re: /最近一周|最近1周/, label: 'recent_week', apply: () => [addDays(now, -7), endOfDay(now)] },
|
|
];
|
|
|
|
for (const { re, label, apply } of patterns) {
|
|
const m = text.match(re);
|
|
if (m) {
|
|
[rangeStart, rangeEnd] = apply(m);
|
|
relative_label = label;
|
|
rule_hits.push(`time:${label}`);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!rangeStart) {
|
|
if (/最近|近期|这几天/.test(text)) {
|
|
[rangeStart, rangeEnd] = [addDays(now, -7), endOfDay(now)];
|
|
relative_label = 'recent_week';
|
|
rule_hits.push('time:recent_fuzzy');
|
|
} else {
|
|
[rangeStart, rangeEnd] = [addDays(now, -7), endOfDay(now)];
|
|
relative_label = 'this_week';
|
|
rule_hits.push('time:default_week');
|
|
}
|
|
}
|
|
|
|
const mention_range = {
|
|
start: rangeStart.toISOString(),
|
|
end: rangeEnd.toISOString(),
|
|
};
|
|
|
|
let event_range = null;
|
|
if (/安排|行程|会议|约会|几点|日历/.test(text)) {
|
|
event_range = { ...mention_range };
|
|
rule_hits.push('time:event_range_linked');
|
|
}
|
|
|
|
return { mention_range, event_range, relative_label, rule_hits };
|
|
}
|