feat(integrations): add Google Health (Fit) integration with full permissions
OAuth2 flow with all 11 Google Fitness scopes (activity, body, sleep, heart rate, nutrition, location, blood glucose/pressure/temperature, oxygen saturation, reproductive health). Stores access + refresh tokens; auto-refreshes on expiry. GoogleHealthSignalSource fetches steps, sleep sessions, active minutes, calories, and heart rate from the Fit aggregate + sessions APIs. Signals flow into both the tip orchestrator and the health-vitals pre-compute agent, which generates prompt snippets about step progress, sleep deficit, sedentary time, and elevated heart rate. Signal.kind extended with 'health'; IntegrationProvider extended with 'google-health'. Agent compute signal mapping enriched to include source, kind, and all features so health-vitals can filter its own signals. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,12 +6,13 @@ import { eq, and, gt, lt } from 'drizzle-orm';
|
||||
import { config } from '../config.js';
|
||||
import { getProfile, type Profile } from '../profile/builder.js';
|
||||
import { todoistSource } from '../signals/todoist.js';
|
||||
import { googleHealthSource } from '../signals/google-health.js';
|
||||
import { SignalAggregator } from '../signals/aggregator.js';
|
||||
|
||||
const router: IRouter = Router();
|
||||
|
||||
// Separate aggregator instance — avoids circular dep with recommender.ts.
|
||||
const _agentAggregator = new SignalAggregator().register(todoistSource);
|
||||
const _agentAggregator = new SignalAggregator().register(todoistSource).register(googleHealthSource);
|
||||
|
||||
// ── Internal auth helper ──────────────────────────────────────────────────────
|
||||
|
||||
@@ -132,11 +133,16 @@ export async function computeAndStore(userId: string, agentId: string): Promise<
|
||||
const signals = await _agentAggregator.fetchAll(userId);
|
||||
tasks = signals.map((s) => ({
|
||||
id: s.id,
|
||||
source: s.source,
|
||||
kind: s.kind,
|
||||
content: s.content,
|
||||
// Task-specific fields (default to harmless values for non-task signals)
|
||||
priority: (s.features.priority as number) ?? 1,
|
||||
is_overdue: Boolean(s.features.is_overdue),
|
||||
task_age_days: (s.features.task_age_days as number) ?? 0,
|
||||
project_id: (s.metadata as Record<string, unknown>).project_id ?? null,
|
||||
// All features spread so source-specific agents (e.g. health-vitals) can read them
|
||||
...s.features,
|
||||
}));
|
||||
} catch {
|
||||
// No integration or fetch error — agents that need tasks will report "no tasks"
|
||||
|
||||
Reference in New Issue
Block a user