@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +6 -0
- package/corpus/templates/calendar/AGENTS.md +4 -0
- package/corpus/templates/calendar/actions/delete-event.ts +2 -2
- package/corpus/templates/calendar/actions/event-action-helpers.ts +7 -0
- package/corpus/templates/calendar/actions/get-event.ts +40 -3
- package/corpus/templates/calendar/actions/list-events.ts +49 -2
- package/corpus/templates/calendar/actions/list-google-calendars.ts +24 -0
- package/corpus/templates/calendar/actions/update-calendar-visual-preferences.ts +52 -1
- package/corpus/templates/calendar/actions/update-event.ts +2 -2
- package/corpus/templates/calendar/actions/view-screen.ts +54 -2
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +33 -19
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +31 -5
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +102 -0
- package/corpus/templates/calendar/app/hooks/use-events.ts +45 -10
- package/corpus/templates/calendar/app/hooks/use-google-auth.ts +8 -1
- package/corpus/templates/calendar/app/hooks/use-google-calendars.ts +41 -0
- package/corpus/templates/calendar/app/hooks/use-view-preferences.ts +56 -4
- package/corpus/templates/calendar/app/lib/shared-calendar-demo.ts +187 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +50 -5
- package/corpus/templates/calendar/changelog/2026-09-01-calendars-shared-with-your-connected-google-accounts-can-now.md +6 -0
- package/corpus/templates/calendar/changelog/2026-09-02-updated-the-booking-link-og-preview-image-with-the-new-monoc.md +6 -0
- package/corpus/templates/calendar/server/lib/booking-og-image.ts +8 -18
- package/corpus/templates/calendar/server/lib/google-api.ts +10 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +215 -21
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/calendar/server/plugins/feature-flags.ts +5 -0
- package/corpus/templates/calendar/shared/api.ts +20 -0
- package/corpus/templates/calendar/shared/calendar-view-preferences.ts +28 -1
- package/corpus/templates/calendar/shared/feature-flags.ts +8 -0
- package/corpus/templates/calendar/shared/google-calendar-sources.ts +46 -0
- package/corpus/templates/forms/server/lib/form-og-image.ts +7 -17
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/social-og-image.js +6 -20
- package/dist/shared/social-meta.d.ts +1 -1
- package/dist/shared/social-meta.js +1 -1
- package/package.json +3 -3
package/corpus/README.md
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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 {
|
|
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: {
|
|
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.
|
|
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 &&
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 &&
|
|
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
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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 ? (
|