@agent-native/core 0.100.0 → 0.100.1
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/core/CHANGELOG.md +6 -0
- package/corpus/core/package.json +1 -1
- package/corpus/templates/calendar/AGENTS.md +9 -0
- package/corpus/templates/calendar/actions/list-events.ts +659 -44
- package/corpus/templates/calendar/changelog/2026-07-13-calendar-inventory-reads-report-source-coverage.md +6 -0
- package/corpus/templates/calendar/server/lib/calendar-connector-catalog.ts +8 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +173 -51
- package/corpus/templates/calendar/server/lib/ical-fetcher.ts +13 -2
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +5 -3
- package/corpus/templates/calendar/shared/api.ts +2 -0
- package/corpus/templates/mail/AGENTS.md +12 -0
- package/corpus/templates/mail/actions/list-emails.ts +486 -4
- package/corpus/templates/mail/changelog/2026-07-13-coverage-aware-connected-inbox-inventory.md +6 -0
- package/corpus/templates/mail/server/db/schema.ts +17 -0
- package/corpus/templates/mail/server/lib/google-auth.ts +33 -5
- package/corpus/templates/mail/server/lib/inventory-cursor.ts +406 -0
- package/corpus/templates/mail/server/lib/list-inbox-emails.ts +21 -2
- package/corpus/templates/mail/server/lib/mail-connector-catalog.ts +8 -0
- package/corpus/templates/mail/server/plugins/agent-chat.ts +4 -9
- package/corpus/templates/mail/server/plugins/db.ts +20 -0
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deliberately narrow authenticated MCP surface for Calendar.
|
|
3
|
+
*
|
|
4
|
+
* External callers may read calendar coverage through list-events. Other
|
|
5
|
+
* actions remain available through the in-app agent, ask_app, or an explicit
|
|
6
|
+
* full-catalog connection; tool-search alone never makes them callable.
|
|
7
|
+
*/
|
|
8
|
+
export const CALENDAR_CONNECTOR_CATALOG = ["list-events"] as const;
|
|
@@ -190,6 +190,22 @@ const LIST_EVENT_TYPES = [
|
|
|
190
190
|
"outOfOffice",
|
|
191
191
|
"workingLocation",
|
|
192
192
|
];
|
|
193
|
+
const GOOGLE_READ_CONCURRENCY = 4;
|
|
194
|
+
|
|
195
|
+
async function mapWithConcurrency<T, R>(
|
|
196
|
+
values: T[],
|
|
197
|
+
map: (value: T) => Promise<R>,
|
|
198
|
+
): Promise<R[]> {
|
|
199
|
+
const results: R[] = [];
|
|
200
|
+
for (let index = 0; index < values.length; index += GOOGLE_READ_CONCURRENCY) {
|
|
201
|
+
results.push(
|
|
202
|
+
...(await Promise.all(
|
|
203
|
+
values.slice(index, index + GOOGLE_READ_CONCURRENCY).map(map),
|
|
204
|
+
)),
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
return results;
|
|
208
|
+
}
|
|
193
209
|
|
|
194
210
|
function mapReminders(
|
|
195
211
|
event: any,
|
|
@@ -684,6 +700,85 @@ export async function getClientsWithErrors(forEmail?: string): Promise<{
|
|
|
684
700
|
return { clients, errors };
|
|
685
701
|
}
|
|
686
702
|
|
|
703
|
+
/**
|
|
704
|
+
* Resolve a caller's connected Google accounts without refreshing tokens. This
|
|
705
|
+
* is intentionally separate from `getClientsWithErrors`: callers that accept
|
|
706
|
+
* an account filter must reject an unowned requested account before they do
|
|
707
|
+
* provider work for any account.
|
|
708
|
+
*/
|
|
709
|
+
export async function getOwnedAccountEmails(
|
|
710
|
+
forEmail?: string,
|
|
711
|
+
): Promise<string[]> {
|
|
712
|
+
if (!forEmail) return [];
|
|
713
|
+
const accounts = await listOAuthAccountsByOwner("google", forEmail);
|
|
714
|
+
return accounts.map((account) => account.accountId);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
export async function getClientsForAccountsWithErrors(
|
|
718
|
+
forEmail: string | undefined,
|
|
719
|
+
accountEmails?: string[],
|
|
720
|
+
): Promise<{
|
|
721
|
+
clients: Array<{ email: string; accessToken: string }>;
|
|
722
|
+
errors: Array<{ email: string; error: string }>;
|
|
723
|
+
requestedAccounts: string[];
|
|
724
|
+
resolvedAccounts: string[];
|
|
725
|
+
}> {
|
|
726
|
+
if (!forEmail) {
|
|
727
|
+
return {
|
|
728
|
+
clients: [],
|
|
729
|
+
errors: [],
|
|
730
|
+
requestedAccounts: [],
|
|
731
|
+
resolvedAccounts: [],
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
const accounts = await listOAuthAccountsByOwner("google", forEmail);
|
|
735
|
+
const byNormalized = new Map(
|
|
736
|
+
accounts.map((account) => [
|
|
737
|
+
account.accountId.trim().toLowerCase(),
|
|
738
|
+
account,
|
|
739
|
+
]),
|
|
740
|
+
);
|
|
741
|
+
const requestedAccounts = Array.from(
|
|
742
|
+
new Set(
|
|
743
|
+
(accountEmails ?? accounts.map((account) => account.accountId))
|
|
744
|
+
.map((email) => email.trim().toLowerCase())
|
|
745
|
+
.filter(Boolean),
|
|
746
|
+
),
|
|
747
|
+
);
|
|
748
|
+
const unowned = requestedAccounts.filter((email) => !byNormalized.has(email));
|
|
749
|
+
if (unowned.length > 0) {
|
|
750
|
+
throw new Error(
|
|
751
|
+
`Google Calendar account not connected for this user: ${unowned.join(", ")}`,
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
const selected = requestedAccounts.map((email) => byNormalized.get(email)!);
|
|
755
|
+
const clients: Array<{ email: string; accessToken: string }> = [];
|
|
756
|
+
const errors: Array<{ email: string; error: string }> = [];
|
|
757
|
+
await mapWithConcurrency(selected, async (account) => {
|
|
758
|
+
try {
|
|
759
|
+
const accessToken = await getValidAccessToken(
|
|
760
|
+
account.accountId,
|
|
761
|
+
account.tokens as unknown as GoogleTokens,
|
|
762
|
+
forEmail,
|
|
763
|
+
);
|
|
764
|
+
clients.push({ email: account.accountId, accessToken });
|
|
765
|
+
} catch (err: any) {
|
|
766
|
+
errors.push({
|
|
767
|
+
email: account.accountId,
|
|
768
|
+
error: err?.message || "Unknown refresh error",
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
clients.sort((a, b) => a.email.localeCompare(b.email));
|
|
773
|
+
errors.sort((a, b) => a.email.localeCompare(b.email));
|
|
774
|
+
return {
|
|
775
|
+
clients,
|
|
776
|
+
errors,
|
|
777
|
+
requestedAccounts,
|
|
778
|
+
resolvedAccounts: selected.map((account) => account.accountId),
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
|
|
687
782
|
export async function isConnected(forEmail?: string): Promise<boolean> {
|
|
688
783
|
return isOAuthConnected("google", forEmail ?? "");
|
|
689
784
|
}
|
|
@@ -779,12 +874,13 @@ export async function listEvents(
|
|
|
779
874
|
timeMin: string,
|
|
780
875
|
timeMax: string,
|
|
781
876
|
forEmail?: string,
|
|
877
|
+
options: { accountEmails?: string[]; maxResults?: number } = {},
|
|
782
878
|
): Promise<{
|
|
783
879
|
events: CalendarEvent[];
|
|
784
880
|
errors: Array<{ email: string; error: string }>;
|
|
785
881
|
}> {
|
|
786
882
|
const { clients, errors: refreshErrors } =
|
|
787
|
-
await
|
|
883
|
+
await getClientsForAccountsWithErrors(forEmail, options.accountEmails);
|
|
788
884
|
// Seed with refresh failures so a fully-dead connection (every account's
|
|
789
885
|
// refresh_token revoked or invalidated by a GOOGLE_CLIENT_ID rotation)
|
|
790
886
|
// reaches the caller — otherwise the result is indistinguishable from
|
|
@@ -792,8 +888,9 @@ export async function listEvents(
|
|
|
792
888
|
const errors: Array<{ email: string; error: string }> = [...refreshErrors];
|
|
793
889
|
if (clients.length === 0) return { events: [], errors };
|
|
794
890
|
|
|
795
|
-
const allResults = await
|
|
796
|
-
clients
|
|
891
|
+
const allResults = await mapWithConcurrency(
|
|
892
|
+
clients,
|
|
893
|
+
async ({ email, accessToken }) => {
|
|
797
894
|
try {
|
|
798
895
|
const events: any[] = [];
|
|
799
896
|
let pageToken: string | undefined;
|
|
@@ -803,7 +900,7 @@ export async function listEvents(
|
|
|
803
900
|
timeMax,
|
|
804
901
|
singleEvents: true,
|
|
805
902
|
orderBy: "startTime",
|
|
806
|
-
maxResults: 2500,
|
|
903
|
+
maxResults: options.maxResults ?? 2500,
|
|
807
904
|
pageToken,
|
|
808
905
|
eventTypes: LIST_EVENT_TYPES,
|
|
809
906
|
});
|
|
@@ -883,7 +980,7 @@ export async function listEvents(
|
|
|
883
980
|
errors.push({ email, error: error.message });
|
|
884
981
|
return [];
|
|
885
982
|
}
|
|
886
|
-
}
|
|
983
|
+
},
|
|
887
984
|
);
|
|
888
985
|
|
|
889
986
|
return { events: allResults.flat(), errors };
|
|
@@ -993,64 +1090,89 @@ export async function listOverlayEvents(
|
|
|
993
1090
|
timeMax: string,
|
|
994
1091
|
overlayEmails: string[],
|
|
995
1092
|
forEmail?: string,
|
|
1093
|
+
options: { accountEmails?: string[] } = {},
|
|
996
1094
|
): Promise<{
|
|
997
1095
|
events: CalendarEvent[];
|
|
998
1096
|
errors: Array<{ email: string; error: string }>;
|
|
1097
|
+
accountErrors: Array<{ email: string; error: string }>;
|
|
999
1098
|
}> {
|
|
1000
1099
|
const { clients, errors: refreshErrors } =
|
|
1001
|
-
await
|
|
1002
|
-
const errors: Array<{ email: string; error: string }> = [
|
|
1003
|
-
if (clients.length === 0)
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1100
|
+
await getClientsForAccountsWithErrors(forEmail, options.accountEmails);
|
|
1101
|
+
const errors: Array<{ email: string; error: string }> = [];
|
|
1102
|
+
if (clients.length === 0) {
|
|
1103
|
+
const message =
|
|
1104
|
+
refreshErrors[0]?.error ?? "Google Calendar is not connected";
|
|
1105
|
+
return {
|
|
1106
|
+
events: [],
|
|
1107
|
+
errors: overlayEmails.map((email) => ({ email, error: message })),
|
|
1108
|
+
accountErrors: refreshErrors,
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1007
1111
|
|
|
1008
1112
|
const allResults = await Promise.all(
|
|
1009
1113
|
overlayEmails.map(async (overlayEmail) => {
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1114
|
+
const accessErrors: string[] = [];
|
|
1115
|
+
for (const client of clients) {
|
|
1116
|
+
try {
|
|
1117
|
+
const events: any[] = [];
|
|
1118
|
+
let pageToken: string | undefined;
|
|
1119
|
+
do {
|
|
1120
|
+
const response = await calendarListEvents(
|
|
1121
|
+
client.accessToken,
|
|
1122
|
+
overlayEmail,
|
|
1123
|
+
{
|
|
1124
|
+
timeMin,
|
|
1125
|
+
timeMax,
|
|
1126
|
+
singleEvents: true,
|
|
1127
|
+
orderBy: "startTime",
|
|
1128
|
+
eventTypes: LIST_EVENT_TYPES,
|
|
1129
|
+
pageToken,
|
|
1130
|
+
},
|
|
1131
|
+
);
|
|
1132
|
+
events.push(...(response.items || []));
|
|
1133
|
+
pageToken = response.nextPageToken;
|
|
1134
|
+
} while (pageToken);
|
|
1135
|
+
return events.map((event: any) => ({
|
|
1136
|
+
id: `overlay-${overlayEmail}-${event.id}`,
|
|
1137
|
+
title: event.summary || "Busy",
|
|
1138
|
+
description: event.description || "",
|
|
1139
|
+
start: event.start?.dateTime || event.start?.date || "",
|
|
1140
|
+
end: event.end?.dateTime || event.end?.date || "",
|
|
1141
|
+
startTimeZone: event.start?.timeZone || undefined,
|
|
1142
|
+
endTimeZone: event.end?.timeZone || undefined,
|
|
1143
|
+
location: event.location || "",
|
|
1144
|
+
allDay: !event.start?.dateTime,
|
|
1145
|
+
source: "google" as const,
|
|
1146
|
+
googleEventId: event.id || undefined,
|
|
1147
|
+
htmlLink: event.htmlLink || undefined,
|
|
1148
|
+
eventType: event.eventType || "default",
|
|
1149
|
+
accountEmail: client.email,
|
|
1150
|
+
overlayEmail,
|
|
1151
|
+
...mapColor(event),
|
|
1152
|
+
attendees: mapAttendees(event),
|
|
1153
|
+
organizer: mapOrganizer(event),
|
|
1154
|
+
createdAt: event.created || new Date().toISOString(),
|
|
1155
|
+
updatedAt: event.updated || new Date().toISOString(),
|
|
1156
|
+
}));
|
|
1157
|
+
} catch (error: any) {
|
|
1158
|
+
const message = error?.message || "Unable to read overlay calendar";
|
|
1159
|
+
console.error(
|
|
1160
|
+
`[listOverlayEvents] Error fetching ${overlayEmail} via ${client.email}:`,
|
|
1161
|
+
message,
|
|
1162
|
+
);
|
|
1163
|
+
accessErrors.push(`${client.email}: ${message}`);
|
|
1164
|
+
}
|
|
1049
1165
|
}
|
|
1166
|
+
|
|
1167
|
+
errors.push({
|
|
1168
|
+
email: overlayEmail,
|
|
1169
|
+
error: `No selected Google account could read this overlay (${accessErrors.join("; ")})`,
|
|
1170
|
+
});
|
|
1171
|
+
return [];
|
|
1050
1172
|
}),
|
|
1051
1173
|
);
|
|
1052
1174
|
|
|
1053
|
-
return { events: allResults.flat(), errors };
|
|
1175
|
+
return { events: allResults.flat(), errors, accountErrors: refreshErrors };
|
|
1054
1176
|
}
|
|
1055
1177
|
|
|
1056
1178
|
export async function getEvent(
|
|
@@ -273,6 +273,7 @@ export async function fetchICalEvents(
|
|
|
273
273
|
color: string,
|
|
274
274
|
from: string,
|
|
275
275
|
to: string,
|
|
276
|
+
options: { throwOnError?: boolean } = {},
|
|
276
277
|
): Promise<CalendarEvent[]> {
|
|
277
278
|
const httpUrl = normalizeUrl(url);
|
|
278
279
|
|
|
@@ -281,6 +282,7 @@ export async function fetchICalEvents(
|
|
|
281
282
|
} catch {
|
|
282
283
|
// Silently degrade — never echo the URL or reason back. A loud error
|
|
283
284
|
// helps an attacker map internal infrastructure via probe responses.
|
|
285
|
+
if (options.throwOnError) throw new Error("ICS feed URL is not allowed");
|
|
284
286
|
return [];
|
|
285
287
|
}
|
|
286
288
|
|
|
@@ -294,9 +296,17 @@ export async function fetchICalEvents(
|
|
|
294
296
|
},
|
|
295
297
|
{ maxRedirects: 3 },
|
|
296
298
|
);
|
|
297
|
-
if (!response.ok)
|
|
299
|
+
if (!response.ok) {
|
|
300
|
+
if (options.throwOnError) throw new Error("ICS feed request failed");
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
298
303
|
icsText = await response.text();
|
|
299
|
-
} catch {
|
|
304
|
+
} catch (error) {
|
|
305
|
+
if (options.throwOnError) {
|
|
306
|
+
throw error instanceof Error
|
|
307
|
+
? error
|
|
308
|
+
: new Error("ICS feed request failed");
|
|
309
|
+
}
|
|
300
310
|
return [];
|
|
301
311
|
}
|
|
302
312
|
|
|
@@ -323,6 +333,7 @@ export async function fetchICalEvents(
|
|
|
323
333
|
location: e.location || "",
|
|
324
334
|
allDay: e.allDay,
|
|
325
335
|
source: "ical" as const,
|
|
336
|
+
sourceId: feedId,
|
|
326
337
|
color,
|
|
327
338
|
createdAt: now,
|
|
328
339
|
updatedAt: now,
|
|
@@ -13,6 +13,7 @@ import { z } from "zod";
|
|
|
13
13
|
// why `autoDiscoverActions` on its own produces 404s for action routes in
|
|
14
14
|
// production.
|
|
15
15
|
import actionsRegistry from "../../.generated/actions-registry.js";
|
|
16
|
+
import { CALENDAR_CONNECTOR_CATALOG } from "../lib/calendar-connector-catalog.js";
|
|
16
17
|
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
18
19
|
// Register calendar event-bus events
|
|
@@ -83,6 +84,7 @@ const INITIAL_TOOL_NAMES = [
|
|
|
83
84
|
export default createAgentChatPlugin({
|
|
84
85
|
appId: "calendar",
|
|
85
86
|
initialToolNames: INITIAL_TOOL_NAMES,
|
|
87
|
+
connectorCatalog: [...CALENDAR_CONNECTOR_CATALOG],
|
|
86
88
|
// Enable sandboxed JavaScript execution so Calendar agents can fetch,
|
|
87
89
|
// paginate, and reduce provider data through providerFetch() without us
|
|
88
90
|
// hardcoding one action per Google Calendar / CRM endpoint.
|
|
@@ -102,7 +104,7 @@ Google Calendar events are NOT stored in the local database. They are fetched li
|
|
|
102
104
|
|
|
103
105
|
Provider-specific Calendar actions are shortcuts, not limits. If a first-class action cannot express the exact Google Calendar/CRM endpoint, calendar id, filter, request body, pagination mode, attendee search, recurrence field, or API version needed, call \`provider-api-catalog\` and \`provider-api-docs\` as needed, then call \`provider-api-request\` against the provider's real HTTP API. Use this raw provider API escape hatch instead of weakening the answer, broadening filters, or claiming Calendar cannot do something the underlying API can do.
|
|
104
106
|
|
|
105
|
-
- \`pnpm action view-screen\` — See
|
|
107
|
+
- \`pnpm action view-screen\` — See the visible UI state (current view, date, selected event). Use it for questions about what the user is looking at, not as a prerequisite for deterministic schedule reads.
|
|
106
108
|
- \`pnpm action list-events --from YYYY-MM-DD --to YYYY-MM-DD\` — List events from Google Calendar. The --to date is exclusive, so use tomorrow for today's events.
|
|
107
109
|
- \`pnpm action search-events --query "term" --from YYYY-MM-DD --to YYYY-MM-DD\` — Convenience bounded search by title, attendees, organizer, location, or description. For relationship history, all-calendar discovery, exact attendee/domain search, or custom pagination, prefer provider-api-request with provider=google_calendar.
|
|
108
110
|
- \`pnpm action provider-api-catalog\` / \`provider-api-docs\` / \`provider-api-request\` — Inspect and call the real Google Calendar, Apollo, Gong, HubSpot, and Pylon APIs directly. For Google Calendar events.list pagination use provider=google_calendar, path=/calendars/primary/events, query={...}, fetchAllPages={cursorPath:"nextPageToken",cursorParam:"pageToken",itemsPath:"items"}. For large relationship-history scans, pass stageAs and pagination={nextCursorPath:"nextPageToken",cursorParam:"pageToken",maxPages:N} with itemsPath="items", then use query-staged-dataset.
|
|
@@ -125,14 +127,14 @@ Provider-specific Calendar actions are shortcuts, not limits. If a first-class a
|
|
|
125
127
|
Use \`create-event\` or \`update-event --colorId 1..11\` when the user wants one specific Google Calendar event color changed. Use \`update-calendar-visual-preferences\` when the user wants broad app-layer display rules such as color-coding meetings by internal/external, 1:1/group, focus time, or one display color for all Google events.
|
|
126
128
|
|
|
127
129
|
## Google Connection Check
|
|
128
|
-
|
|
130
|
+
For broad, deterministic schedule reads, call list-events directly for the requested date range in inventory/coverage mode, even when UI context is irrelevant or the user is on Settings, Booking Links, or another non-calendar page. Do not require view-screen as a connection preflight, and do not infer a Google Calendar connection problem from the current screen. Only ask the user to reconnect Google if list-events or the explicit Google status reports an auth/connection error.
|
|
129
131
|
|
|
130
132
|
When the user explicitly asks you to connect or reconnect Google Calendar, call \`connect-google-calendar\` and give them the returned link. Do not call raw HTTP/fetch/web-request against \`/_agent-native/google/auth-url\`; that route depends on the user's browser session and will return 401 from the agent backend.
|
|
131
133
|
|
|
132
134
|
For relationship-history or frequency questions such as "who have I met at Adobe?" or "how often do I meet with Mattel?", prefer the raw Google Calendar API through provider-api-request so you can choose the exact timeMin/timeMax, q, calendarId, maxResults, and pageToken behavior. Stage large paginated results before analysis. Do not conclude there are no recurring meetings from the visible range or from a convenience action alone.
|
|
133
135
|
|
|
134
136
|
## Context Awareness
|
|
135
|
-
The UI writes navigation state including the current view, date, view mode (day/week/month), and selected event ID.
|
|
137
|
+
The UI writes navigation state including the current view, date, view mode (day/week/month), and selected event ID. Check view-screen when the answer depends on that visible state; skip it for headless inventory/coverage reads with an explicit date range.
|
|
136
138
|
|
|
137
139
|
When the user says "show me", "go to", "open", or "switch to" a view or date, ALWAYS use the \`navigate\` action to update the UI first, then fetch/display data. The user expects to SEE the result in the app.`,
|
|
138
140
|
mentionProviders: async () => {
|
|
@@ -11,6 +11,8 @@ export interface CalendarEvent {
|
|
|
11
11
|
location: string;
|
|
12
12
|
allDay: boolean;
|
|
13
13
|
source: "local" | "google" | "ical";
|
|
14
|
+
/** Stable feed/source identifier for non-Google inventory provenance. */
|
|
15
|
+
sourceId?: string;
|
|
14
16
|
googleEventId?: string;
|
|
15
17
|
/** Absolute Google Calendar web URL for Google events */
|
|
16
18
|
htmlLink?: string;
|
|
@@ -7,6 +7,18 @@ updates mail state through actions and application state.
|
|
|
7
7
|
Detailed draft, queue, and contact-resolution patterns live in
|
|
8
8
|
`.agents/skills/`.
|
|
9
9
|
|
|
10
|
+
## Coverage-aware inventory reads
|
|
11
|
+
|
|
12
|
+
`list-emails` remains the compatibility list action for the UI and internal
|
|
13
|
+
callers. External MCP callers receive its structured inventory envelope by
|
|
14
|
+
default (or pass `format: "inventory"`). Inventory reads use `accountEmails`
|
|
15
|
+
for an explicit set; the legacy singular `account` alias cannot be combined
|
|
16
|
+
with it. The response reports each account's success, empty result, exhaustion
|
|
17
|
+
or bounded error, so partial coverage must never be described as complete.
|
|
18
|
+
Inventory items are intentionally compact metadata only — use `get-email` or
|
|
19
|
+
`get-thread` only after selecting a specific result when body content is
|
|
20
|
+
needed.
|
|
21
|
+
|
|
10
22
|
## Core Rules
|
|
11
23
|
|
|
12
24
|
- Store large file/blob payloads in configured file/blob storage, not SQL: no
|