@mulmoclaude/core 5.0.1 → 5.3.0

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 (41) hide show
  1. package/assets/helps/error-recovery.md +78 -0
  2. package/assets/helps/google-calendar-collection.md +130 -15
  3. package/assets/helps/google.md +14 -4
  4. package/dist/collection/core/schemaZ.d.ts +15 -2
  5. package/dist/collection/core/viewChatPolicy.d.ts +6 -3
  6. package/dist/collection/index.cjs +6 -3
  7. package/dist/collection/index.cjs.map +1 -1
  8. package/dist/collection/index.js +6 -3
  9. package/dist/collection/index.js.map +1 -1
  10. package/dist/collection/registry/server/index.cjs +2 -2
  11. package/dist/collection/registry/server/index.js +2 -2
  12. package/dist/collection/server/index.cjs +2 -2
  13. package/dist/collection/server/index.js +2 -2
  14. package/dist/collection-watchers/index.cjs +2 -2
  15. package/dist/collection-watchers/index.js +2 -2
  16. package/dist/{discovery-CBjI1lx9.js → discovery-B_64PBZU.js} +14 -3
  17. package/dist/{discovery-CBjI1lx9.js.map → discovery-B_64PBZU.js.map} +1 -1
  18. package/dist/{discovery-DCMW05Bp.cjs → discovery-CDXYswtw.cjs} +14 -3
  19. package/dist/{discovery-DCMW05Bp.cjs.map → discovery-CDXYswtw.cjs.map} +1 -1
  20. package/dist/feeds/server/index.cjs +2 -2
  21. package/dist/feeds/server/index.js +2 -2
  22. package/dist/google/calendar.d.ts +24 -0
  23. package/dist/google/collectionPush.d.ts +45 -2
  24. package/dist/google/deletePlan.d.ts +32 -0
  25. package/dist/google/eventDerived.d.ts +19 -0
  26. package/dist/google/eventSpanInput.d.ts +58 -0
  27. package/dist/google/index.cjs +286 -18
  28. package/dist/google/index.cjs.map +1 -1
  29. package/dist/google/index.d.ts +5 -2
  30. package/dist/google/index.js +272 -19
  31. package/dist/google/index.js.map +1 -1
  32. package/dist/remote-view/index.cjs +7 -7
  33. package/dist/remote-view/index.cjs.map +1 -1
  34. package/dist/remote-view/index.d.ts +3 -3
  35. package/dist/remote-view/index.js +7 -7
  36. package/dist/remote-view/index.js.map +1 -1
  37. package/dist/{server-BMZ_BcPH.js → server-7POrhHKv.js} +2 -2
  38. package/dist/{server-BMZ_BcPH.js.map → server-7POrhHKv.js.map} +1 -1
  39. package/dist/{server-rA9FSkl2.cjs → server-DfYwSX8b.cjs} +2 -2
  40. package/dist/{server-rA9FSkl2.cjs.map → server-DfYwSX8b.cjs.map} +1 -1
  41. package/package.json +1 -1
@@ -7,7 +7,7 @@ const require_root = require("../root-BvwBUcol.cjs");
7
7
  const require_utils_fetch = require("../utils/fetch.cjs");
8
8
  const require_itemId = require("../itemId-CGT2J7YK.cjs");
9
9
  const require_project = require("../project-C1ep9pvo.cjs");
10
- const require_discovery = require("../discovery-DCMW05Bp.cjs");
10
+ const require_discovery = require("../discovery-CDXYswtw.cjs");
11
11
  const require_paths = require("../paths-C8-jxR0s.cjs");
12
12
  let node_fs = require("node:fs");
13
13
  let node_path = require("node:path");
@@ -45,6 +45,172 @@ var isIsoDateTimeWithOffset = (value) => {
45
45
  return isRealCalendarDate(year, month, day) && timeInRange && offsetInRange;
46
46
  };
47
47
  //#endregion
