@mulmoclaude/core 1.12.0 → 1.14.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 (117) hide show
  1. package/assets/helps/error-recovery.md +110 -1
  2. package/dist/{calendarGrid-CaS9er8i.cjs → calendarGrid-BQYqOCnF.cjs} +8 -9
  3. package/dist/{calendarGrid-CaS9er8i.cjs.map → calendarGrid-BQYqOCnF.cjs.map} +1 -1
  4. package/dist/{calendarGrid-CrGxeCOG.js → calendarGrid-DivcNKkX.js} +8 -9
  5. package/dist/{calendarGrid-CrGxeCOG.js.map → calendarGrid-DivcNKkX.js.map} +1 -1
  6. package/dist/collection/core/project.d.ts +11 -0
  7. package/dist/collection/core/schemaZ.d.ts +4 -4
  8. package/dist/collection/index.cjs +5 -4
  9. package/dist/collection/index.js +5 -5
  10. package/dist/collection/registry/server/index.cjs +3 -3
  11. package/dist/collection/registry/server/index.cjs.map +1 -1
  12. package/dist/collection/registry/server/index.js +3 -3
  13. package/dist/collection/registry/server/index.js.map +1 -1
  14. package/dist/collection/server/csvQuery.d.ts +7 -2
  15. package/dist/collection/server/csvStore.d.ts +2 -1
  16. package/dist/collection/server/index.cjs +3 -3
  17. package/dist/collection/server/index.js +3 -3
  18. package/dist/collection-watchers/index.cjs +10 -25
  19. package/dist/collection-watchers/index.cjs.map +1 -1
  20. package/dist/collection-watchers/index.js +10 -25
  21. package/dist/collection-watchers/index.js.map +1 -1
  22. package/dist/{discovery-NRy3tyUA.cjs → discovery-CRDx6TS4.cjs} +88 -47
  23. package/dist/discovery-CRDx6TS4.cjs.map +1 -0
  24. package/dist/{discovery-B4CZvrXR.js → discovery-D6p4ITNR.js} +88 -47
  25. package/dist/discovery-D6p4ITNR.js.map +1 -0
  26. package/dist/{dist-pWpC-b04.cjs → dist-CsgSfWwR.cjs} +66 -1
  27. package/dist/{dist-pWpC-b04.cjs.map → dist-CsgSfWwR.cjs.map} +1 -1
  28. package/dist/{dist-Cwk0e12G.js → dist-D8zokgGo.js} +25 -2
  29. package/dist/{dist-Cwk0e12G.js.map → dist-D8zokgGo.js.map} +1 -1
  30. package/dist/feeds/index.cjs +2 -2
  31. package/dist/feeds/index.js +2 -2
  32. package/dist/feeds/server/engine.d.ts +27 -0
  33. package/dist/feeds/server/index.cjs +97 -26
  34. package/dist/feeds/server/index.cjs.map +1 -1
  35. package/dist/feeds/server/index.d.ts +1 -1
  36. package/dist/feeds/server/index.js +96 -27
  37. package/dist/feeds/server/index.js.map +1 -1
  38. package/dist/google/calendar.d.ts +23 -9
  39. package/dist/google/calendarStateLock.d.ts +23 -0
  40. package/dist/google/collectionProjection.d.ts +0 -8
  41. package/dist/google/collectionPush.d.ts +13 -8
  42. package/dist/google/collectionSync.d.ts +80 -10
  43. package/dist/google/index.cjs +366 -98
  44. package/dist/google/index.cjs.map +1 -1
  45. package/dist/google/index.d.ts +6 -4
  46. package/dist/google/index.js +357 -99
  47. package/dist/google/index.js.map +1 -1
  48. package/dist/{ingestTypes-D1GQdG8e.cjs → ingestTypes-C7IJSqv4.cjs} +2 -2
  49. package/dist/{ingestTypes-D1GQdG8e.cjs.map → ingestTypes-C7IJSqv4.cjs.map} +1 -1
  50. package/dist/{ingestTypes-XReG-li7.js → ingestTypes-DmtDLRmt.js} +2 -2
  51. package/dist/{ingestTypes-XReG-li7.js.map → ingestTypes-DmtDLRmt.js.map} +1 -1
  52. package/dist/notifier/index.cjs +1 -1
  53. package/dist/notifier/index.js +1 -1
  54. package/dist/notifier/store.d.ts +4 -4
  55. package/dist/{notifier-BdA5qzhe.js → notifier-Dxh8mEFh.js} +39 -25
  56. package/dist/notifier-Dxh8mEFh.js.map +1 -0
  57. package/dist/{notifier-tMsAXyXp.cjs → notifier-Wc4M24Bi.cjs} +39 -25
  58. package/dist/notifier-Wc4M24Bi.cjs.map +1 -0
  59. package/dist/plugin-vue/index.cjs +4 -2
  60. package/dist/plugin-vue/index.cjs.map +1 -1
  61. package/dist/plugin-vue/index.js +4 -2
  62. package/dist/plugin-vue/index.js.map +1 -1
  63. package/dist/project-B0j_Vf_Q.js +33 -0
  64. package/dist/project-B0j_Vf_Q.js.map +1 -0
  65. package/dist/project-C1ep9pvo.cjs +44 -0
  66. package/dist/project-C1ep9pvo.cjs.map +1 -0
  67. package/dist/{promptSafety-FD8cQn3e.cjs → promptSafety-B52GvaJc.cjs} +99 -85
  68. package/dist/promptSafety-B52GvaJc.cjs.map +1 -0
  69. package/dist/{promptSafety-C--op_6a.js → promptSafety-DJgaDjM4.js} +99 -85
  70. package/dist/promptSafety-DJgaDjM4.js.map +1 -0
  71. package/dist/remote-host/index.cjs +2 -1
  72. package/dist/remote-host/index.d.ts +10 -0
  73. package/dist/remote-host/index.js +2 -2
  74. package/dist/remote-host/server/index.cjs +2 -2
  75. package/dist/remote-host/server/index.cjs.map +1 -1
  76. package/dist/remote-host/server/index.js +2 -2
  77. package/dist/remote-host/server/index.js.map +1 -1
  78. package/dist/{remote-host-DkDVxNim.cjs → remote-host-CtQjagPt.cjs} +53 -1
  79. package/dist/remote-host-CtQjagPt.cjs.map +1 -0
  80. package/dist/remote-host-Dba4lF3Z.js +82 -0
  81. package/dist/remote-host-Dba4lF3Z.js.map +1 -0
  82. package/dist/remote-view/index.cjs +6 -4
  83. package/dist/remote-view/index.cjs.map +1 -1
  84. package/dist/remote-view/index.js +6 -4
  85. package/dist/remote-view/index.js.map +1 -1
  86. package/dist/scheduler/index.cjs +1 -1
  87. package/dist/scheduler/index.js +1 -1
  88. package/dist/{server-DdyIvFhz.js → server-D6EUf3BF.js} +46 -27
  89. package/dist/server-D6EUf3BF.js.map +1 -0
  90. package/dist/{server-7U-3DE2e.cjs → server-D7_oRbTJ.cjs} +46 -27
  91. package/dist/server-D7_oRbTJ.cjs.map +1 -0
  92. package/dist/utils/index.cjs +1 -1
  93. package/dist/utils/index.js +1 -1
  94. package/dist/whisper/index.cjs +2 -6
  95. package/dist/whisper/index.cjs.map +1 -1
  96. package/dist/whisper/index.js +2 -6
  97. package/dist/whisper/index.js.map +1 -1
  98. package/dist/wiki/index.cjs +2 -2
  99. package/dist/wiki/index.cjs.map +1 -1
  100. package/dist/wiki/index.js +2 -2
  101. package/dist/wiki/index.js.map +1 -1
  102. package/package.json +2 -2
  103. package/dist/discovery-B4CZvrXR.js.map +0 -1
  104. package/dist/discovery-NRy3tyUA.cjs.map +0 -1
  105. package/dist/notifier-BdA5qzhe.js.map +0 -1
  106. package/dist/notifier-tMsAXyXp.cjs.map +0 -1
  107. package/dist/project-BWI5w_BT.cjs +0 -17
  108. package/dist/project-BWI5w_BT.cjs.map +0 -1
  109. package/dist/project-bU98ycsy.js +0 -12
  110. package/dist/project-bU98ycsy.js.map +0 -1
  111. package/dist/promptSafety-C--op_6a.js.map +0 -1
  112. package/dist/promptSafety-FD8cQn3e.cjs.map +0 -1
  113. package/dist/remote-host-D_BRFHcI.js +0 -36
  114. package/dist/remote-host-D_BRFHcI.js.map +0 -1
  115. package/dist/remote-host-DkDVxNim.cjs.map +0 -1
  116. package/dist/server-7U-3DE2e.cjs.map +0 -1
  117. package/dist/server-DdyIvFhz.js.map +0 -1
