@agent-native/core 0.176.4 → 0.176.5-nightly-20260902202853

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +6 -0
  3. package/corpus/templates/calendar/AGENTS.md +4 -0
  4. package/corpus/templates/calendar/actions/delete-event.ts +2 -2
  5. package/corpus/templates/calendar/actions/event-action-helpers.ts +7 -0
  6. package/corpus/templates/calendar/actions/get-event.ts +40 -3
  7. package/corpus/templates/calendar/actions/list-events.ts +49 -2
  8. package/corpus/templates/calendar/actions/list-google-calendars.ts +24 -0
  9. package/corpus/templates/calendar/actions/update-calendar-visual-preferences.ts +52 -1
  10. package/corpus/templates/calendar/actions/update-event.ts +2 -2
  11. package/corpus/templates/calendar/actions/view-screen.ts +54 -2
  12. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +33 -19
  13. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +31 -5
  14. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +102 -0
  15. package/corpus/templates/calendar/app/hooks/use-events.ts +45 -10
  16. package/corpus/templates/calendar/app/hooks/use-google-auth.ts +8 -1
  17. package/corpus/templates/calendar/app/hooks/use-google-calendars.ts +41 -0
  18. package/corpus/templates/calendar/app/hooks/use-view-preferences.ts +56 -4
  19. package/corpus/templates/calendar/app/lib/shared-calendar-demo.ts +187 -0
  20. package/corpus/templates/calendar/app/pages/CalendarView.tsx +50 -5
  21. package/corpus/templates/calendar/changelog/2026-09-01-calendars-shared-with-your-connected-google-accounts-can-now.md +6 -0
  22. package/corpus/templates/calendar/changelog/2026-09-02-updated-the-booking-link-og-preview-image-with-the-new-monoc.md +6 -0
  23. package/corpus/templates/calendar/server/lib/booking-og-image.ts +8 -18
  24. package/corpus/templates/calendar/server/lib/google-api.ts +10 -0
  25. package/corpus/templates/calendar/server/lib/google-calendar.ts +215 -21
  26. package/corpus/templates/calendar/server/plugins/agent-chat.ts +2 -0
  27. package/corpus/templates/calendar/server/plugins/feature-flags.ts +5 -0
  28. package/corpus/templates/calendar/shared/api.ts +20 -0
  29. package/corpus/templates/calendar/shared/calendar-view-preferences.ts +28 -1
  30. package/corpus/templates/calendar/shared/feature-flags.ts +8 -0
  31. package/corpus/templates/calendar/shared/google-calendar-sources.ts +46 -0
  32. package/corpus/templates/forms/server/lib/form-og-image.ts +7 -17
  33. package/dist/collab/awareness.d.ts +2 -2
  34. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  35. package/dist/notifications/routes.d.ts +3 -3
  36. package/dist/observability/routes.d.ts +5 -5
  37. package/dist/progress/routes.d.ts +1 -1
  38. package/dist/resources/handlers.d.ts +1 -1
  39. package/dist/secrets/routes.d.ts +9 -9
  40. package/dist/server/realtime-token.d.ts +1 -1
  41. package/dist/server/social-og-image.js +6 -20
  42. package/dist/shared/social-meta.d.ts +1 -1
  43. package/dist/shared/social-meta.js +1 -1
  44. package/package.json +3 -3
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 8851
34
+ - template files: 8859
@@ -27,6 +27,12 @@ slot does not inject per-row UI.
27
27
 
28
28
  Query events from Google Calendar within a date range.
29
29
 