48
+ //#region src/google/deletePlan.ts
49
+ var ATTENDEES_REASON = "it carries attendees — deleting an invited event withdraws it from their calendars too";
50
+ var ALREADY_GONE_REASON = "it is no longer in Google";
51
+ /** The message the push reports for a refusal, with the event named. */
52
+ var deleteRefusalMessage = (eventId, refusal) => `${eventId}: left in Google because ${refusal.kind === "has-attendees" ? ATTENDEES_REASON : ALREADY_GONE_REASON}`;
53
+ /** Decide one deletion against what Google currently holds.
54
+ *
55
+ * `null` means the event is already absent — a 404/410 on the read. Nothing to
56
+ * delete, and reported apart from a refusal so "we chose not to" never reads
57
+ * as "we could not".
58
+ *
59
+ * ANY attendee refuses, including the entry Google adds for the organiser. A
60
+ * rule that tried to exclude "only me" would have to work out which entry is
61
+ * the user from a payload that may not say, and being wrong there withdraws a
62
+ * real invitation. Solo events — the ones this feature exists for — carry no
63
+ * attendees at all, so the blunt rule costs them nothing. */
64
+ function planDelete(fetched) {
65
+ if (fetched === null) return {
66
+ ok: false,
67
+ kind: "already-gone"
68
+ };
69
+ return fetched.attendeeCount > 0 ? {
70
+ ok: false,
71
+ kind: "has-attendees"
72
+ } : {
73
+ ok: true,
74
+ etag: fetched.etag
75
+ };
76
+ }
77
+ //#endregion
78
+ //#region src/google/util.ts
79
+ var ONE_SECOND_MS = 1e3;
80
+ var ONE_MINUTE_MS = 6e4;
81
+ function isRecord(value) {
82
+ return typeof value === "object" && value !== null && !Array.isArray(value);
83
+ }
84
+ //#endregion
85
+ //#region src/google/eventDerived.ts
86
+ var stringAt = (record, key) => typeof record[key] === "string" ? record[key] : "";
87
+ var recordsIn = (value) => Array.isArray(value) ? value.filter(isRecord) : [];
88
+ /** The signed-in user's own `responseStatus` on this event, `""` when Google
89
+ * reported none.
90
+ *
91
+ * `""` is the common case, not an edge one: an event with no attendees has no
92
+ * entry to mark `self`, which is most of a personal calendar. So it means
93
+ * "Google said nothing", never "not going" — filter with `!= "declined"`, not
94
+ * with `== "accepted"`, or every solo event disappears too.
95
+ *
96
+ * Google's values: `needsAction`, `declined`, `tentative`, `accepted`. */
97
+ function selfResponseStatus(attendees) {
98
+ const self = recordsIn(attendees).find((attendee) => attendee.self === true);
99
+ return self === void 0 ? "" : stringAt(self, "responseStatus");
100
+ }
101
+ var VIDEO_ENTRY_POINT = "video";
102
+ /** The URL that joins this event's meeting, `""` when it has none.
103
+ *
104
+ * Only the `video` entry point. The others are a phone number and a dial-in
105
+ * PIN page, and a column named for joining that sometimes holds `tel:` would
106
+ * be worse than one that is empty — the caller can see empty and fall back.
107
+ *
108
+ * `hangoutLink` (#3229) already carries this for Google Meet; this is what
109
+ * reaches the events whose conference is Zoom or Teams. */
110
+ function conferenceVideoUri(conferenceData) {
111
+ if (!isRecord(conferenceData)) return "";
112
+ const video = recordsIn(conferenceData.entryPoints).find((entry) => entry.entryPointType === VIDEO_ENTRY_POINT);
113
+ return video === void 0 ? "" : stringAt(video, "uri");
114
+ }
115
+ //#endregion
116
+ //#region src/google/eventSpanInput.ts
117
+ var DATE_ONLY_RE$1 = /^\d{4}-\d{2}-\d{2}$/;
118
+ /** Whether `value` is a bare calendar day — the all-day spelling of an event
119
+ * time, and the one shape `isIsoDateTimeWithOffset` is built to refuse.
120
+ *
121
+ * Realness comes from the parser the record lint, the calendar grid and the
122
+ * push already share, so every surface agrees that `2026-02-30` is not a day.
123
+ * The regex runs first because that parser trims, and a value with whitespace
124
+ * around it would validate here and then reach Google verbatim. */
125
+ var isCalendarDateOnly = (value) => DATE_ONLY_RE$1.test(value) && require_itemId.parseIsoDate(value) !== null;
126
+ /** One end of a span, or null when it is neither shape Calendar accepts. */
127
+ var toEventTimeInput = (value) => {
128
+ if (isCalendarDateOnly(value)) return { date: value };
129
+ return isIsoDateTimeWithOffset(value) ? { dateTime: value } : null;
130
+ };
131
+ /** The rule every surface states for a value that is neither shape. Shared so
132
+ * the tool, the remote host and their tests cannot drift apart. */
133
+ var EVENT_TIME_HINT = "must be an ISO 8601 date-time with a timezone offset (e.g. 2026-07-17T09:00:00+09:00), or a date for an all-day event (e.g. 2026-07-17)";
134
+ /** The same rule with the offending key named, for a surface whose error text
135
+ * carries no path of its own. */
136
+ var eventTimeHint = (key) => `${key} ${EVENT_TIME_HINT}`;
137
+ /** All-day `end` is exclusive, so a one-day event ends on the NEXT day. Spelled
138
+ * out because the natural reading of "all day on the 17th" is `end` = the
139
+ * 17th, which Calendar refuses. */
140
+ var ALL_DAY_END_HINT = "an all-day `end` is EXCLUSIVE — it is the day AFTER the last day, so a single day on 2026-07-17 needs end 2026-07-18";
141
+ var MIXED_SPAN_HINT = "start and end must both be date-times with an offset, or both be dates for an all-day event — not one of each";
142
+ /** A lone all-day end cannot be patched: the stored event's kind is unknown
143
+ * here, and giving one end of a timed event a `date` makes an event Calendar
144
+ * rejects — with a message that names neither end. Both together are
145
+ * unambiguous, so that is what the caller is asked for. */
146
+ var LONE_ALL_DAY_HINT = `pass BOTH start and end to move an all-day event — ${ALL_DAY_END_HINT}`;
147
+ var isAllDay = (time) => "date" in time;
148
+ /** Both ends of a create, validated together.
149
+ *
150
+ * Only the all-day ordering is checked: a timed span's ordering depends on the
151
+ * two offsets, which Calendar resolves and reports on clearly, while an all-day
152
+ * `end <= start` comes back as an opaque 400. Lexicographic comparison is exact
153
+ * for `YYYY-MM-DD` — fixed-width, most significant part first. */
154
+ function resolveSpanInput(start, end) {
155
+ const startTime = toEventTimeInput(start);
156
+ if (startTime === null) return {
157
+ ok: false,
158
+ reason: eventTimeHint("start")
159
+ };
160
+ const endTime = toEventTimeInput(end);
161
+ if (endTime === null) return {
162
+ ok: false,
163
+ reason: eventTimeHint("end")
164
+ };
165
+ if (isAllDay(startTime) !== isAllDay(endTime)) return {
166
+ ok: false,
167
+ reason: MIXED_SPAN_HINT
168
+ };
169
+ if (isAllDay(startTime) && isAllDay(endTime) && endTime.date <= startTime.date) return {
170
+ ok: false,
171
+ reason: ALL_DAY_END_HINT
172
+ };
173
+ return {
174
+ ok: true,
175
+ span: {
176
+ start: startTime,
177
+ end: endTime
178
+ }
179
+ };
180
+ }
181
+ /** One end of an EDIT, where the other end is absent. */
182
+ function resolveLoneEnd(key, value) {
183
+ const time = toEventTimeInput(value);
184
+ if (time === null) return {
185
+ ok: false,
186
+ reason: eventTimeHint(key)
187
+ };
188
+ if (isAllDay(time)) return {
189
+ ok: false,
190
+ reason: LONE_ALL_DAY_HINT
191
+ };
192
+ return {
193
+ ok: true,
194
+ times: { [key]: time }
195
+ };
196
+ }
197
+ /** The span of an EDIT, where either end may be absent. */
198
+ function resolvePartialSpanInput(start, end) {
199
+ if (start !== void 0 && end !== void 0) {
200
+ const resolved = resolveSpanInput(start, end);
201
+ return resolved.ok ? {
202
+ ok: true,
203
+ times: resolved.span
204
+ } : resolved;
205
+ }
206
+ if (start !== void 0) return resolveLoneEnd("start", start);
207
+ if (end !== void 0) return resolveLoneEnd("end", end);
208
+ return {
209
+ ok: true,
210
+ times: {}
211
+ };
212
+ }
213
+ //#endregion
48
214
  //#region src/google/paths.ts