@@ -1,11 +1,12 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rolldown_runtime = require("../rolldown-runtime-D6vf50IK.cjs");
3
- const require_dist = require("../dist-pWpC-b04.cjs");
3
+ const require_dist = require("../dist-CsgSfWwR.cjs");
4
4
  const require_text = require("../text-CaAh9ZSO.cjs");
5
5
  const require_atomic = require("../atomic-C_7YpMiM.cjs");
6
6
  const require_utils_fetch = require("../utils/fetch.cjs");
7
- const require_calendarGrid = require("../calendarGrid-CaS9er8i.cjs");
8
- const require_discovery = require("../discovery-NRy3tyUA.cjs");
7
+ const require_calendarGrid = require("../calendarGrid-BQYqOCnF.cjs");
8
+ const require_project = require("../project-C1ep9pvo.cjs");
9
+ const require_discovery = require("../discovery-CRDx6TS4.cjs");
9
10
  let node_fs = require("node:fs");
10
11
  let node_path = require("node:path");
11
12
  node_path = require_rolldown_runtime.__toESM(node_path, 1);
@@ -592,6 +593,7 @@ var CALENDAR_LIST_PAGE_SIZE = 250;
592
593
  var MAX_CALENDAR_LIST_PAGES = 40;
593
594
  var EVENT_SYNC_PAGE_SIZE = 2500;
594
595
  var MAX_EVENT_SYNC_PAGES = 200;
596
+ var CALENDAR_META_PAGE_SIZE = 1;
595
597
  var HTTP_GONE = 410;
596
598
  var EVENT_ABSENT_STATUSES = [404, HTTP_GONE];
