import type { Me } from "@rakazo/contracts"; import { localTimezone } from "./local-timezone"; import { resolveUiLocale } from "./ui-locale"; import { inferHelpVibeHints, mergeHelpVibeIntoMemory, type OnboardingFocusId, type UserHelpVibeHints, } from "./user-help-vibe"; import { mergeRelationshipContractIntoMemory } from "./user-relationship-contract"; export interface UserProfileSignals { name?: string; email?: string; timezone: string; locale: string; uiLocale?: string; platform?: string; languages: string[]; defaultProvider?: string | null; defaultModel?: string | null; computerHost?: string | null; harvestedAt: string; } /** Local, respectful signals available during web onboarding (no new permissions). */ export function collectLocalProfileSignals( me?: Pick, ): UserProfileSignals { const locale = typeof navigator !== "undefined" && navigator.language ? navigator.language : "en"; const languages = typeof navigator !== "undefined" && navigator.languages?.length ? [...navigator.languages] : [locale]; return { name: me?.name?.trim() || undefined, email: me?.email?.trim() || undefined, timezone: localTimezone(), locale, uiLocale: resolveUiLocale(), platform: typeof navigator !== "undefined" ? navigator.platform : undefined, languages, defaultProvider: me?.defaultProvider ?? null, defaultModel: me?.defaultModel ?? null, computerHost: me?.computerHost ?? null, harvestedAt: new Date().toISOString(), }; } export type MemoryListClient = { memory: { list: (input: { scope?: "user" | "bot"; }) => Promise>; update: (input: { documentId: string; content: string }) => Promise; }; }; /** * Optional help/vibe + Oo relationship product contract → user MEMORY.md. Name, time zone and * language live in the user profile instead, so they are not repeated here. */ export function mergeUserMemorySections(existing: string, helpVibe?: UserHelpVibeHints): string { const content = helpVibe ? mergeHelpVibeIntoMemory(existing, helpVibe) : existing; return mergeRelationshipContractIntoMemory(content); } /** Persist help/vibe hints into space user `MEMORY.md` (existing memory system). */ export async function persistUserProfileHarvest( client: MemoryListClient, signals: UserProfileSignals, options?: { focusChoiceId?: OnboardingFocusId }, ): Promise { const helpVibe = inferHelpVibeHints(signals, options); const docs = await client.memory.list({ scope: "user" }); const memoryDoc = docs.find((doc) => doc.path === "MEMORY.md") ?? docs[0]; if (!memoryDoc) return helpVibe; const content = mergeUserMemorySections(memoryDoc.content, helpVibe); if (content !== memoryDoc.content) { await client.memory.update({ documentId: memoryDoc.id, content }); } return helpVibe; }