30
+ Use `list-google-calendars` first when the user wants events from calendars
31
+ shared with any connected account. Pass the returned opaque `sourceKey` values
32
+ as `calendarSourceKeys`; never construct or decode them. Non-primary calendars
33
+ are view-only in Calendar, even when Google reports an editable access role,
34
+ and are excluded from booking availability.
35
+
30
36
  ```bash
31
37
  # Today's events (--to is exclusive, so use tomorrow)
32
38
  pnpm action list-events --from 2026-04-03 --to 2026-04-04
@@ -55,6 +55,10 @@ Detailed event, availability, booking, storage, and UI rules live in
55
55
  `sourceCoverage`, and `coverageComplete` fields — a partial source failure is
56
56
  not an empty calendar. See `event-management` for the formats and the
57
57
  `accountEmails` rules.
58
+ - Use `list-google-calendars` to discover primary and shared calendars available
59
+ through connected Google accounts, then pass its opaque `sourceKey` values to
60
+ `list-events`. Shared calendars are view-only in Calendar and do not affect
61
+ booking availability.
58
62
  - Treat Google Calendar working locations and full-day out-of-office events as
59
63
  native status events; see `event-management` for their action contracts.
60
64
  - Use framework sharing actions for calendar, event, and booking resources;
@@ -9,7 +9,7 @@ import { isGoogleNotFoundError } from "../server/lib/google-api.js";
9
9
  import * as googleCalendar from "../server/lib/google-calendar.js";
10
10
  import {
11
11
  cliBoolean,
12
- normalizeGoogleEventId,
12
+ normalizeWritableGoogleEventId,
13
13
  requireActionUserEmail,
14
14
  resolveOwnedAccountEmail,
15
15
  } from "./event-action-helpers.js";
@@ -57,7 +57,7 @@ export default defineAction({
57
57
  );
58
58
  }
59
59
 
60
- const googleEventId = normalizeGoogleEventId(args.id);
60
+ const googleEventId = normalizeWritableGoogleEventId(args.id);
61
61
  const accountEmail = await resolveOwnedAccountEmail(
62
62
  args.accountEmail,
63
63
  ownerEmail,
@@ -176,6 +176,13 @@ export function normalizeGoogleEventId(id: string): string {
176
176
  return id.startsWith("google-") ? id.slice("google-".length) : id;
177
177
  }
178
178
 
179
+ export function normalizeWritableGoogleEventId(id: string): string {
180
+ if (id.startsWith("google-google-calendar:")) {
181
+ throw new Error("Shared Google calendar events are read-only");
182
+ }
183
+ return normalizeGoogleEventId(id);
184
+ }
185
+
179
186
  export async function resolveOwnedAccountEmail(
180
187
  requestedAccountEmail: string | undefined,
181
188
  ownerEmail: string,
@@ -1,10 +1,13 @@
1
1
  import { defineAction } from "@agent-native/core/action";
2
+ import { isFeatureFlagEnabled } from "@agent-native/core/feature-flags";
2
3
  import { getRequestUserEmail, buildDeepLink } from "@agent-native/core/server";
3
4
  import { z } from "zod";
4
5
 
5
6
  import { calendarGetEvent } from "../server/lib/google-api.js";
6
7
  import * as googleCalendar from "../server/lib/google-calendar.js";
7
8
  import type { CalendarEvent } from "../shared/api.js";
9
+ import { SHARED_GOOGLE_CALENDARS } from "../shared/feature-flags.js";
10
+ import { parseGoogleCalendarSourceKey } from "../shared/google-calendar-sources.js";
8
11
  import { getGoogleEventColorHex } from "../shared/google-event-colors.js";
9
12
 
10
13
  export default defineAction({
@@ -20,13 +23,24 @@ export default defineAction({
20
23
  .optional()
21
24
  .default("primary")
22
25
  .describe('Calendar id — defaults to "primary"'),
26
+ calendarSourceKey: z
27
+ .string()
28
+ .optional()
29
+ .describe(
30
+ "Opaque source key from list-google-calendars for a shared event",
31
+ ),
23
32
  }),
24
33
  http: { method: "GET" },
25
34
  readOnly: true,
26
35
  publicAgent: { expose: true, readOnly: true, requiresAuth: true },
27
36
  link: ({ result }) => {
28
37
  if (!result || typeof result !== "object") return null;
29
- const evt = result as { id?: string; start?: string; error?: string };
38
+ const evt = result as {
39
+ id?: string;
40
+ start?: string;
41
+ error?: string;
42
+ calendarSourceKey?: string;
43
+ };
30
44
  if (evt.error || !evt.id) return null;
31
45
  const date =
32
46
  typeof evt.start === "string" && evt.start
@@ -36,16 +50,39 @@ export default defineAction({
36
50
  url: buildDeepLink({
37
51
  app: "calendar",
38
52
  view: "calendar",
39
- params: { eventId: evt.id, date },
53
+ params: {
54
+ eventId: evt.id,
55
+ date,
56
+ calendarSourceKey: evt.calendarSourceKey,
57
+ },
40
58
  }),
41
59
  label: "Open event in Calendar",
42
60
  view: "calendar",
43
61
  };
44
62
  },
45
- run: async (args) => {
63
+ run: async (args, ctx) => {
46
64
  const email = getRequestUserEmail();
47
65
  if (!email) throw new Error("no authenticated user");
48
66
 
67
+ if (args.calendarSourceKey) {
68
+ if (!(await isFeatureFlagEnabled(SHARED_GOOGLE_CALENDARS, ctx))) {
69
+ throw new Error("Shared Google calendars is not enabled");
70
+ }
71
+ const source = parseGoogleCalendarSourceKey(args.calendarSourceKey);
72
+ if (!source) throw new Error("Invalid Google Calendar source key");
73
+ const namespacedPrefix = `google-${args.calendarSourceKey}-`;
74
+ const rawId = args.id.startsWith(namespacedPrefix)
75
+ ? args.id.slice(namespacedPrefix.length)
76
+ : args.id.startsWith("google-")
77
+ ? args.id.slice("google-".length)
78
+ : args.id;
79
+ return googleCalendar.getEvent(
80
+ rawId,
81
+ { ownerEmail: email, accountEmail: source.accountEmail },
82
+ { calendarSourceKey: args.calendarSourceKey },
83
+ );
84
+ }
85
+
49
86
  const rawId = args.id.startsWith("google-")
50
87
  ? args.id.slice("google-".length)
51
88
  : args.id;
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
 
3
3
  import { defineAction } from "@agent-native/core/action";
4
+ import { isFeatureFlagEnabled } from "@agent-native/core/feature-flags";
4
5
  import {
5
6
  getRequestTimezone,
6
7
  getRequestUserEmail,
@@ -17,6 +18,7 @@ import { getCalendarTimezone } from "../server/lib/calendar-settings.js";
17
18
  import * as googleCalendar from "../server/lib/google-calendar.js";
18
19
  import { fetchICalEvents } from "../server/lib/ical-fetcher.js";
19
20
  import type { CalendarEvent, ExternalCalendar } from "../shared/api.js";
21
+ import { SHARED_GOOGLE_CALENDARS } from "../shared/feature-flags.js";
20
22
  import {
21
23
  addDaysToDateKey,
22
24
  dateKeyInTimezone,
@@ -74,6 +76,7 @@ interface ListCalendarEventsArgs {
74
76
  query?: string;
75
77
  overlayEmails?: string | string[];
76
78
  accountEmails?: string[];
79
+ calendarSourceKeys?: string[];
77
80
  sources?: CalendarInventorySource[];
78
81
  providerPageSize?: number;
79
82
  }
@@ -131,6 +134,12 @@ export interface CalendarInventoryItem {
131
134
  source: "google" | "booking" | "ics" | "overlay";
132
135
  sourceId?: string;
133
136
  accountEmail?: string;
137
+ calendarSourceKey?: string;
138
+ calendarId?: string;
139
+ calendarName?: string;
140
+ calendarAccessRole?: CalendarEvent["calendarAccessRole"];
141
+ calendarPrimary?: boolean;
142
+ calendarReadOnly?: boolean;
134
143
  overlayEmail?: string;
135
144
  organizer?: { email?: string; displayName?: string; self?: boolean };
136
145
  selfResponseStatus?: CalendarEvent["responseStatus"];
@@ -239,6 +248,7 @@ function inventoryQueryKey(args: {
239
248
  to: string;
240
249
  query?: string;
241
250
  accountEmails: string[];
251
+ calendarSourceKeys?: string[];
242
252
  overlayEmails?: string | string[];
243
253
  sources: CalendarInventorySource[];
244
254
  }): string {
@@ -248,6 +258,7 @@ function inventoryQueryKey(args: {
248
258
  to: args.to,
249
259
  query: args.query?.trim().toLowerCase() || "",
250
260
  accountEmails: normalizedEmails(args.accountEmails),
261
+ calendarSourceKeys: [...(args.calendarSourceKeys ?? [])].sort(),
251
262
  overlayEmails: normalizedOverlayEmails(args.overlayEmails),
252
263
  sources: [...args.sources].sort(),
253
264
  });
@@ -314,7 +325,10 @@ function compactInventoryEvent(event: CalendarEvent): CalendarInventoryItem {
314
325
  );
315
326
  const key = [
316
327
  event.source,
317
- event.accountEmail ?? event.overlayEmail ?? "local",
328
+ event.calendarSourceKey ??
329
+ event.accountEmail ??
330
+ event.overlayEmail ??
331
+ "local",
318
332
  event.googleEventId ?? event.id,
319
333
  event.start,
320
334
  ].join(":");
@@ -338,6 +352,12 @@ function compactInventoryEvent(event: CalendarEvent): CalendarInventoryItem {
338
352
  source,
339
353
  sourceId: event.sourceId,
340
354
  accountEmail: event.accountEmail,
355
+ calendarSourceKey: event.calendarSourceKey,
356
+ calendarId: event.calendarId,
357
+ calendarName: cap(event.calendarName),
358
+ calendarAccessRole: event.calendarAccessRole,
359
+ calendarPrimary: event.calendarPrimary,
360
+ calendarReadOnly: event.calendarReadOnly,
341
361
  overlayEmail: event.overlayEmail,
342
362
  organizer: event.organizer
343
363
  ? {
@@ -638,6 +658,7 @@ export async function listCalendarEvents(
638
658
  connected && includeGoogle
639
659
  ? googleCalendar.listEvents(range.from, range.to, email, {
640
660
  accountEmails: args.accountEmails,
661
+ calendarSourceKeys: args.calendarSourceKeys,
641
662
  maxResults: args.providerPageSize,
642
663
  })
643
664
  : Promise.resolve({ events: [], errors: [] });
@@ -727,11 +748,21 @@ export async function listCalendarEvents(
727
748
 
728
749
  const googleEventIds = new Set(
729
750
  googleEvents
751
+ .filter(
752
+ (event) =>
753
+ event.calendarPrimary !== false &&
754
+ !event.overlayEmail &&
755
+ event.googleEventId,
756
+ )
730
757
  .map((event) => event.googleEventId)
731
758
  .filter((id): id is string => Boolean(id)),
732
759
  );
733
760
  const googleReadAuthoritative =
734
- includeGoogle && connected && googleResult.errors.length === 0;
761
+ includeGoogle &&
762
+ connected &&
763
+ googleResult.errors.length === 0 &&
764
+ (!args.calendarSourceKeys?.length ||
765
+ googleEvents.some((event) => event.calendarPrimary === true));
735
766
  const bookingEvents = rawBookingEvents.filter((event) =>
736
767
  shouldShowLocalBookingEvent({
737
768
  event,
@@ -800,6 +831,14 @@ export default defineAction({
800
831
  .describe(
801
832
  "Connected Google accounts to read; omitted reads every connected account",
802
833
  ),
834
+ calendarSourceKeys: z
835
+ .array(z.string().min(1).max(4096))
836
+ .min(1)
837
+ .max(100)
838
+ .optional()
839
+ .describe(
840
+ "Opaque Google calendar source keys returned by list-google-calendars; each is revalidated before reading",
841
+ ),
803
842
  sources: z
804
843
  .array(z.enum(["google", "bookings", "ics", "overlays"]))
805
844
  .max(4)
@@ -825,6 +864,12 @@ export default defineAction({
825
864
  readOnly: true,
826
865
  publicAgent: { expose: true, readOnly: true, requiresAuth: true },
827
866
  run: async (args, ctx) => {
867
+ if (
868
+ args.calendarSourceKeys &&
869
+ !(await isFeatureFlagEnabled(SHARED_GOOGLE_CALENDARS, ctx))
870
+ ) {
871
+ throw new Error("Shared Google calendars is not enabled");
872
+ }
828
873
  const inventory =
829
874
  args.format === "inventory" || (ctx?.caller === "mcp" && !args.format);
830
875
  const owner = inventory ? getRequestUserEmail() : undefined;
@@ -855,6 +900,7 @@ export default defineAction({
855
900
  to: preparedRange.to,
856
901
  query: args.query,
857
902
  accountEmails: args.accountEmails ?? preparedOwnedAccounts ?? [],
903
+ calendarSourceKeys: args.calendarSourceKeys,
858
904
  overlayEmails: args.overlayEmails,
859
905
  sources: resolveInventorySources(args.sources),
860
906
  });
@@ -884,6 +930,7 @@ export default defineAction({
884
930
  to: result.range.to,
885
931
  query: args.query,
886
932
  accountEmails: result.requestedAccounts ?? result.resolvedAccounts,
933
+ calendarSourceKeys: args.calendarSourceKeys,
887
934
  overlayEmails: args.overlayEmails,
888
935
  sources: result.sources,
889
936
  });
@@ -0,0 +1,24 @@
1
+ import { defineAction } from "@agent-native/core/action";
2
+ import { isFeatureFlagEnabled } from "@agent-native/core/feature-flags";
3
+ import { getRequestUserEmail } from "@agent-native/core/server";
4
+ import { z } from "zod";
5
+
6
+ import { listGoogleCalendars } from "../server/lib/google-calendar.js";
7
+ import { SHARED_GOOGLE_CALENDARS } from "../shared/feature-flags.js";
8
+
9
+ export default defineAction({
10
+ description:
11
+ "Discover Google Calendar sources available through every connected account. Source keys are opaque and must be passed back to list-events; names and roles in client requests are never trusted.",
12
+ schema: z.object({}),
13
+ http: { method: "GET" },
14
+ readOnly: true,
15
+ publicAgent: { expose: true, readOnly: true, requiresAuth: true },
16
+ run: async (_args, ctx) => {
17
+ if (!(await isFeatureFlagEnabled(SHARED_GOOGLE_CALENDARS, ctx))) {
18
+ throw new Error("Shared Google calendars is not enabled");
19
+ }
20
+ const ownerEmail = getRequestUserEmail();
21
+ if (!ownerEmail) throw new Error("no authenticated user");
22
+ return listGoogleCalendars(ownerEmail);
23
+ },
24
+ });
@@ -3,13 +3,17 @@ import {
3
3
  readAppState,
4
4
  writeAppState,
5
5
  } from "@agent-native/core/application-state";
6
+ import { isFeatureFlagEnabled } from "@agent-native/core/feature-flags";
7
+ import { getRequestUserEmail } from "@agent-native/core/server";
6
8
  import { z } from "zod";
7
9
 
10
+ import { listGoogleCalendars } from "../server/lib/google-calendar.js";
8
11
  import {
9
12
  CALENDAR_VIEW_PREFERENCES_KEY,
10
13
  isValidCalendarColor,
11
14
  normalizeCalendarViewPreferences,
12
15
  } from "../shared/calendar-view-preferences.js";
16
+ import { SHARED_GOOGLE_CALENDARS } from "../shared/feature-flags.js";
13
17
 
14
18
  const hexColor = z
15
19
  .string()
@@ -53,6 +57,19 @@ export default defineAction({
53
57
  .describe(
54
58
  "Map of connected Google Calendar account email to hex color, for setting multiple accounts at once",
55
59
  ),
60
+ googleCalendarSourceKey: z
61
+ .string()
62
+ .min(1)
63
+ .optional()
64
+ .describe(
65
+ "Opaque sourceKey returned by list-google-calendars whose Agent-Native visibility should change",
66
+ ),
67
+ googleCalendarVisible: z
68
+ .boolean()
69
+ .optional()
70
+ .describe(
71
+ "Whether the Google calendar source should appear in Agent-Native; does not change Google Calendar selection",
72
+ ),
56
73
  hideWeekends: z
57
74
  .boolean()
58
75
  .optional()
@@ -66,8 +83,33 @@ export default defineAction({
66
83
  "accountColor and accountColorMode require accountEmail to be set",
67
84
  path: ["accountEmail"],
68
85
  },
86
+ )
87
+ .refine(
88
+ (args) =>
89
+ args.googleCalendarVisible === undefined ||
90
+ !!args.googleCalendarSourceKey,
91
+ {
92
+ message:
93
+ "googleCalendarVisible requires googleCalendarSourceKey to be set",
94
+ path: ["googleCalendarSourceKey"],
95
+ },
69
96
  ),
70
- run: async (args) => {
97
+ run: async (args, ctx) => {
98
+ if (args.googleCalendarSourceKey) {
99
+ if (!(await isFeatureFlagEnabled(SHARED_GOOGLE_CALENDARS, ctx))) {
100
+ throw new Error("Shared Google calendars is not enabled");
101
+ }
102
+ const ownerEmail = getRequestUserEmail();
103
+ if (!ownerEmail) throw new Error("no authenticated user");
104
+ const sourceResult = await listGoogleCalendars(ownerEmail);
105
+ if (
106
+ !sourceResult.calendars.some(
107
+ (source) => source.sourceKey === args.googleCalendarSourceKey,
108
+ )
109
+ ) {
110
+ throw new Error("Unknown Google calendar source");
111
+ }
112
+ }
71
113
  const runUpdate = async () => {
72
114
  const current = normalizeCalendarViewPreferences(
73
115
  (await readAppState(CALENDAR_VIEW_PREFERENCES_KEY)) as any,
@@ -105,6 +147,15 @@ export default defineAction({
105
147
  : {}),
106
148
  accountColors,
107
149
  accountColorModes,
150
+ googleCalendarVisibility: {
151
+ ...current.googleCalendarVisibility,
152
+ ...(args.googleCalendarSourceKey &&
153
+ args.googleCalendarVisible !== undefined
154
+ ? {
155
+ [args.googleCalendarSourceKey]: args.googleCalendarVisible,
156
+ }
157
+ : {}),
158
+ },
108
159
  });
109
160
 
110
161
  await writeAppState(
@@ -18,7 +18,7 @@ import {
18
18
  cliBoolean,
19
19
  googleColorIdInput,
20
20
  normalizeAttendees,
21
- normalizeGoogleEventId,
21
+ normalizeWritableGoogleEventId,
22
22
  normalizeRecurrence,
23
23
  reminderMethodInput,
24
24
  reminderMinutesInput,
@@ -215,7 +215,7 @@ export default defineAction({
215
215
  );
216
216
  }
217
217
 
218
- const googleEventId = normalizeGoogleEventId(args.id);
218
+ const googleEventId = normalizeWritableGoogleEventId(args.id);
219
219
  const accountEmail = await resolveOwnedAccountEmail(
220
220
  args.accountEmail,
221
221
  ownerEmail,
@@ -1,5 +1,6 @@
1
1
  import { defineAction } from "@agent-native/core/action";
2
2
  import { readAppState } from "@agent-native/core/application-state";
3
+ import { isFeatureFlagEnabled } from "@agent-native/core/feature-flags";
3
4
  import { getRequestUserEmail } from "@agent-native/core/server";
4
5
  import { accessFilter } from "@agent-native/core/sharing";
5
6
  import { z } from "zod";
@@ -7,12 +8,14 @@ import { z } from "zod";
7
8
  import { getDb, schema } from "../server/db/index.js";
8
9
  import { rowToBookingLink } from "../server/lib/booking-link-utils.js";
9
10
  import { readCalendarSettings } from "../server/lib/calendar-settings.js";
11
+ import { listGoogleCalendars } from "../server/lib/google-calendar.js";
10
12
  import type { CalendarEvent, CalendarEventDraft } from "../shared/api.js";
11
13
  import {
12
14
  CALENDAR_VIEW_PREFERENCES_KEY,
13
15
  normalizeCalendarViewPreferences,
14
16
  } from "../shared/calendar-view-preferences.js";
15
17
  import { getWeekStartsOn } from "../shared/calendar-week.js";
18
+ import { SHARED_GOOGLE_CALENDARS } from "../shared/feature-flags.js";
16
19
  import {
17
20
  getCalendarViewDateRange,
18
21
  dateKeyInTimezone,
@@ -33,6 +36,7 @@ async function fetchEventsForRange(
33
36
  from: string,
34
37
  to: string,
35
38
  timezone: string,
39
+ calendarSourceKeys?: string[],
36
40
  ): Promise<{
37
41
  events: CalendarEvent[];
38
42
  errors: Array<{ email: string; error: string }>;
@@ -40,7 +44,10 @@ async function fetchEventsForRange(
40
44
  range: { from: string; to: string; timezone: string; defaulted: boolean };
41
45
  }> {
42
46
  try {
43
- return await listCalendarEvents({ from, to }, { timezone });
47
+ return await listCalendarEvents(
48
+ { from, to, calendarSourceKeys },
49
+ { timezone },
50
+ );
44
51
  } catch (error: any) {
45
52
  return {
46
53
  events: [],
@@ -66,7 +73,7 @@ export default defineAction({
66
73
  "See what the user is currently looking at on screen. Returns the current view, date range, and visible events. Always call this first before taking any action.",
67
74
  schema: z.object({}),
68
75
  http: false,
69
- run: async () => {
76
+ run: async (_args, ctx) => {
70
77
  const navigation = await readAppState("navigation");
71
78
  const visualPreferences = normalizeCalendarViewPreferences(
72
79
  (await readAppState(CALENDAR_VIEW_PREFERENCES_KEY)) as any,
@@ -97,10 +104,49 @@ export default defineAction({
97
104
  getWeekStartsOn(settings.weekStart),
98
105
  );
99
106
 
107
+ const sharedCalendarsEnabled = await isFeatureFlagEnabled(
108
+ SHARED_GOOGLE_CALENDARS,
109
+ ctx,
110
+ );
111
+ const calendarSourceResult = sharedCalendarsEnabled
112
+ ? await listGoogleCalendars(email)
113
+ : { calendars: [], errors: [] };
114
+ const calendarSources = calendarSourceResult.calendars;
115
+ const visibleCalendarSources = calendarSources.filter(
116
+ (source) =>
117
+ source.accessRole !== "freeBusyReader" &&
118
+ (source.primary ||
119
+ (visualPreferences.googleCalendarVisibility[source.sourceKey] ??
120
+ source.selected)),
121
+ );
122
+
123
+ if (sharedCalendarsEnabled) {
124
+ screen.googleCalendars = calendarSources.map((source) => ({
125
+ sourceKey: source.sourceKey,
126
+ accountEmail: source.accountEmail,
127
+ calendarId: source.calendarId,
128
+ name: source.name,
129
+ color: source.color,
130
+ visible:
131
+ source.primary ||
132
+ (visualPreferences.googleCalendarVisibility[source.sourceKey] ??
133
+ source.selected),
134
+ primary: source.primary,
135
+ accessRole: source.accessRole,
136
+ readOnly: source.readOnly || !source.primary,
137
+ }));
138
+ if (calendarSourceResult.errors.length > 0) {
139
+ screen.googleCalendarErrors = calendarSourceResult.errors;
140
+ }
141
+ }
142
+
100
143
  const eventResult = await fetchEventsForRange(
101
144
  range.from,
102
145
  range.to,
103
146
  timezone,
147
+ sharedCalendarsEnabled
148
+ ? visibleCalendarSources.map((source) => source.sourceKey)
149
+ : undefined,
104
150
  );
105
151
  const { events } = eventResult;
106
152
 
@@ -112,6 +158,12 @@ export default defineAction({
112
158
  end: e.end,
113
159
  source: e.source,
114
160
  accountEmail: e.accountEmail || undefined,
161
+ calendarSourceKey: e.calendarSourceKey || undefined,
162
+ calendarId: e.calendarId || undefined,
163
+ calendarName: e.calendarName || undefined,
164
+ calendarAccessRole: e.calendarAccessRole || undefined,
165
+ calendarPrimary: e.calendarPrimary,
166
+ calendarReadOnly: e.calendarReadOnly,
115
167
  location: e.location || undefined,
116
168
  allDay: e.allDay || undefined,
117
169
  recurrence: e.recurrence || undefined,
@@ -150,7 +150,10 @@ export function EventDetailPanel({
150
150
  );
151
151
  const { promptGuestNotification, guestNotificationDialog } =
152
152
  useGuestNotificationPrompt();
153
- const isOverlay = !!event?.overlayEmail;
153
+ const isOverlay =
154
+ !!event?.overlayEmail ||
155
+ event?.calendarPrimary === false ||
156
+ event?.calendarReadOnly === true;
154
157
  const isWorkingLocation = event ? isWorkingLocationEvent(event) : false;
155
158
  const isOutOfOffice = event ? isOutOfOfficeEvent(event) : false;
156
159
  const isRecurringEvent = !!(
@@ -167,7 +170,13 @@ export function EventDetailPanel({
167
170
  entryPoint.entryPointType === "video" &&
168
171
  entryPoint.uri.includes("meet.google.com"),
169
172
  ));
170
- const ownerLabel = event?.ownerName || event?.overlayEmail;
173
+ const ownerLabel =
174
+ event?.ownerName ||
175
+ event?.overlayEmail ||
176
+ ((event?.calendarPrimary === false || event?.calendarReadOnly) &&
177
+ event?.calendarName
178
+ ? `${event.calendarName} · ${event.accountEmail ?? "Google"}`
179
+ : undefined);
171
180
  const eventDetailSlotContext = useMemo(
172
181
  () => (event ? buildEventDetailSlotContext(event) : null),
173
182
  [event],
@@ -426,7 +435,7 @@ export function EventDetailPanel({
426
435
  {/* Content */}
427
436
  <div className="flex-1 overflow-y-auto px-4 py-4 space-y-4">
428
437
  {/* Title — click to edit */}
429
- {isEditingTitle && !isWorkingLocation ? (
438
+ {isEditingTitle && !isWorkingLocation && !isOverlay ? (
430
439
  <input
431
440
  ref={titleInputRef}
432
441
  value={editingTitle}
@@ -462,10 +471,12 @@ export function EventDetailPanel({
462
471
  <h2
463
472
  className={cn(
464
473
  "-mx-0.5 rounded px-0.5 text-lg font-semibold leading-tight text-foreground",
465
- !isWorkingLocation && "cursor-text hover:bg-muted/50",
474
+ !isWorkingLocation &&
475
+ !isOverlay &&
476
+ "cursor-text hover:bg-muted/50",
466
477
  )}
467
478
  onClick={() => {
468
- if (isWorkingLocation) return;
479
+ if (isWorkingLocation || isOverlay) return;
469
480
  setEditingTitle(getEditableEventTitle(event));
470
481
  setIsEditingTitle(true);
471
482
  }}
@@ -548,20 +559,23 @@ export function EventDetailPanel({
548
559
  </div>
549
560
  ) : null}
550
561
 
551
- {event.overlayEmail && ownerLabel && (
552
- <div className="flex items-center gap-2.5 text-sm text-muted-foreground">
553
- <span
554
- aria-hidden="true"
555
- className="ml-0.5 size-2 shrink-0 rounded-full ring-1 ring-border"
556
- style={{ backgroundColor: event.ownerColor }}
557
- />
558
- <span>
559
- {t("eventForm.viewingOwnerCalendar", {
560
- owner: ownerLabel,
561
- })}
562
- </span>
563
- </div>
564
- )}
562
+ {(event.overlayEmail ||
563
+ event.calendarPrimary === false ||
564
+ event.calendarReadOnly) &&
565
+ ownerLabel && (
566
+ <div className="flex items-center gap-2.5 text-sm text-muted-foreground">
567
+ <span
568
+ aria-hidden="true"
569
+ className="ml-0.5 size-2 shrink-0 rounded-full ring-1 ring-border"
570
+ style={{ backgroundColor: event.ownerColor }}
571
+ />
572
+ <span>
573
+ {t("eventForm.viewingOwnerCalendar", {
574
+ owner: ownerLabel,
575
+ })}
576
+ </span>
577
+ </div>
578
+ )}
565
579
 
566
580
  {!isWorkingLocation &&
567
581
  (meetingLink ? (