597
599
  /** An `If-Match` write whose etag no longer matches — someone else changed the
@@ -803,20 +805,34 @@ async function collectCalendarPages(fetchPage, maxPages = MAX_CALENDAR_LIST_PAGE
803
805
  }
804
806
  return calendars;
805
807
  }
806
- /** The calendars the user has added/subscribed to (primary + secondary +
807
- * shared), each with its id, name and colour, following pagination. Needs the
808
- * calendar-list read scope (GOOGLE_CALENDARLIST_SCOPE). */
809
- /** One calendar's own resource, addressed by id rather than looked up in the
810
- * user's list.
808
+ var toCalendarMeta = (value) => {
809
+ const record = asRecord(value);
810
+ return {
811
+ timeZone: stringField(record, "timeZone"),
812
+ accessRole: stringField(record, "accessRole")
813
+ };
814
+ };
815
+ /** One calendar's zone and the caller's role on it, read by id rather than
816
+ * looked up in the user's list.
811
817
  *
812
818
  * `calendarList` only holds calendars the user has ADDED; a calendar shared
813
- * with them can be readable and writable by id without appearing there. This is
814
- * how a push learns such a calendar's `timeZone`. Note the resource carries no
815
- * `accessRole` — that is a calendarList property — so reachability here says
816
- * nothing about writability. */
817
- async function getCalendar(accessToken, calendarId) {
818
- return toCalendarSummary(await googleRequest(CALENDAR_API_LABEL, accessToken, `${CALENDAR_BASE_URL}/calendars/${encodeURIComponent(canonicalCalendarId(calendarId))}`));
819
+ * with them can be readable and writable by id without appearing there, and a
820
+ * push still needs its zone to send an offset-less `dateTime`.
821
+ *
822
+ * Deliberately `events.list` and NOT `calendars.get`: the latter takes only
823
+ * full-calendar scopes (`calendar`, `calendar.readonly`, `calendar.calendars*`)
824
+ * and this app asks for none of them, so it answers 403 for every account it
825
+ * links (#2735). The events list carries the same `timeZone` — plus the
826
+ * `accessRole` the calendar resource does not have — under `calendar.events`,
827
+ * which the push already holds because it writes with it. Only the envelope is
828
+ * read; the one event asked for is the smallest page Google will serve. */
829
+ async function getCalendarMeta(accessToken, calendarId) {
830
+ const params = new URLSearchParams({ maxResults: String(CALENDAR_META_PAGE_SIZE) });
831
+ return toCalendarMeta(await googleRequest(CALENDAR_API_LABEL, accessToken, `${eventsUrl(calendarId)}?${params.toString()}`));
819
832
  }
833
+ /** The calendars the user has added/subscribed to (primary + secondary +
834
+ * shared), each with its id, name and colour, following pagination. Needs the
835
+ * calendar-list read scope (GOOGLE_CALENDARLIST_SCOPE). */
820
836
  async function listCalendars(accessToken) {
821
837
  return collectCalendarPages(async (pageToken) => {
822
838
  const params = new URLSearchParams({ maxResults: String(CALENDAR_LIST_PAGE_SIZE) });
@@ -838,6 +854,159 @@ async function getCalendarColors(accessToken) {
838
854
  };
839
855
  }
840
856
  //#endregion
857
+ //#region src/google/calendarStateLock.ts
858
+ var LOCK_MODE = 384;
859
+ /** How long a lock may sit untouched before another process may reclaim it.
860
+ *
861
+ * Sized against the HOLD time, which is one read plus one atomic write — a few
862
+ * milliseconds. Generous by three orders of magnitude so a slow disk never
863
+ * looks like a crash, while a host that died mid-write blocks the others for
864
+ * at most this long. */
865
+ var LOCK_STALE_MS = 1e4;
866
+ /** How long a caller waits before giving up and proceeding unlocked. */
867
+ var LOCK_WAIT_MS = 5e3;
868
+ var LOCK_RETRY_MS = 20;
869
+ var liveLockClock = {
870
+ now: () => Date.now(),
871
+ sleep: (delay_ms) => new Promise((resolve) => setTimeout(resolve, delay_ms))
872
+ };
873
+ var isEexist = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
874
+ /** The file's bytes, or null when it is not there. */
875
+ async function readLockRaw(lockPath) {
876
+ try {
877
+ return await (0, node_fs_promises.readFile)(lockPath, "utf-8");
878
+ } catch {
879
+ return null;
880
+ }
881
+ }
882
+ /** Absent means unheld; UNREADABLE does not — see `stealIfStale`. */
883
+ function parseLock(raw) {
884
+ try {
885
+ const parsed = JSON.parse(raw);
886
+ return require_dist.hasStringProp(parsed, "holder") ? { holder: parsed.holder } : null;
887
+ } catch {
888
+ return null;
889
+ }
890
+ }
891
+ async function readLock(lockPath) {
892
+ const raw = await readLockRaw(lockPath);
893
+ return raw === null ? null : parseLock(raw);
894
+ }
895
+ /** Take the lock, or answer false because someone else holds a live one. */
896
+ async function tryAcquire(lockPath, holder) {
897
+ try {
898
+ const handle = await (0, node_fs_promises.open)(lockPath, "wx", LOCK_MODE);
899
+ try {
900
+ await handle.writeFile(JSON.stringify({ holder }));
901
+ } finally {
902
+ await handle.close();
903
+ }
904
+ return true;
905
+ } catch (error) {
906
+ if (!isEexist(error)) throw error;
907
+ return false;
908
+ }
909
+ }
910
+ /** How long this lock has existed, per the filesystem that holds it.
911
+ *
912
+ * From mtime rather than anything the holder wrote down, and for every lock
913
+ * rather than only the unreadable ones. A timestamp written by one host and
914
+ * compared against another host's clock is two wall clocks pretending to be
915
+ * one: skew either way reclaims a live lock or immortalises a dead one (Codex
916
+ * review #2690). Every host reading this workspace sees the SAME mtime.
917
+ *
918
+ * This still assumes the reader's clock and the filesystem's are within a TTL
919
+ * of each other — true by construction on a local disk, and a sub-second NTP
920
+ * question on a network mount, against a window of ten seconds.
921
+ *
922
+ * Reading mtime also covers the moment `open("wx")` has created the file but
923
+ * its payload has not landed yet: "unreadable" is a state every healthy lock
924
+ * passes through, and one being born reads as newborn rather than dead.
925
+ *
926
+ * Null when the file is gone, which is not an age but an absence. */
927
+ async function lockAgeMs(lockPath, clock) {
928
+ const stamped = await (0, node_fs_promises.stat)(lockPath).catch(() => null);
929
+ return stamped === null ? null : clock.now() - stamped.mtimeMs;
930
+ }
931
+ /** Drop a lock that has sat untouched past the TTL, whatever it says.
932
+ *
933
+ * Readable or not, nothing is reclaimed until it is demonstrably stale. For a
934
+ * readable one the holder is re-read and compared before the unlink, so a
935
+ * holder that renewed in between is not evicted. That narrows the window
936
+ * rather than closing it — two processes can still both decide to clear the
937
+ * same corpse. The cost of losing that race is one lost update, which is what
938
+ * this file reduces rather than a new failure, and reaching it takes a host
939
+ * dying mid-write plus a full TTL of waiting. */
940
+ async function stealIfStale(lockPath, clock) {
941
+ const raw = await readLockRaw(lockPath);
942
+ if (raw === null) return;
943
+ const held = parseLock(raw);
944
+ const age_ms = await lockAgeMs(lockPath, clock);
945
+ if (age_ms === null || age_ms < LOCK_STALE_MS) return;
946
+ if (held === null) {
947
+ log.warn("google", "reclaiming an unreadable calendar state lock", {
948
+ lockPath,
949
+ age_ms
950
+ });
951
+ await (0, node_fs_promises.unlink)(lockPath).catch(() => void 0);
952
+ return;
953
+ }
954
+ if ((await readLock(lockPath))?.holder !== held.holder) return;
955
+ log.warn("google", "reclaiming a calendar state lock whose holder went quiet", {
956
+ lockPath,
957
+ age_ms
958
+ });
959
+ await (0, node_fs_promises.unlink)(lockPath).catch(() => void 0);
960
+ }
961
+ async function release(lockPath, holder) {
962
+ if ((await readLock(lockPath))?.holder !== holder) return;
963
+ await (0, node_fs_promises.unlink)(lockPath).catch(() => void 0);
964
+ }
965
+ /** Run `mutate` with no other process mutating the same file.
966
+ *
967
+ * Fails OPEN. A workspace that cannot create the lock file — a read-only
968
+ * mount, a missing directory, an exhausted disk — still syncs, the way it did
969
+ * before #2679; the lock removes a race, it is not a precondition for
970
+ * correctness. The same applies to waiting the timeout out: proceeding
971
+ * unlocked risks the lost update this exists to prevent, whereas refusing
972
+ * would stop the sync outright.
973
+ *
974
+ * Note what this cannot promise. `O_EXCL` is atomic on a local filesystem and
975
+ * on NFSv3+, but a workspace living in a consumer sync folder (Dropbox,
976
+ * iCloud, Drive) has no such guarantee — there the file is replicated after
977
+ * the fact and both hosts believe they hold it. */
978
+ async function withCalendarStateLock(lockPath, mutate, clock = liveLockClock) {
979
+ const holder = (0, node_crypto.randomUUID)();
980
+ const deadline = clock.now() + LOCK_WAIT_MS;
981
+ let holding = false;
982
+ try {
983
+ holding = await acquireBefore(lockPath, holder, deadline, clock);
984
+ if (!holding) log.warn("google", "proceeding without the calendar state lock — another host is holding it", { lockPath });
985
+ return await mutate();
986
+ } finally {
987
+ if (holding) await release(lockPath, holder);
988
+ }
989
+ }
990
+ /** Poll until the lock is ours or the deadline passes. */
991
+ async function acquireBefore(lockPath, holder, deadline, clock) {
992
+ for (;;) {
993
+ try {
994
+ if (await tryAcquire(lockPath, holder)) return true;
995
+ await stealIfStale(lockPath, clock);
996
+ } catch (error) {
997
+ log.warn("google", "could not take the calendar state lock — mutating without it", {
998
+ lockPath,
999
+ error: String(error)
1000
+ });
1001
+ return false;
1002
+ }
1003
+ if (clock.now() >= deadline) return false;
1004
+ await clock.sleep(LOCK_RETRY_MS);
1005
+ }
1006
+ }
1007
+ /** `<state file>.lock`, so each state file is serialised on its own. */
1008
+ var stateLockPath = (statePath) => `${statePath}.lock`;
1009
+ //#endregion
841
1010
  //#region src/google/calendarSyncStore.ts
842
1011
  var SYNC_STATE_MODE = 384;
843
1012
  /** `<workspace>/data/calendar/.sync-state.json` */
@@ -865,11 +1034,12 @@ var writeQueue$1 = { tail: Promise.resolve() };
865
1034
  * nothing in this process can read between the two. A `null` state skips the
866
1035
  * write; `result` is what the caller learns about the decision. */
867
1036
  async function updateStateWith(decide, workspaceRoot) {
868
- const run = writeQueue$1.tail.then(async () => {
1037
+ const statePath = calendarSyncStatePath(workspaceRoot);
1038
+ const run = writeQueue$1.tail.then(async () => await withCalendarStateLock(stateLockPath(statePath), async () => {
869
1039
  const { state, result } = decide(await readState$1(workspaceRoot));
870
- if (state) await writeJsonAtomicWithMode(calendarSyncStatePath(workspaceRoot), state, SYNC_STATE_MODE);
1040
+ if (state) await writeJsonAtomicWithMode(statePath, state, SYNC_STATE_MODE);
871
1041
  return result;
872
- });
1042
+ }));
873
1043
  writeQueue$1.tail = run.catch(() => void 0);
874
1044
  return await run;
875
1045
  }
@@ -989,10 +1159,11 @@ async function readState(workspaceRoot) {
989
1159
  }
990
1160
  var writeQueue = { tail: Promise.resolve() };
991
1161
  async function updateState(mutate, workspaceRoot) {
992
- const run = writeQueue.tail.then(async () => {
1162
+ const statePath = calendarPushStatePath(workspaceRoot);
1163
+ const run = writeQueue.tail.then(async () => await withCalendarStateLock(stateLockPath(statePath), async () => {
993
1164
  const state = await readState(workspaceRoot);
994
- await writeJsonAtomicWithMode(calendarPushStatePath(workspaceRoot), { events: mutate(state.events) }, PUSH_STATE_MODE);
995
- });
1165
+ await writeJsonAtomicWithMode(statePath, { events: mutate(state.events) }, PUSH_STATE_MODE);
1166
+ }));
996
1167
  writeQueue.tail = run.catch(() => void 0);
997
1168
  return await run;
998
1169
  }
@@ -1197,19 +1368,6 @@ function toCollectionRecord(event, map, primaryKey, fields) {
1197
1368
  [primaryKey]: event.id
1198
1369
  };
1199
1370
  }
1200
- /** Google's mapped values laid over whatever the record already holds.
1201
- *
1202
- * A record file is written whole (`writeItem`), so projecting alone would drop
1203
- * every column the map does not name — a collection cannot then carry a local
1204
- * note next to a mirrored event, because the next pull that touches that event
1205
- * silently deletes it. The projection still wins on the fields it covers: those
1206
- * are Google's to own. */
1207
- function mergeIntoExisting(existing, projected) {
1208
- return {
1209
- ...existing ?? {},
1210
- ...projected
1211
- };
1212
- }
1213
1371
  //#endregion
1214
1372
  //#region src/google/pushPlan.ts
1215
1373
  /** The event fields Google lets a caller write. `htmlLink` and `status` are
@@ -1224,7 +1382,7 @@ var PUSHABLE_SOURCE_FIELDS = [
1224
1382
  "description",
1225
1383
  "location"
1226
1384
  ];
1227
- var isPushableSource = (source) => PUSHABLE_SOURCE_FIELDS.includes(source);
1385
+ var isPushableSource = (source) => PUSHABLE_SOURCE_FIELDS.some((field) => field === source);
1228
1386
  /** The subset of a schema's `map` a push can act on: collection field → event field. */
1229
1387
  function pushableMap(map) {
1230
1388
  return Object.fromEntries(Object.entries(map).filter((entry) => isPushableSource(entry[1])));
@@ -1346,21 +1504,28 @@ function locallyDeletedIds(shadow, presentIds) {
1346
1504
  var WRITABLE_ACCESS_ROLES = ["owner", "writer"];
1347
1505
  /** Whether to refuse the whole push before touching a single event.
1348
1506
  *
1349
- * Only on POSITIVE evidence of a non-writable role. An unlisted calendar is
1350
- * unknown and must fall through: hard-denying it would block the feature
1351
- * outright for a calendar the user can in fact write to. Those attempts surface
1352
- * Google's own 403 per record if the write really is not allowed. */
1507
+ * Only on POSITIVE evidence of a non-writable role. A calendar whose role
1508
+ * neither source reported is unknown and must fall through: hard-denying it
1509
+ * would block the feature outright for a calendar the user can in fact write
1510
+ * to. Those attempts surface Google's own 403 per record if the write really is
1511
+ * not allowed. */
1353
1512
  var isDeniedAccessRole = (accessRole) => accessRole !== null && !WRITABLE_ACCESS_ROLES.includes(accessRole);
1513
+ /** A role Google left blank is UNKNOWN, not a denial. `isDeniedAccessRole`
1514
+ * refuses every role it does not recognise — `""` included — so passing an
1515
+ * unreported one straight through would refuse a calendar Google simply said
1516
+ * nothing about. */
1517
+ var reportedAccessRole = (accessRole) => accessRole || null;
1354
1518
  /** Resolve the calendar a schema names, for its writability and its timezone.
1355
1519
  *
1356
- * The list is consulted first because only it reports `accessRole`, which is
1357
- * what lets a read-only calendar be refused with the real reason instead of an
1358
- * opaque per-event 403. `"primary"` is matched on the `primary` flag, not the
1359
- * id — the primary calendar's own id is the account's email address, which never
1360
- * equals the literal the schema declares.
1520
+ * The list is consulted first because it answers for every added calendar in
1521
+ * one call the push already makes. `"primary"` is matched on the `primary`
1522
+ * flag, not the id — the primary calendar's own id is the account's email
1523
+ * address, which never equals the literal the schema declares.
1361
1524
  *
1362
- * A calendar absent from the list is NOT denied: it is fetched by id for its
1363
- * timezone and its role left unknown. A 404 there means the calendar really is
1525
+ * A calendar absent from the list is NOT denied: `calendarList` holds only the
1526
+ * calendars the user has added, so one shared with write access can be missing
1527
+ * from it (Codex review). It is asked about itself instead, which reports the
1528
+ * same zone AND the real role. A 404 there means the calendar is genuinely
1364
1529
  * unreachable, and propagates as a `failed` outcome. */
1365
1530
  async function liveCalendarMeta(accessToken, calendarId) {
1366
1531
  const key = canonicalCalendarId(calendarId);
@@ -1370,10 +1535,13 @@ async function liveCalendarMeta(accessToken, calendarId) {
1370
1535
  accessRole: listed.accessRole,
1371
1536
  timeZone: listed.timeZone
1372
1537
  };
1373
- const direct = await getCalendar(accessToken, calendarId);
1374
- log.info("google", "calendar is not in the user's list — pushing without an up-front role check", { calendarId: key });
1538
+ const direct = await getCalendarMeta(accessToken, calendarId);
1539
+ log.info("google", "calendar is not in the user's list — role read from the calendar itself", {
1540
+ calendarId: key,
1541
+ accessRole: direct.accessRole
1542
+ });
1375
1543
  return {
1376
- accessRole: null,
1544
+ accessRole: reportedAccessRole(direct.accessRole),
1377
1545
  timeZone: direct.timeZone
1378
1546
  };
1379
1547
  }
@@ -1487,15 +1655,23 @@ function buildPatch(ctx, record, changed, shadow) {
1487
1655
  }
1488
1656
  };
1489
1657
  }
1658
+ /** Google has dropped the event this record mirrors.
1659
+ *
1660
+ * The advice used to be "sync first, then re-create it", which sent people
1661
+ * nowhere: Google never resends a cancellation, so a sync sees nothing to do —
1662
+ * and before #2688 the sync that DID see it deleted the record along with the
1663
+ * edit. Deliberately silent on whether Google will hand the id back; that is
1664
+ * unverified. */
1665
+ var goneFromGoogle = (eventId) => ({
1666
+ kind: "skipped",
1667
+ message: `${eventId}: Google no longer has this event, and your local edit was kept. Delete this record, or copy it into a new one.`
1668
+ });
1490
1669
  async function updateFromRecord(ctx, eventId, record, changed, shadow) {
1491
1670
  const fetched = await getCalendarEvent(ctx.accessToken, {
1492
1671
  calendarId: ctx.calendarId,
1493
1672
  eventId
1494
1673
  });
1495
- if (fetched === null || fetched.event.status === "cancelled") return {
1496
- kind: "skipped",
1497
- message: `${eventId}: the event no longer exists in Google — sync first, then re-create it`
1498
- };
1674
+ if (fetched === null || fetched.event.status === "cancelled") return goneFromGoogle(eventId);
1499
1675
  if (conflictingFields(shadow, toShadowEvent(fetched.event), changed).length > 0) return { kind: "conflict" };
1500
1676
  const built = buildPatch(ctx, record, changed, shadow);
1501
1677
  if (!built.ok) return {
@@ -1550,9 +1726,9 @@ async function createOrAdopt(ctx, eventId, record) {
1550
1726
  }
1551
1727
  /** A 403 on a write is a permissions answer, but `googleApiError` appends the
1552
1728
  * "is the API enabled for the Cloud project?" hint to every 403 — true for a
1553
- * disabled API, misleading here. Reached when the up-front role check could not
1554
- * run because the calendar is not in the user's list, which is exactly when the
1555
- * user needs the real reason. */
1729
+ * disabled API, misleading here. Reached when the up-front gate let the push
1730
+ * through on an unknown role, which is exactly when the user needs the real
1731
+ * reason. */
1556
1732
  function writeFailure(eventId, error) {
1557
1733
  if (isGoogleApiError(error) && error.status === 403) return {
1558
1734
  kind: "skipped",
@@ -1723,7 +1899,42 @@ function classifyDelete(eventId, kind) {
1723
1899
  message: `delete ${eventId}: ${kind}`
1724
1900
  };
1725
1901
  }
1726
- async function applyEvent(collection, event, workspaceRoot) {
1902
+ /** Whether this record still says what the workspace last saw Google say.
1903
+ *
1904
+ * Compared against the baseline as it stood when this run STARTED READING, not
1905
+ * as it stands now: a full re-walk clears the baseline before writing a new one
1906
+ * (`restartFullSync`), so reading it live would answer "no baseline" for every
1907
+ * event and protect nothing exactly when the window is widest (#2684).
1908
+ *
1909
+ * A record with no baseline at all is NOT withheld — that is an event this
1910
+ * workspace has never held, so there is no local edit to lose. */
1911
+ function unsentEditGuard(schema, baseline) {
1912
+ const map = pushableMap(schema.googleCalendar?.map ?? {});
1913
+ return (existing, eventId) => {
1914
+ const shadow = baseline[eventId];
1915
+ if (shadow === void 0) return false;
1916
+ return locallyChangedFields(existing, baselineRecord(eventId, shadow, map, schema.primaryKey, schema.fields), map).length > 0;
1917
+ };
1918
+ }
1919
+ /** What the pull may do to the record behind one event.
1920
+ *
1921
+ * The guard runs BEFORE the status is consulted, and that order is the whole
1922
+ * fix: "is there something local to lose here?" outranks "what did Google do
1923
+ * to it?". Asking about the status first is how a cancellation kept deleting
1924
+ * records that held an edit Google had never seen (#2688), long after the
1925
+ * same guard had been put in front of the overwrite (#2684). */
1926
+ function applyPlanFor(existing, event, hasUnsentEdit) {
1927
+ if (existing !== null && hasUnsentEdit(existing, event.id)) return "withhold";
1928
+ return event.status === "cancelled" ? "delete" : "write";
1929
+ }
1930
+ /** Lay Google's mapped values over whatever the record already holds. Split out
1931
+ * so `applyEvent` reads as read → decide → act rather than carrying the write
1932
+ * itself (CodeRabbit review #2689). */
1933
+ async function writeProjected(write, event, schema, existing) {
1934
+ const record = toCollectionRecord(event, schema.googleCalendar?.map ?? {}, schema.primaryKey, schema.fields);
1935
+ return classifyWrite(event.id, (await write(event.id, require_project.mergeIntoExisting(existing, record))).kind);
1936
+ }
1937
+ async function applyEvent(collection, event, workspaceRoot, hasUnsentEdit) {
1727
1938
  const { schema } = collection;
1728
1939
  try {
1729
1940
  const store = require_discovery.storeFor(collection, { workspaceRoot });
@@ -1731,13 +1942,11 @@ async function applyEvent(collection, event, workspaceRoot) {
1731
1942
  kind: "unwritable",
1732
1943
  message: `collection '${collection.slug}' is read-only`
1733
1944
  };
1734
- if (event.status === "cancelled") {
1735
- const deleted = await store.delete(event.id);
1736
- return classifyDelete(event.id, deleted.kind);
1737
- }
1738
- const record = toCollectionRecord(event, schema.googleCalendar?.map ?? {}, schema.primaryKey, schema.fields);
1739
- const written = await store.write(event.id, mergeIntoExisting(await store.read(event.id), record));
1740
- return classifyWrite(event.id, written.kind);
1945
+ const existing = await store.read(event.id);
1946
+ const plan = applyPlanFor(existing, event, hasUnsentEdit);
1947
+ if (plan === "withhold") return { kind: "withheld" };
1948
+ if (plan === "delete") return classifyDelete(event.id, (await store.delete(event.id)).kind);
1949
+ return await writeProjected(store.write, event, schema, existing);
1741
1950
  } catch (error) {
1742
1951
  return {
1743
1952
  kind: "error",
@@ -1788,15 +1997,15 @@ var NOTHING_UNPUSHED = /* @__PURE__ */ new Set();
1788
1997
  * protection — would overwrite the very edits this exists to protect; the read
1789
1998
  * that failed is no evidence that the pull's own writes would fail too, so they
1790
1999
  * would land (CodeRabbit review #2666). */
1791
- var PROTECTION_UNKNOWN = "could not work out which records to protect after a failed push";
1792
- /** What ONE collection's pull must leave alone: only what ITS OWN push failed to
2000
+ var PROTECTION_UNKNOWN = "could not work out which records to protect from the pull";
2001
+ /** What ONE collection's pull must leave alone: only what ITS OWN push did not
1793
2002
  * send.
1794
2003
  *
1795
- * Scoped per collection because a calendar can back several of them, and a
1796
- * conflict in one says nothing about the others. Sharing one set across the
1797
- * group starved a collection that never even declares `autoPush`: it cannot
1798
- * conflict, yet a neighbour's conflict froze its records — and the sync token
1799
- * still advanced, so Google never resent them (Codex review #2666). */
2004
+ * Scoped per collection because a calendar can back several of them, and one
2005
+ * collection's unsent edit says nothing about the others. Sharing one set across
2006
+ * the group starved a collection that never even declares `autoPush`: a
2007
+ * neighbour's conflict froze its records — and the sync token still advanced, so
2008
+ * Google never resent them (Codex review #2666). */
1800
2009
  var unpushedFor = (unpushed, slug) => {
1801
2010
  const protection = unpushed.get(slug);
1802
2011
  return protection === void 0 ? NOTHING_UNPUSHED : protection;
@@ -1821,16 +2030,19 @@ var allUnpushed = (unpushed) => new Set([...unpushed.values()].flatMap((ids) =>
1821
2030
  * feature exists to prevent: a calendar whose role degrades to reader refuses
1822
2031
  * the whole push, yet the pull still runs — reading needs no write access — and
1823
2032
  * overwrote every unsent local edit while advancing its baseline past it, so the
1824
- * next push could not even report the conflict (CodeRabbit review #2666).
2033
+ * next push could not even report the conflict (CodeRabbit review #2666). A
2034
+ * collection that never declares `autoPush` is in that same state on every
2035
+ * single run, which is how the loss reappeared without a failure in sight
2036
+ * (#2683).
1825
2037
  *
1826
2038
  * Protects the edited records rather than all of them, so an unchanged record
1827
2039
  * keeps syncing normally. `null` when even that could not be worked out — the
1828
2040
  * caller then refuses to pull the collection at all, because failing open here
1829
2041
  * destroys exactly what this protects. */
1830
- async function protectUnsentEdits(collection, workspaceRoot) {
2042
+ async function protectUnsentEdits(collection, workspaceRoot, deps) {
1831
2043
  try {
1832
- const edited = await unsentLocalEdits(collection, workspaceRoot);
1833
- if (edited.length > 0) log.warn("google", "protecting local edits a failed push could not send", {
2044
+ const edited = await deps.unsentEdits(collection, workspaceRoot);
2045
+ if (edited.length > 0) log.warn("google", "protecting local edits that have not reached Google", {
1834
2046
  slug: collection.slug,
1835
2047
  edited
1836
2048
  });
@@ -1843,8 +2055,18 @@ async function protectUnsentEdits(collection, workspaceRoot) {
1843
2055
  return null;
1844
2056
  }
1845
2057
  }
1846
- /** Push every `autoPush` collection in this group, and answer with the records
1847
- * whose local edit did not reach Google, keyed by collection.
2058
+ var livePullProtectionDeps = {
2059
+ pushNow: pushCollectionNow,
2060
+ unsentEdits: unsentLocalEdits
2061
+ };
2062
+ /** What ONE collection's pull must leave alone, pushing it first if it asked to
2063
+ * be pushed.
2064
+ *
2065
+ * A collection WITHOUT `autoPush` never pushes, so its local edits are unsent by
2066
+ * definition — the same state a failed push leaves behind, and it needs the same
2067
+ * protection. Pulling over them destroys the edit AND advances the baseline past
2068
+ * it, after which no conflict can be detected any more (#2683). "The push did not
2069
+ * run" is the condition that matters here; why it did not run is not.
1848
2070
  *
1849
2071
  * MUST run inside the calendar lock the caller already holds — hence
1850
2072
  * `pushCollectionNow` rather than `pushCalendarForCollection`, which would take
@@ -1852,27 +2074,31 @@ async function protectUnsentEdits(collection, workspaceRoot) {
1852
2074
  *
1853
2075
  * A failed push must not stop the pull: the pull is what keeps the collection
1854
2076
  * fresh, and a revoked write grant is no reason to freeze reading. */
1855
- async function pushAutoCollections(collections, workspaceRoot) {
1856
- const unpushed = /* @__PURE__ */ new Map();
1857
- for (const collection of collections.filter((entry) => entry.schema.googleCalendar?.autoPush)) try {
1858
- const outcome = await pushCollectionNow(collection, workspaceRoot);
2077
+ async function pullProtectionFor(collection, workspaceRoot, deps = livePullProtectionDeps) {
2078
+ if (!collection.schema.googleCalendar?.autoPush) return await protectUnsentEdits(collection, workspaceRoot, deps);
2079
+ try {
2080
+ const outcome = await deps.pushNow(collection, workspaceRoot);
1859
2081
  if (outcome.kind === "pushed") {
1860
2082
  reportAutoPush(collection.slug, outcome.result);
1861
- unpushed.set(collection.slug, new Set(outcome.result.unpushedIds));
1862
- continue;
2083
+ return new Set(outcome.result.unpushedIds);
1863
2084
  }
1864
2085
  log.warn("google", "auto push did not run", {
1865
2086
  slug: collection.slug,
1866
2087
  reason: outcome.kind
1867
2088
  });
1868
- unpushed.set(collection.slug, await protectUnsentEdits(collection, workspaceRoot));
1869
2089
  } catch (error) {
1870
2090
  log.warn("google", "auto push failed — pulling anyway", {
1871
2091
  slug: collection.slug,
1872
2092
  error: String(error)
1873
2093
  });
1874
- unpushed.set(collection.slug, await protectUnsentEdits(collection, workspaceRoot));
1875
2094
  }
2095
+ return await protectUnsentEdits(collection, workspaceRoot, deps);
2096
+ }
2097
+ /** Push the `autoPush` collections in this group, and answer with what every
2098
+ * collection's pull must leave alone, keyed by collection. */
2099
+ async function pushAndProtect(collections, workspaceRoot, deps = livePullProtectionDeps) {
2100
+ const unpushed = /* @__PURE__ */ new Map();
2101
+ for (const collection of collections) unpushed.set(collection.slug, await pullProtectionFor(collection, workspaceRoot, deps));
1876
2102
  return unpushed;
1877
2103
  }
1878
2104
  async function restartFullSync(accessToken, calendarId, workspaceRoot) {
@@ -1949,17 +2175,26 @@ async function releaseCalendarSyncClaim(calendarId, workspaceRoot) {
1949
2175
  });
1950
2176
  }
1951
2177
  }
2178
+ /** Every event whose baseline must NOT advance: what the push could not send,
2179
+ * plus what the apply refused to overwrite.
2180
+ *
2181
+ * The two must agree exactly. The apply's refusals only became visible after it
2182
+ * ran, so they join the union here rather than at push time — leaving them out
2183
+ * would advance the baseline past a record the pull deliberately left holding a
2184
+ * local edit, which is the silent overwrite this whole path exists to stop. */
2185
+ var heldBack = (unpushed, results) => /* @__PURE__ */ new Set([...allUnpushed(unpushed), ...results.flatMap((result) => result.withheld)]);
1952
2186
  async function syncCalendarGroupNow(calendarId, collections, workspaceRoot) {
1953
- const unpushed = await pushAutoCollections(collections, workspaceRoot);
2187
+ const unpushed = await pushAndProtect(collections, workspaceRoot);
2188
+ const baseline = await loadCalendarShadow(calendarId, workspaceRoot);
1954
2189
  const accessToken = await getGoogleAccessToken();
1955
2190
  const first = await syncCalendarEvents(accessToken, {
1956
2191
  calendarId,
1957
2192
  syncToken: await loadCalendarSyncToken(calendarId, workspaceRoot) ?? void 0
1958
2193
  });
1959
2194
  const result = first.fullResyncRequired ? await restartFullSync(accessToken, calendarId, workspaceRoot) : first;
1960
- const results = await applyWindowToGroup(collections, result.events, workspaceRoot, unpushed);
2195
+ const results = await applyWindowToGroup(collections, result.events, workspaceRoot, unpushed, baseline);
1961
2196
  if (windowFullyLanded(calendarId, results)) {
1962
- await saveCalendarShadow(calendarId, shadowUpdates(result.events, allUnpushed(unpushed)), workspaceRoot);
2197
+ await saveCalendarShadow(calendarId, shadowUpdates(result.events, heldBack(unpushed, results), baseline), workspaceRoot);
1963
2198
  if (result.nextSyncToken) await advanceToken(calendarId, result.nextSyncToken, collections, workspaceRoot);
1964
2199
  }
1965
2200
  return results;
@@ -1968,7 +2203,7 @@ async function syncCalendarGroupNow(calendarId, collections, workspaceRoot) {
1968
2203
  * one's own push protected. A collection whose protection could not be worked
1969
2204
  * out is not pulled at all — it reports a retryable error instead, which holds
1970
2205
  * the token and the baseline back for the whole group. */
1971
- async function applyWindowToGroup(collections, events, workspaceRoot, unpushed) {
2206
+ async function applyWindowToGroup(collections, events, workspaceRoot, unpushed, baseline) {
1972
2207
  const results = [];
1973
2208
  for (const collection of collections) {
1974
2209
  const protection = unpushedFor(unpushed, collection.slug);
@@ -1977,8 +2212,9 @@ async function applyWindowToGroup(collections, events, workspaceRoot, unpushed)
1977
2212
  written: 0,
1978
2213
  removed: 0,
1979
2214
  unwritable: [],
2215
+ withheld: [],
1980
2216
  errors: [PROTECTION_UNKNOWN]
1981
- } : await applyEventsToCollection(collection, events, workspaceRoot, protection));
2217
+ } : await applyEventsToCollection(collection, events, workspaceRoot, protection, baseline));
1982
2218
  }
1983
2219
  return results;
1984
2220
  }
@@ -1995,6 +2231,11 @@ function windowFullyLanded(calendarId, results) {
1995
2231
  slug: entry.slug,
1996
2232
  unwritable: entry.unwritable
1997
2233
  }));
2234
+ results.filter((entry) => entry.withheld.length > 0).forEach((entry) => log.warn("google", "leaving calendar records alone — they hold edits Google has not seen", {
2235
+ calendarId,
2236
+ slug: entry.slug,
2237
+ withheld: entry.withheld
2238
+ }));
1998
2239
  const failed = results.filter((entry) => entry.errors.length > 0);
1999
2240
  failed.forEach((entry) => log.warn("google", "holding back calendar sync token after failed writes", {
2000
2241
  calendarId,
@@ -2011,10 +2252,20 @@ function windowFullyLanded(calendarId, results) {
2011
2252
  * keeps the local one would make the next push read a plain one-sided edit —
2012
2253
  * no conflict to detect any more — and quietly overwrite Google. Held back, the
2013
2254
  * baseline stays older than both sides, so the conflict keeps being reported
2014
- * until someone resolves it (#2620). */
2015
- function shadowUpdates(events, unpushed = /* @__PURE__ */ new Set()) {
2016
- const carried = pullableEvents(events, unpushed);
2017
- return Object.fromEntries(carried.map((event) => [event.id, event.status === "cancelled" ? null : toShadowEvent(event)]));
2255
+ * until someone resolves it (#2620).
2256
+ *
2257
+ * `held` carries what those events must KEEP. Omitting them is enough on an
2258
+ * incremental run, where the file is merged rather than replaced — but a full
2259
+ * re-walk CLEARS the baseline first (`restartFullSync`), and there omission
2260
+ * drops the entry for good. The next push would then read a conflicted record
2261
+ * as a brand-new create, hit Google's duplicate-id 409 and refuse it, instead
2262
+ * of reporting the conflict it actually is. Re-stating the pre-run value makes
2263
+ * a held-back event behave the same either way (observed during Claude review;
2264
+ * no bot flagged it). */
2265
+ function shadowUpdates(events, unpushed = /* @__PURE__ */ new Set(), held = {}) {
2266
+ const advanced = pullableEvents(events, unpushed).map((event) => [event.id, event.status === "cancelled" ? null : toShadowEvent(event)]);
2267
+ const kept = [...unpushed].flatMap((eventId) => held[eventId] === void 0 ? [] : [[eventId, held[eventId]]]);
2268
+ return Object.fromEntries([...kept, ...advanced]);
2018
2269
  }
2019
2270
  /** Save the window's token unless every collection that consumed it was deleted
2020
2271
  * while the sync was in flight.
@@ -2047,13 +2298,19 @@ async function pathExists(absPath) {
2047
2298
  async function anySyncedCollectionSurvives(collections, exists = pathExists) {
2048
2299
  return (await Promise.all(collections.map((collection) => exists(collection.skillDir)))).some(Boolean);
2049
2300
  }
2050
- async function applyEventsToCollection(collection, events, workspaceRoot, unpushed) {
2051
- const outcomes = [];
2052
- for (const event of pullableEvents(events, unpushed)) outcomes.push(await applyEvent(collection, event, workspaceRoot));
2301
+ async function applyEventsToCollection(collection, events, workspaceRoot, unpushed, baseline) {
2302
+ const hasUnsentEdit = unsentEditGuard(collection.schema, baseline);
2303
+ const attempts = [];
2304
+ for (const event of pullableEvents(events, unpushed)) attempts.push({
2305
+ eventId: event.id,
2306
+ outcome: await applyEvent(collection, event, workspaceRoot, hasUnsentEdit)
2307
+ });
2308
+ const outcomes = attempts.map((attempt) => attempt.outcome);
2053
2309
  return {
2054
2310
  slug: collection.slug,
2055
2311
  written: outcomes.filter((outcome) => outcome.kind === "written").length,
2056
2312
  removed: outcomes.filter((outcome) => outcome.kind === "removed").length,
2313
+ withheld: attempts.flatMap((attempt) => attempt.outcome.kind === "withheld" ? [attempt.eventId] : []),
2057
2314
  unwritable: outcomes.flatMap((outcome) => outcome.kind === "unwritable" ? [outcome.message] : []),
2058
2315
  errors: outcomes.flatMap((outcome) => outcome.kind === "error" ? [outcome.message] : [])
2059
2316
  };
@@ -2156,6 +2413,7 @@ async function runCalendarGroups(groups, workspaceRoot, mayClaim = ALWAYS_CLAIM)
2156
2413
  written: 0,
2157
2414
  removed: 0,
2158
2415
  unwritable: [],
2416
+ withheld: [],
2159
2417
  errors: [String(error)]
2160
2418
  })));
2161
2419
  }
@@ -2459,6 +2717,7 @@ exports.PROTECTION_UNKNOWN = PROTECTION_UNKNOWN;
2459
2717
  exports.PUSHABLE_SOURCE_FIELDS = PUSHABLE_SOURCE_FIELDS;
2460
2718
  exports.allUnpushed = allUnpushed;
2461
2719
  exports.anySyncedCollectionSurvives = anySyncedCollectionSurvives;
2720
+ exports.applyPlanFor = applyPlanFor;
2462
2721
  exports.assertSafeMimeType = assertSafeMimeType;
2463
2722
  exports.authorizeGoogle = authorizeGoogle;
2464
2723
  exports.baselineRecord = baselineRecord;
@@ -2498,9 +2757,9 @@ exports.deleteGoogleTokens = deleteGoogleTokens;
2498
2757
  exports.deleteTask = deleteTask;
2499
2758
  exports.fieldText = fieldText;
2500
2759
  exports.findClientSecretPath = findClientSecretPath;
2501
- exports.getCalendar = getCalendar;
2502
2760
  exports.getCalendarColors = getCalendarColors;
2503
2761
  exports.getCalendarEvent = getCalendarEvent;
2762
+ exports.getCalendarMeta = getCalendarMeta;
2504
2763
  exports.getGoogleAccessToken = getGoogleAccessToken;
2505
2764
  exports.googleApiError = googleApiError;
2506
2765
  exports.googleAuthFlow = googleAuthFlow;
@@ -2509,6 +2768,7 @@ exports.googleConfigDir = googleConfigDir;
2509
2768
  exports.googleSecretsDir = googleSecretsDir;
2510
2769
  exports.googleTokenPath = googleTokenPath;
2511
2770
  exports.groupByCalendar = groupByCalendar;
2771
+ exports.heldBack = heldBack;
2512
2772
  exports.isCalendarSyncDue = isCalendarSyncDue;
2513
2773
  exports.isClientSettableEventId = isClientSettableEventId;
2514
2774
  exports.isDeniedAccessRole = isDeniedAccessRole;
@@ -2522,6 +2782,7 @@ exports.listCalendars = listCalendars;
2522
2782
  exports.listDriveFiles = listDriveFiles;
2523
2783
  exports.listTaskLists = listTaskLists;
2524
2784
  exports.listTasks = listTasks;
2785
+ exports.liveLockClock = liveLockClock;
2525
2786
  exports.loadCalendarLastSyncedAt = loadCalendarLastSyncedAt;
2526
2787
  exports.loadCalendarShadow = loadCalendarShadow;
2527
2788
  exports.loadCalendarSyncToken = loadCalendarSyncToken;
@@ -2532,27 +2793,32 @@ exports.locallyDeletedIds = locallyDeletedIds;
2532
2793
  exports.locallyEditedIds = locallyEditedIds;
2533
2794
  exports.mayAdoptExisting = mayAdoptExisting;
2534
2795
  exports.mergeGoogleTokens = mergeGoogleTokens;
2535
- exports.mergeIntoExisting = mergeIntoExisting;
2796
+ exports.mergeIntoExisting = require_project.mergeIntoExisting;
2536
2797
  exports.mergeShadow = mergeShadow;
2537
2798
  exports.orphanedCalendarId = orphanedCalendarId;
2538
2799
  exports.pickBoundary = pickBoundary;
2539
2800
  exports.planRecord = planRecord;
2801
+ exports.pullProtectionFor = pullProtectionFor;
2540
2802
  exports.pullableEvents = pullableEvents;
2803
+ exports.pushAndProtect = pushAndProtect;
2541
2804
  exports.pushCalendarForCollection = pushCalendarForCollection;
2542
2805
  exports.pushCollectionNow = pushCollectionNow;
2543
2806
  exports.pushableMap = pushableMap;
2544
2807
  exports.readDriveFile = readDriveFile;
2545
2808
  exports.releaseOrphanedCalendarToken = releaseOrphanedCalendarToken;
2809
+ exports.reportedAccessRole = reportedAccessRole;
2546
2810
  exports.resolveEventSpan = resolveEventSpan;
2547
2811
  exports.saveCalendarShadow = saveCalendarShadow;
2548
2812
  exports.saveCalendarSyncToken = saveCalendarSyncToken;
2549
2813
  exports.saveGoogleTokens = saveGoogleTokens;
2550
2814
  exports.shadowUpdates = shadowUpdates;
2815
+ exports.stateLockPath = stateLockPath;
2551
2816
  exports.syncCalendarEvents = syncCalendarEvents;
2552
2817
  exports.syncCalendarForCollection = syncCalendarForCollection;
2553
2818
  exports.syncCalendarGroup = syncCalendarGroup;
2554
2819
  exports.syncDueCalendarCollections = syncDueCalendarCollections;
2555
2820
  exports.syncNewCalendarCollections = syncNewCalendarCollections;
2821
+ exports.toCalendarMeta = toCalendarMeta;
2556
2822
  exports.toCalendarSummary = toCalendarSummary;
2557
2823
  exports.toCollectionDateTime = toCollectionDateTime;
2558
2824
  exports.toCollectionRecord = toCollectionRecord;
@@ -2565,12 +2831,14 @@ exports.toTaskSummary = toTaskSummary;
2565
2831
  exports.uncompleteTask = uncompleteTask;
2566
2832
  exports.unlinkGoogle = unlinkGoogle;
2567
2833
  exports.unpushedFor = unpushedFor;
2834
+ exports.unsentEditGuard = unsentEditGuard;
2568
2835
  exports.unsentLocalEdits = unsentLocalEdits;
2569
2836
  exports.unsyncedGroups = unsyncedGroups;
2570
2837
  exports.updateCalendarEvent = updateCalendarEvent;
2571
2838
  exports.updateTask = updateTask;
2572
2839
  exports.waitForAuthCode = waitForAuthCode;
2573
2840
  exports.withCalendarLock = withCalendarLock;
2841
+ exports.withCalendarStateLock = withCalendarStateLock;
2574
2842
  exports.withKeyedLock = withKeyedLock;
2575
2843
  exports.zoneSuffixOf = zoneSuffixOf;
2576
2844