49
215
  /** Same directory as every other cross-app per-user file — see
50
216
  * `../global-config/paths.ts`, which owns the spelling. */
@@ -62,13 +228,6 @@ function googleSecretsDir(home) {
62
228
  return (0, node_path.join)(home ?? (0, node_os.homedir)(), ".secrets");
63
229
  }
64
230
  //#endregion
65
- //#region src/google/util.ts
66
- var ONE_SECOND_MS = 1e3;
67
- var ONE_MINUTE_MS = 6e4;
68
- function isRecord(value) {
69
- return typeof value === "object" && value !== null && !Array.isArray(value);
70
- }
71
- //#endregion
72
231
  //#region src/google/clientSecret.ts
73
232
  var isNonEmptyString = (value) => typeof value === "string" && value !== "";
74
233
  var isInstalledClientSecret = (value) => {
@@ -599,6 +758,10 @@ var EVENT_SYNC_PAGE_SIZE = 2500;
599
758
  var MAX_EVENT_SYNC_PAGES = 200;
600
759
  var CALENDAR_META_PAGE_SIZE = 1;
601
760
  var HTTP_GONE = 410;
761
+ /** A GET or DELETE on an event that is not there. Exported because the push's
762
+ * delete sweep has to read "already gone" as success, not as a failure to
763
+ * retry — a retained baseline would announce the same phantom deletion on
764
+ * every run. */
602
765
  var EVENT_ABSENT_STATUSES = [404, HTTP_GONE];
603
766
  /** An `If-Match` write whose etag no longer matches — someone else changed the
604
767
  * event since it was read. */
@@ -616,6 +779,10 @@ var CANCELLED_EVENT_STATUS = "cancelled";
616
779
  * apart while they share a sync token silently loses events (#2184). */
617
780
  var canonicalCalendarId = (calendarId) => calendarId || DEFAULT_CALENDAR_ID;
618
781
  var eventsUrl = (calendarId) => `${CALENDAR_BASE_URL}/calendars/${encodeURIComponent(canonicalCalendarId(calendarId))}/events`;
782
+ /** People Google lists on the event, `0` when it carries none. Counts EVERY
783
+ * entry, including the one Google adds for the organiser: see `deletePlan.ts`
784
+ * for why the guard is not cleverer than that. */
785
+ var attendeeCount = (record) => Array.isArray(record.attendees) ? record.attendees.length : 0;
619
786
  var eventTime$1 = (value) => {
620
787
  if (!isRecord(value)) return "";
621
788
  if (typeof value.dateTime === "string") return value.dateTime;
@@ -639,7 +806,9 @@ var toEventSummary = (value) => {
639
806
  updated: stringField(record, "updated"),
640
807
  transparency: stringField(record, "transparency"),
641
808
  eventType: stringField(record, "eventType"),
642
- hangoutLink: stringField(record, "hangoutLink")
809
+ hangoutLink: stringField(record, "hangoutLink"),
810
+ selfResponseStatus: selfResponseStatus(record.attendees),
811
+ conferenceVideoUri: conferenceVideoUri(record.conferenceData)
643
812
  };
644
813
  };
645
814
  var toCalendarSummary = (value) => {
@@ -730,9 +899,11 @@ async function updateCalendarEvent(accessToken, input) {
730
899
  async function getCalendarEvent(accessToken, input) {
731
900
  try {
732
901
  const fetched = await googleRequest(CALENDAR_API_LABEL, accessToken, eventUrl(input.calendarId, input.eventId));
902
+ const record = asRecord(fetched);
733
903
  return {
734
904
  event: toEventSummary(fetched),
735
- etag: stringField(asRecord(fetched), "etag")
905
+ etag: stringField(record, "etag"),
906
+ attendeeCount: attendeeCount(record)
736
907
  };
737
908
  } catch (error) {
738
909
  if (isGoogleApiError(error) && EVENT_ABSENT_STATUSES.includes(error.status)) return null;
@@ -743,7 +914,10 @@ async function getCalendarEvent(accessToken, input) {
743
914
  * return; a second delete of the same id answers 410, which surfaces as a
744
915
  * GoogleApiError rather than being swallowed. */
745
916
  async function deleteCalendarEvent(accessToken, input) {
746
- await googleRequest(CALENDAR_API_LABEL, accessToken, eventUrl(input.calendarId, input.eventId), { method: "DELETE" });
917
+ await googleRequest(CALENDAR_API_LABEL, accessToken, eventUrl(input.calendarId, input.eventId), {
918
+ method: "DELETE",
919
+ ...input.ifMatch ? { extraHeaders: { "If-Match": input.ifMatch } } : {}
920
+ });
747
921
  }
748
922
  async function listCalendarEvents(accessToken, input = {}) {
749
923
  const params = new URLSearchParams({
@@ -1525,7 +1699,9 @@ var UNPUSHABLE_EVENT_FIELDS = {
1525
1699
  updated: "",
1526
1700
  transparency: "",
1527
1701
  eventType: "",
1528
- hangoutLink: ""
1702
+ hangoutLink: "",
1703
+ selfResponseStatus: "",
1704
+ conferenceVideoUri: ""
1529
1705
  };
1530
1706
  /** A baseline shaped as the event it came from, so the comparison can run
1531
1707
  * through the very projection that wrote the record. */
@@ -1646,7 +1822,9 @@ var liveDeps = {
1646
1822
  findCollection: findCalendarCollection,
1647
1823
  isLinked: async () => Boolean((await loadGoogleTokens())?.refresh_token),
1648
1824
  accessToken: getGoogleAccessToken,
1649
- calendarMeta: liveCalendarMeta
1825
+ calendarMeta: liveCalendarMeta,
1826
+ fetchEvent: getCalendarEvent,
1827
+ deleteEvent: deleteCalendarEvent
1650
1828
  };
1651
1829
  var localValue = (ctx, record, source) => {
1652
1830
  const field = ctx.bySource[source];
@@ -1856,7 +2034,7 @@ var UNPUSHED_KINDS = ["conflict", "error"];
1856
2034
  /** Whether this outcome leaves a local edit that only exists locally, so the
1857
2035
  * pull must not overwrite the record and the baseline must not advance. */
1858
2036
  var isUnpushed = (kind) => UNPUSHED_KINDS.includes(kind);
1859
- function tally(slug, attempts, localDeletes) {
2037
+ function tally(slug, attempts, deletes) {
1860
2038
  const outcomes = attempts.map((attempt) => attempt.outcome);
1861
2039
  const count = (kind) => outcomes.filter((outcome) => outcome.kind === kind).length;
1862
2040
  return {
@@ -1864,9 +2042,10 @@ function tally(slug, attempts, localDeletes) {
1864
2042
  created: count("created"),
1865
2043
  updated: count("updated"),
1866
2044
  conflicts: count("conflict"),
1867
- localDeletes,
1868
- skipped: outcomes.flatMap((outcome) => outcome.kind === "skipped" ? [outcome.message] : []),
1869
- errors: outcomes.flatMap((outcome) => outcome.kind === "error" ? [outcome.message] : []),
2045
+ localDeletes: deletes.seen,
2046
+ deletedInGoogle: deletes.deleted.length,
2047
+ skipped: [...outcomes.flatMap((outcome) => outcome.kind === "skipped" ? [outcome.message] : []), ...deletes.skipped],
2048
+ errors: [...outcomes.flatMap((outcome) => outcome.kind === "error" ? [outcome.message] : []), ...deletes.errors],
1870
2049
  unpushedIds: attempts.filter((attempt) => isUnpushed(attempt.outcome.kind)).map((attempt) => attempt.eventId)
1871
2050
  };
1872
2051
  }
@@ -1895,6 +2074,69 @@ async function unsentLocalEdits(collection, workspaceRoot) {
1895
2074
  const shadow = await loadCalendarShadow(schema.googleCalendar?.calendarId, workspaceRoot);
1896
2075
  return locallyEditedIds(await require_discovery.storeFor(collection, { workspaceRoot }).list(), shadow, pushableMap(schema.googleCalendar?.map ?? {}), schema.primaryKey, schema.fields);
1897
2076
  }
2077
+ var noDeletes = (seen) => ({
2078
+ seen,
2079
+ deleted: [],
2080
+ skipped: [],
2081
+ errors: []
2082
+ });
2083
+ /** A delete whose event changed in Google between the guard reading it and the
2084
+ * delete landing. Reported rather than retried: what changed may be the very
2085
+ * thing the guard refuses, and re-reading in a loop would race the same way. */
2086
+ var staleDeleteMessage = (eventId) => `${eventId}: left in Google because it changed there while this push was checking it — press Push again to re-check`;
2087
+ /** Delete ONE event and forget its baseline, or say why it was left alone.
2088
+ *
2089
+ * The baseline entry goes only after Google confirms, and it must go: the
2090
+ * baseline is what `locallyDeletedIds` reads, so a kept entry would re-report
2091
+ * the same deletion on every run forever. A refusal keeps its entry on
2092
+ * purpose — the event IS still standing in Google, and the report is the only
2093
+ * thing saying so.
2094
+ *
2095
+ * The delete carries the etag the guard read. Without it, an attendee added
2096
+ * between the two requests would be withdrawn by a decision taken before they
2097
+ * existed — the same race `updateCalendarEvent` already guards, and it costs
2098
+ * more here because a delete cannot be undone from this side. */
2099
+ async function propagateOneDelete(eventId, deps) {
2100
+ const decision = planDelete(await deps.fetchEvent(eventId));
2101
+ if (!decision.ok) {
2102
+ if (decision.kind === "already-gone") {
2103
+ await deps.forget(eventId);
2104
+ return {};
2105
+ }
2106
+ return { skipped: [deleteRefusalMessage(eventId, decision)] };
2107
+ }
2108
+ try {
2109
+ await deps.deleteEvent(eventId, decision.etag);
2110
+ } catch (error) {
2111
+ if (!isGoogleApiError(error)) throw error;
2112
+ if (EVENT_ABSENT_STATUSES.includes(error.status)) {
2113
+ await deps.forget(eventId);
2114
+ return {};
2115
+ }
2116
+ if (error.status !== 412) throw error;
2117
+ return { skipped: [staleDeleteMessage(eventId)] };
2118
+ }
2119
+ await deps.forget(eventId);
2120
+ log.info("google", "calendar event deleted by push", { id: eventId });
2121
+ return { deleted: [eventId] };
2122
+ }
2123
+ /** Carry every local deletion, when the collection opted in.
2124
+ *
2125
+ * Sequential rather than concurrent: each one writes the shared baseline file,
2126
+ * and an interrupted run has to leave a state the next run resumes from rather
2127
+ * than a partially applied batch. */
2128
+ async function sweepDeletes(eventIds, propagate, deps) {
2129
+ if (!propagate || eventIds.length === 0) return noDeletes(eventIds.length);
2130
+ const sweep = noDeletes(eventIds.length);
2131
+ for (const eventId of eventIds) try {
2132
+ const one = await propagateOneDelete(eventId, deps);
2133
+ sweep.deleted.push(...one.deleted ?? []);
2134
+ sweep.skipped.push(...one.skipped ?? []);
2135
+ } catch (error) {
2136
+ sweep.errors.push(`${eventId}: could not be deleted in Google — ${require_dist.errorMessage(error)}`);
2137
+ }
2138
+ return sweep;
2139
+ }
1898
2140
  /** Push one collection WITHOUT taking the calendar lock.
1899
2141
  *
1900
2142
  * The caller must already hold it. The scheduled push→pull cycle
@@ -1933,7 +2175,18 @@ async function pushCollectionNow(collection, workspaceRoot, deps = liveDeps) {
1933
2175
  });
1934
2176
  await saveCalendarShadow(calendarId, pushedShadow([outcome]), workspaceRoot);
1935
2177
  }
1936
- const result = tally(slug, attempts, locallyDeletedIds(shadow, records.map((record) => fieldText(record[schema.primaryKey]))).length);
2178
+ const result = tally(slug, attempts, await sweepDeletes(locallyDeletedIds(shadow, records.map((record) => fieldText(record[schema.primaryKey]))), schema.googleCalendar?.propagateDeletes === true, {
2179
+ fetchEvent: (eventId) => (deps.fetchEvent ?? getCalendarEvent)(accessToken, {
2180
+ calendarId,
2181
+ eventId
2182
+ }),
2183
+ deleteEvent: (eventId, ifMatch) => (deps.deleteEvent ?? deleteCalendarEvent)(accessToken, {
2184
+ calendarId,
2185
+ eventId,
2186
+ ifMatch
2187
+ }),
2188
+ forget: (eventId) => saveCalendarShadow(calendarId, { [eventId]: null }, workspaceRoot)
2189
+ }));
1937
2190
  if (result.errors.length > 0) log.warn("google", "calendar push finished with errors", {
1938
2191
  slug,
1939
2192
  errors: result.errors.length
@@ -2891,8 +3144,11 @@ async function deleteDriveFile(accessToken, input) {
2891
3144
  await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}/${encodeURIComponent(input.fileId)}`, { method: "DELETE" });
2892
3145
  }
2893
3146
  //#endregion
3147
+ exports.ALL_DAY_END_HINT = ALL_DAY_END_HINT;
2894
3148
  exports.CANCELLED_EVENT_STATUS = CANCELLED_EVENT_STATUS;
2895
3149
  exports.DEFAULT_LIST_MAX_RESULTS = DEFAULT_LIST_MAX_RESULTS;
3150
+ exports.EVENT_ABSENT_STATUSES = EVENT_ABSENT_STATUSES;
3151
+ exports.EVENT_TIME_HINT = EVENT_TIME_HINT;
2896
3152
  exports.GOOGLE_AUTH_CANCELLED = GOOGLE_AUTH_CANCELLED;
2897
3153
  exports.GOOGLE_CALENDARLIST_SCOPE = GOOGLE_CALENDARLIST_SCOPE;
2898
3154
  exports.GOOGLE_CALENDAR_SCOPE = GOOGLE_CALENDAR_SCOPE;
@@ -2904,7 +3160,9 @@ exports.GoogleApiError = GoogleApiError;
2904
3160
  exports.HTTP_CONFLICT = HTTP_CONFLICT;
2905
3161
  exports.HTTP_FORBIDDEN = HTTP_FORBIDDEN;
2906
3162
  exports.HTTP_PRECONDITION_FAILED = HTTP_PRECONDITION_FAILED;
3163
+ exports.LONE_ALL_DAY_HINT = LONE_ALL_DAY_HINT;
2907
3164
  exports.MAX_LIST_RESULTS = MAX_LIST_RESULTS;
3165
+ exports.MIXED_SPAN_HINT = MIXED_SPAN_HINT;
2908
3166
  exports.PARTIAL_CALENDAR_WINDOW = PARTIAL_CALENDAR_WINDOW;
2909
3167
  exports.PROTECTION_UNKNOWN = PROTECTION_UNKNOWN;
2910
3168
  exports.PUSHABLE_SOURCE_FIELDS = PUSHABLE_SOURCE_FIELDS;
@@ -2940,6 +3198,7 @@ exports.collectCalendarPages = collectCalendarPages;
2940
3198
  exports.collectionsNeedingBackfill = collectionsNeedingBackfill;
2941
3199
  exports.commitLinkedTokens = commitLinkedTokens;
2942
3200
  exports.completeTask = completeTask;
3201
+ exports.conferenceVideoUri = conferenceVideoUri;
2943
3202
  exports.configureGoogleHost = configureGoogleHost;
2944
3203
  exports.conflictingFields = conflictingFields;
2945
3204
  exports.createCalendarEvent = createCalendarEvent;
@@ -2949,7 +3208,9 @@ exports.createTask = createTask;
2949
3208
  exports.deleteCalendarEvent = deleteCalendarEvent;
2950
3209
  exports.deleteDriveFile = deleteDriveFile;
2951
3210
  exports.deleteGoogleTokens = deleteGoogleTokens;
3211
+ exports.deleteRefusalMessage = deleteRefusalMessage;
2952
3212
  exports.deleteTask = deleteTask;
3213
+ exports.eventTimeHint = eventTimeHint;
2953
3214
  exports.fieldText = fieldText;
2954
3215
  exports.findClientSecretPath = findClientSecretPath;
2955
3216
  exports.getCalendarColors = getCalendarColors;
@@ -2965,6 +3226,7 @@ exports.googleTokenPath = googleTokenPath;
2965
3226
  exports.groupByCalendar = groupByCalendar;
2966
3227
  exports.groupsNeedingBackfill = groupsNeedingBackfill;
2967
3228
  exports.heldBack = heldBack;
3229
+ exports.isCalendarDateOnly = isCalendarDateOnly;
2968
3230
  exports.isCalendarSyncDue = isCalendarSyncDue;
2969
3231
  exports.isClientSettableEventId = isClientSettableEventId;
2970
3232
  exports.isDeniedAccessRole = isDeniedAccessRole;
@@ -2995,6 +3257,7 @@ exports.mergeShadow = mergeShadow;
2995
3257
  exports.needsCalendarBackfill = needsCalendarBackfill;
2996
3258
  exports.orphanedCalendarId = orphanedCalendarId;
2997
3259
  exports.pickBoundary = pickBoundary;
3260
+ exports.planDelete = planDelete;
2998
3261
  exports.planRecord = planRecord;
2999
3262
  exports.pullProtectionFor = pullProtectionFor;
3000
3263
  exports.pullableEvents = pullableEvents;
@@ -3006,12 +3269,16 @@ exports.readDriveFile = readDriveFile;
3006
3269
  exports.releaseOrphanedCalendarToken = releaseOrphanedCalendarToken;
3007
3270
  exports.reportedAccessRole = reportedAccessRole;
3008
3271
  exports.resolveEventSpan = resolveEventSpan;
3272
+ exports.resolvePartialSpanInput = resolvePartialSpanInput;
3273
+ exports.resolveSpanInput = resolveSpanInput;
3009
3274
  exports.resumableToken = resumableToken;
3010
3275
  exports.saveCalendarShadow = saveCalendarShadow;
3011
3276
  exports.saveCalendarSyncToken = saveCalendarSyncToken;
3012
3277
  exports.saveGoogleTokens = saveGoogleTokens;
3278
+ exports.selfResponseStatus = selfResponseStatus;
3013
3279
  exports.shadowUpdates = shadowUpdates;
3014
3280
  exports.stateLockPath = stateLockPath;
3281
+ exports.sweepDeletes = sweepDeletes;
3015
3282
  exports.syncCalendarEvents = syncCalendarEvents;
3016
3283
  exports.syncCalendarForCollection = syncCalendarForCollection;
3017
3284
  exports.syncCalendarGroup = syncCalendarGroup;
@@ -3024,6 +3291,7 @@ exports.toCollectionDateTime = toCollectionDateTime;
3024
3291
  exports.toCollectionRecord = toCollectionRecord;
3025
3292
  exports.toDriveFileSummary = toDriveFileSummary;
3026
3293
  exports.toEventSummary = toEventSummary;
3294
+ exports.toEventTimeInput = toEventTimeInput;
3027
3295
  exports.toGoogleEventTime = toGoogleEventTime;
3028
3296
  exports.toShadowEvent = toShadowEvent;
3029
3297
  exports.toTaskListSummary = toTaskListSummary;