Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
194 lines
6.5 KiB
JavaScript
194 lines
6.5 KiB
JavaScript
/**
|
|
* Briefing — Public API / Generators
|
|
*
|
|
* generateMorningBriefing, generateEveningCheckin,
|
|
* sendWellnessReminder, generateWellnessSchedule
|
|
*/
|
|
|
|
import { sendToTopic } from '../TelegramService.js';
|
|
import { log, getTodayBounds, escTg } from './constants.js';
|
|
import {
|
|
fetchTodayEvents, fetchSprintTasks, fetchHealthMetrics,
|
|
fetchMedications, fetchCompletedToday,
|
|
fetchWellnessSchedule, fetchAtomicHabits,
|
|
} from './fetchers.js';
|
|
import { formatMorningBriefing, formatEveningCheckin } from './formatters.js';
|
|
|
|
// =============================================================================
|
|
// PUBLIC API — called by ScheduleTriggerService
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Generate and send morning briefing via Telegram.
|
|
* All briefings are routed to forum topics (not DMs).
|
|
* @param {Object} config - { topic?: string }
|
|
* @returns {Promise<{success: boolean, stats: Object}>}
|
|
*/
|
|
export async function generateMorningBriefing(config = {}) {
|
|
// Route to forum topic — default: schedule
|
|
const topic = config.topic || 'schedule';
|
|
|
|
log.info({ topic }, 'Generating morning briefing \u2192 topic');
|
|
|
|
const { todayStr, todayLabel } = getTodayBounds();
|
|
|
|
const [events, tasks, health, medications] = await Promise.all([
|
|
fetchTodayEvents(todayStr),
|
|
fetchSprintTasks(),
|
|
fetchHealthMetrics(),
|
|
fetchMedications(),
|
|
]);
|
|
|
|
// Single message for the household (no per-user DM)
|
|
const message = formatMorningBriefing({ todayLabel, events, tasks, health, medications, recipientName: '\u043a\u043e\u043c\u0430\u043d\u0434\u0430' });
|
|
const res = await sendToTopic(topic, message);
|
|
|
|
log.info({
|
|
topic,
|
|
eventsCount: events.length,
|
|
tasksCount: tasks.length,
|
|
telegramSuccess: res.success,
|
|
}, 'Morning briefing sent to topic');
|
|
|
|
return {
|
|
success: res.success,
|
|
stats: {
|
|
events: events.length,
|
|
active_tasks: tasks.length,
|
|
medications: medications.length,
|
|
has_health_data: !!health,
|
|
},
|
|
telegram: [{ topic, success: res.success }],
|
|
date: todayStr,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate and send evening check-in via Telegram.
|
|
* All check-ins are routed to forum topics (not DMs).
|
|
* @param {Object} config - { topic?: string }
|
|
* @returns {Promise<{success: boolean, stats: Object}>}
|
|
*/
|
|
export async function generateEveningCheckin(config = {}) {
|
|
// Route to forum topic — default: tasks (итоги дня = задачи)
|
|
const topic = config.topic || 'tasks';
|
|
|
|
log.info({ topic }, 'Generating evening check-in \u2192 topic');
|
|
|
|
const { todayStr, todayLabel } = getTodayBounds();
|
|
|
|
const [completedTasks, remainingTasks, health, medications] = await Promise.all([
|
|
fetchCompletedToday(todayStr),
|
|
fetchSprintTasks(),
|
|
fetchHealthMetrics(),
|
|
fetchMedications(),
|
|
]);
|
|
|
|
const message = formatEveningCheckin({ todayLabel, completedTasks, remainingTasks, medications, health });
|
|
const res = await sendToTopic(topic, message);
|
|
|
|
const total = completedTasks.length + remainingTasks.length;
|
|
|
|
log.info({
|
|
topic,
|
|
completedCount: completedTasks.length,
|
|
remainingCount: remainingTasks.length,
|
|
telegramSuccess: res.success,
|
|
}, 'Evening check-in sent to topic');
|
|
|
|
return {
|
|
success: res.success,
|
|
stats: {
|
|
completed_today: completedTasks.length,
|
|
remaining: remainingTasks.length,
|
|
productivity_pct: total > 0 ? Math.round((completedTasks.length / total) * 100) : 0,
|
|
medications: medications.length,
|
|
has_health_data: !!health,
|
|
},
|
|
telegram: [{ topic, success: res.success }],
|
|
date: todayStr,
|
|
};
|
|
}
|
|
|
|
// =============================================================================
|
|
// WELLNESS SCHEDULE REMINDERS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Send a wellness reminder to the forum topic (not DMs).
|
|
* Called by ScheduleTriggerService via automation config.
|
|
*
|
|
* @param {Object} config - { message: string, topic?: string, category?: string }
|
|
*/
|
|
export async function sendWellnessReminder(config = {}) {
|
|
const message = config.message || config.text || 'Wellness reminder';
|
|
// Route to forum topic — default: fitness
|
|
const topic = config.topic || config.category || 'fitness';
|
|
|
|
log.info({ topic, messagePreview: message.substring(0, 50) }, 'Sending wellness reminder \u2192 topic');
|
|
|
|
const res = await sendToTopic(topic, message);
|
|
|
|
return {
|
|
success: res.success,
|
|
telegram: [{ topic, success: res.success }],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate a full daily wellness schedule summary.
|
|
* Can be sent as part of morning briefing or on-demand via /schedule command.
|
|
*
|
|
* @param {Object} config - { chatIds?: string[] }
|
|
*/
|
|
export async function generateWellnessSchedule(config = {}) {
|
|
const [schedule, habits] = await Promise.all([
|
|
fetchWellnessSchedule(),
|
|
fetchAtomicHabits(),
|
|
]);
|
|
|
|
const lines = [];
|
|
lines.push('\u{1F4CB} *\u0420\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0434\u043d\u044f*');
|
|
lines.push('');
|
|
|
|
if (schedule.length === 0) {
|
|
lines.push('\u0420\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043d\u0435 \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u043e');
|
|
} else {
|
|
for (const block of schedule) {
|
|
const timeStr = block.time_start ? `${block.time_start}-${block.time_end}` : '\u23F0';
|
|
const catIcon = {
|
|
Morning: '\u{1F305}', Work: '\u{1F4BB}', Fitness: '\u{1F3CB}',
|
|
Nutrition: '\u{1F372}', Relationship: '\u{2764}\u{FE0F}', Communication: '\u{1F4AC}',
|
|
Creative: '\u{1F3B5}', 'Self-Care': '\u{1F9D8}', Recovery: '\u{1F634}',
|
|
}[block.category] || '\u{1F4CC}';
|
|
lines.push(`${catIcon} \`${timeStr}\` ${escTg(block.block_name)}`);
|
|
}
|
|
}
|
|
|
|
if (habits.length > 0) {
|
|
lines.push('');
|
|
lines.push('\u{1F517} *Atomic Habits \u0441\u0442\u0435\u043a\u0438:*');
|
|
for (const h of habits.slice(0, 10)) {
|
|
lines.push(` \u{27A1}\u{FE0F} ${escTg(h.trigger)} \u2192 ${escTg(h.habit)} (${escTg(h.duration)})`);
|
|
}
|
|
if (habits.length > 10) lines.push(` ... \u0438 \u0435\u0449\u0451 ${habits.length - 10}`);
|
|
}
|
|
|
|
lines.push('');
|
|
lines.push('\u{1F4AA} \u041e\u0442\u043b\u0438\u0447\u043d\u043e\u0433\u043e \u0434\u043d\u044f!');
|
|
|
|
const message = lines.join('\n');
|
|
|
|
// Route to forum topic — default: schedule
|
|
const topic = config.topic || 'schedule';
|
|
|
|
log.info({ topic }, 'Sending wellness schedule \u2192 topic');
|
|
|
|
const res = await sendToTopic(topic, message);
|
|
|
|
return {
|
|
success: res.success,
|
|
telegram: [{ topic, success: res.success }],
|
|
stats: { schedule_blocks: schedule.length, habits: habits.length },
|
|
};
|
|
}
|