import test from 'node:test'; import assert from 'node:assert/strict'; import { resolveRainTimeRange, RAIN_DEFAULT_DAYS } from './rain-service/time-range.mjs'; import { formatMeinputFullBlock } from './rain-service/meinput-full.mjs'; import { buildRainGooseHandoffText, stripRainSkillPrefix } from './rain-service/llm-analysis.mjs'; import { RAIN_SKILL_NAME, isRainModeMessage, filterChatSkills, CHAT_SKILL_DEFINITIONS, } from './chat-skills.mjs'; test('resolveRainTimeRange defaults to 3 days when no time hint', () => { const now = new Date('2026-09-04T12:00:00+08:00'); const result = resolveRainTimeRange('帮我总结最近的输入', now); assert.equal(result.needsClarification, false); assert.equal(result.source, 'default_3d'); assert.ok(result.range?.start); assert.ok(result.range?.end); const spanMs = new Date(result.range.end).getTime() - new Date(result.range.start).getTime(); assert.ok(spanMs >= RAIN_DEFAULT_DAYS * 24 * 60 * 60 * 1000 - 60_000); }); test('resolveRainTimeRange asks clarification for ambiguous phrases', () => { const result = resolveRainTimeRange('前几天我在做什么', new Date('2026-09-04T12:00:00+08:00')); assert.equal(result.needsClarification, true); assert.match(result.clarificationQuestion ?? '', /起止时间/); }); test('resolveRainTimeRange parses yesterday explicitly', () => { const now = new Date('2026-09-04T12:00:00+08:00'); const result = resolveRainTimeRange('我昨天输入了什么', now); assert.equal(result.needsClarification, false); assert.equal(result.source, 'user_explicit'); assert.equal(result.label, 'yesterday'); }); test('formatMeinputFullBlock lists every record with timestamp', () => { const block = formatMeinputFullBlock( [ { created_at: '2026-09-02T19:42:16.541Z', text: '项目', app_name: 'Cursor', app_bundle_id: 'com.cursor', }, { created_at: '2026-09-02T19:46:15.181Z', text: '手机', app_name: null, app_bundle_id: 'com.apple.mobile', }, ], { range: { start: '2026-09-01T00:00:00.000Z', end: '2026-09-04T00:00:00.000Z' }, label: '近3天', }, ); assert.match(block, /MeInput 原始输入 · Rain/); assert.match(block, /项目/); assert.match(block, /手机/); assert.match(block, /2026-09-02 19:42:16/); assert.doesNotMatch(block, /0\.2795/); }); test('buildRainGooseHandoffText carries analysis to agent', () => { const text = buildRainGooseHandoffText({ userQuery: '最近我输入了什么', timeRangeLabel: '近3天', recordCount: 12, analysis: '用户在 Cursor 中输入了项目相关词', userGoal: '回顾输入', suggestedNextSteps: ['给用户时间线总结'], }); assert.match(text, /Rain · MeInput 分析简报/); assert.match(text, /用户在 Cursor 中输入了项目相关词/); assert.match(text, /最近我输入了什么/); }); test('isRainModeMessage detects selected rain skill', () => { assert.equal( isRainModeMessage({ metadata: { memindRun: { selectedChatSkill: RAIN_SKILL_NAME } } }), true, ); assert.equal(isRainModeMessage({ metadata: { memindRun: { selectedChatSkill: 'web' } } }), false); }); test('filterChatSkills hides page templates by default', () => { const options = CHAT_SKILL_DEFINITIONS.map((def) => ({ ...def, buildPrompt: () => '' })); const filtered = filterChatSkills(options, { grantedSkills: ['page-template-travel'], canPublish: true }); assert.ok(filtered.some((item) => item.id === RAIN_SKILL_NAME)); assert.equal(filtered.some((item) => item.id === 'page-template-travel'), false); }); test('stripRainSkillPrefix removes rain prompt header', () => { const text = stripRainSkillPrefix('【Rain · MeInput 输入分析】请描述要分析的时间区间和你的诉求。未写时间则默认近3天;区间不明确时我会先追问。我的问题是:总结输入'); assert.equal(text, '总结输入'); });