feat(lifeops): support multi-calendar Google feeds#7072
feat(lifeops): support multi-calendar Google feeds#7072lalalune merged 7 commits intoelizaOS:developfrom
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
1b9da11 to
08fdc40
Compare
|
Upstream dependency is cloud#472 for the hosted managed-calendar list route. Once that lands, the follow-up top-level Milady PR should only advance the \�liza\ pointer; this PR intentionally does not move the \cloud\ submodule pointer. |
This completes the LifeOps multi-calendar stack on the app/runtime side and adds the last safety fallback for hosted cloud lag.
Included
Dependency
cloudsubmodule pointer alone until that cloud change lands upstreamWhy
Hosted users were getting blank or incorrect calendar views when the connector only exposed
primaryor when the new managed calendar-list route was not yet deployed. This keeps the UI and agent behavior stable while the cloud route lands.Validation
bun run --cwd C:\Users\epj33\Documents\Playground\milady\eliza\apps\app-lifeops test -- service-mixin-calendar.test.tsGreptile Summary
This PR completes the LifeOps multi-calendar stack: it adds
listCalendars/setCalendarIncludedservice methods, agetCalendarFeedpath that aggregates across all user-enabled calendars (with a fallback to primary when managed calendar discovery is unavailable), per-calendarincludeInFeedpreferences stored in the scheduler task metadata, and UI for toggling calendar visibility in Settings.calendarFeedIncludesis keyed only bycalendarId. Users with two connected Google accounts both have a calendar withcalendarId: \"primary\", so toggling one will silently override the other's preference. The UI already uses a${grantId}:${calendarId}composite key for React rendering — the backend persistence and lookup should do the same.Confidence Score: 4/5
Safe to merge for single-account users; multi-account users will see incorrect calendar visibility preferences until the key collision is fixed.
One P1 correctness issue: calendarFeedIncludes keyed by calendarId alone causes a data collision for any user with two Google accounts sharing a 'primary' calendarId. All other findings are P2 (fragile error string match, unnecessary API call on toggle). The fallback, merge, and UI logic are otherwise well-structured and the new tests give good coverage of the happy paths.
apps/app-lifeops/src/lifeops/service-mixin-calendar.ts (preference lookup) and apps/app-lifeops/src/lifeops/owner-profile.ts (preference storage key)
Important Files Changed
Sequence Diagram
sequenceDiagram participant UI as Settings UI participant Client as ElizaClient participant Route as lifeops-routes participant Svc as LifeOpsService participant GMC as GoogleManagedClient participant GCal as Google CalendarList API participant Profile as owner-profile (Task) UI->>Client: getLifeOpsCalendars({side, mode}) Client->>Route: GET /api/lifeops/calendar/calendars Route->>Svc: listCalendars(url, request) alt cloud_managed grant Svc->>GMC: listCalendars({side, grantId}) GMC-->>Svc: ManagedGoogleCalendarSummaryResponse[] else browser/local grant Svc->>GCal: GET calendarList (Bearer token) GCal-->>Svc: GoogleCalendarListEntry[] end Svc->>Profile: ensureLifeOpsCalendarFeedIncludes(calendarIds) Profile-->>Svc: LifeOpsCalendarFeedPreferences Svc-->>Route: LifeOpsCalendarSummary[] (includeInFeed merged) Route-->>Client: { calendars } Client-->>UI: render toggle list UI->>Client: setLifeOpsCalendarIncluded({calendarId, includeInFeed, grantId}) Client->>Route: PUT /api/lifeops/calendar/calendars/:id/include Route->>Svc: setCalendarIncluded(url, request) Svc->>Svc: listCalendars() — verify existence Svc->>Profile: setLifeOpsCalendarFeedIncluded(calendarId, included) Profile-->>Svc: updated LifeOpsCalendarFeedPreferences Svc-->>Route: updated LifeOpsCalendarSummary Route-->>Client: { calendar } Client-->>UI: optimistic local state updateComments Outside Diff (3)
apps/app-lifeops/src/lifeops/service-mixin-calendar.ts, line 1350-1358 (link)calendarFeedIncludesis stored asRecord<string, boolean>keyed only bycalendarId. Google's calendarList API returns"primary"as thecalendarIdfor every account's primary calendar — so two connected Google accounts will share the same key in the preferences store. Toggling the primary calendar on account A will silently affect account B's primary calendar visibility as well.The UI already uses the composite key
${calendar.grantId}:${calendar.calendarId}for React'skeyprop and for local-state matching after a toggle, but the backend dropsgrantIdbefore persisting. The preference lookup here also ignoresgrantId:The fix is to key preferences by
${grantId}:${calendarId}throughout (ensureLifeOpsCalendarFeedIncludes,setLifeOpsCalendarFeedIncluded, and the lookup inlistCalendars).apps/app-lifeops/src/lifeops/service-mixin-calendar.ts, line 1432-1458 (link)The fallback to primary aggregation is gated on
error.message.includes("managed calendar-list route"). This is a coupling between the error-throw site inlistCalendars(same file) and this catch site; if the message is ever rephrased or if a different 503 from the managed client triggerslistCalendars, the fallback silently does not fire.Consider using a dedicated error subclass or a typed error code (
error.code === "MANAGED_CALENDAR_LIST_UNAVAILABLE") that is stable across refactors and locale-agnostic.apps/app-lifeops/src/lifeops/service-mixin-calendar.ts, line 1380-1401 (link)setCalendarIncludedissues a full Google API round-trip to validate the calendarsetCalendarIncludedcallsthis.listCalendars(...)— which hits either the managed cloud API or Google's calendarList endpoint for every grant — purely to confirm the targetcalendarIdexists before writing the preference. For users with several connected accounts this means multiple external calls on every toggle.Consider persisting a local snapshot of known calendars (already returned to the UI) so this validation can be done from cache, or skip the existence check if it's acceptable to trust the client-supplied
calendarId.Reviews (1): Last reviewed commit: "fix(lifeops): fall back when managed cal..." | Re-trigger Greptile