import { analyzeKeywords, expandQueries, inferTargets, inferTemporalMode, isTemporalRecallQuery, } from './keyword-rules.mjs'; import { parseTimeScope } from './time-parser.mjs'; function envCalendarEnabled() { const raw = String(process.env.MEMIND_TEMPORAL_RECALL_CALENDAR_ENABLED ?? '1').trim().toLowerCase(); return ['1', 'true', 'yes', 'on'].includes(raw); } /** * @param {{ query: string, user_id: string, now?: Date, planner_level?: string }} input */ export function buildContextPlan(input) { const query = String(input.query ?? '').trim(); const now = input.now instanceof Date ? input.now : new Date(input.now ?? Date.now()); const planner_level = input.planner_level ?? 'auto'; if (!query) { throw new Error('invalid_query'); } if (planner_level === 'semantic') { const err = new Error('semantic planner not implemented in v0.1'); err.code = 'not_implemented'; throw err; } const temporal = isTemporalRecallQuery(query); if (!temporal) { return { query_type: 'general_chat', time: { mention_range: parseTimeScope('这周', now).mention_range, event_range: null, relative_label: 'this_week', }, temporal_mode: 'AMBIGUOUS', targets: [{ type: 'mention', weight: 0.5 }], sources: { calendar: 0.2, chat: 0.4, meinput: 0.4, memory_v2: 0.3 }, retrievals: [], filters: { importance_min: 0.35, status: 'any' }, output: { group_by: 'importance', dedupe: true, timeline: true, wide_recall: true, max_items: 50 }, context_needs: { user_snapshot: 'REQUIRED', temporal_recall: 'SKIP', memory_retrieval: 'SKIP', }, planner_meta: { level: 'cheap_router', rule_hits: ['query:general_chat'], confidence: 0.9 }, }; } const time = parseTimeScope(query, now); const kw = analyzeKeywords(query); const targets = inferTargets(query, kw); const temporal_mode = inferTemporalMode(query); const rule_hits = [...time.rule_hits, ...kw.rule_hits, `temporal_mode:${temporal_mode}`]; const sources = { ...kw.sources }; if (temporal_mode === 'PLANNED_IN' || temporal_mode === 'DUE_IN') { sources.calendar = Math.min(1, sources.calendar + 0.2); } if (temporal_mode === 'MENTIONED_IN' || temporal_mode === 'CREATED_IN') { sources.meinput = Math.min(1, sources.meinput + 0.15); sources.chat = Math.min(1, sources.chat + 0.15); } const skip_below = 0.3; const retrievals = []; for (const [source, weight] of Object.entries(sources)) { if (weight < skip_below) continue; if (source === 'calendar' && !envCalendarEnabled()) continue; retrievals.push({ source, query_type: source === 'meinput' ? 'important_mentions' : source === 'chat' ? 'commitments_and_decisions' : source === 'calendar' ? 'events' : 'time_bounded', expanded_queries: expandQueries(source, query, kw.commitmentKeywords), weight: Number(weight.toFixed(2)), skip_below, }); } retrievals.sort((a, b) => b.weight - a.weight); const memoryHint = /决定|定了|上次|记得|记住/.test(query); const context_needs = { user_snapshot: 'OPTIONAL', temporal_recall: 'REQUIRED', memory_retrieval: memoryHint ? 'OPTIONAL' : 'SKIP', }; return { query_type: 'personal_temporal_recall', time: { mention_range: time.mention_range, event_range: time.event_range, relative_label: time.relative_label, }, temporal_mode, targets, sources: Object.fromEntries( Object.entries(sources).map(([k, v]) => [k, Number(v.toFixed(2))]), ), retrievals, filters: { importance_min: kw.importance_min, status: /还没|未完成|没做|待/.test(query) ? 'unresolved' : 'any', }, output: { group_by: temporal_mode === 'AMBIGUOUS' ? 'importance' : 'time', dedupe: true, timeline: true, wide_recall: true, max_items: 50, }, context_needs, planner_meta: { level: 'cheap_router', rule_hits, confidence: Math.min(0.95, 0.65 + retrievals.length * 0.08), }, }; }