@mulmoclaude/core 1.11.0 → 1.12.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.
- package/dist/google/calendarSyncDue.d.ts +10 -0
- package/dist/google/calendarSyncStore.d.ts +20 -1
- package/dist/google/collectionSync.d.ts +24 -5
- package/dist/google/index.cjs +203 -23
- package/dist/google/index.cjs.map +1 -1
- package/dist/google/index.d.ts +3 -2
- package/dist/google/index.js +199 -24
- package/dist/google/index.js.map +1 -1
- package/package.json +1 -1
package/dist/google/index.d.ts
CHANGED
|
@@ -8,7 +8,8 @@ export { authorizeGoogle, commitLinkedTokens, getGoogleAccessToken, unlinkGoogle
|
|
|
8
8
|
export { createGoogleAuthFlow, googleAuthFlow, type GoogleAuthFlow, type GoogleAuthFlowStatus } from './authFlow.js';
|
|
9
9
|
export { googleApiError, isGoogleApiError, GoogleApiError, DEFAULT_LIST_MAX_RESULTS, HTTP_FORBIDDEN, MAX_LIST_RESULTS } from './apiClient.js';
|
|
10
10
|
export { buildEventPatch, calendarApiError, collectCalendarPages, createCalendarEvent, deleteCalendarEvent, getCalendarColors, canonicalCalendarId, listCalendarEvents, listCalendars, syncCalendarEvents, toCalendarSummary, toEventSummary, updateCalendarEvent, getCalendar, getCalendarEvent, resolveEventSpan, CANCELLED_EVENT_STATUS, HTTP_CONFLICT, HTTP_PRECONDITION_FAILED, type CalendarColorEntry, type CalendarColors, type CalendarEventInput, type CalendarEventSpan, type CalendarEventSummary, type CalendarEventTime, type CalendarListPage, type CalendarSummary, type CalendarSyncResult, type DeleteCalendarEventInput, type FetchedCalendarEvent, type ListEventsInput, type SyncEventsInput, type UpdateCalendarEventInput, } from './calendar.js';
|
|
11
|
-
export { calendarSyncStatePath, clearCalendarSyncToken, loadCalendarSyncToken, saveCalendarSyncToken } from './calendarSyncStore.js';
|
|
11
|
+
export { calendarSyncStatePath, claimCalendarSyncIfDue, clearCalendarLastSyncedAt, clearCalendarSyncToken, loadCalendarLastSyncedAt, loadCalendarSyncToken, saveCalendarSyncToken, } from './calendarSyncStore.js';
|
|
12
|
+
export { calendarSyncDueWindowMs, isCalendarSyncDue } from './calendarSyncDue.js';
|
|
12
13
|
export { calendarPushStatePath, clearCalendarShadow, loadCalendarShadow, mergeShadow, saveCalendarShadow, toShadowEvent, type ShadowEvent, } from './calendarPushState.js';
|
|
13
14
|
export { isDeniedAccessRole, isUnpushed, locallyEditedIds, pushCalendarForCollection, pushCollectionNow, unsentLocalEdits, type PushOutcomeKind, type CalendarCollectionPushResult, type CalendarPushDeps, type CalendarPushOutcome, type CalendarWriteTarget, } from './collectionPush.js';
|
|
14
15
|
export { toGoogleEventTime, zoneSuffixOf } from './pushDateTime.js';
|
|
@@ -16,6 +17,6 @@ export { baselineRecord, bySourceField, conflictingFields, fieldText, isClientSe
|
|
|
16
17
|
export { toCollectionDateTime } from './collectionDateTime.js';
|
|
17
18
|
export { withCalendarLock, withKeyedLock } from './calendarLock.js';
|
|
18
19
|
export { mergeIntoExisting, toCollectionRecord, type GoogleCalendarSourceField } from './collectionProjection.js';
|
|
19
|
-
export { googleCalendarSyncTaskDef, classifyDelete, classifyWrite, anySyncedCollectionSurvives, groupByCalendar, allUnpushed, orphanedCalendarId, pullableEvents, releaseOrphanedCalendarToken, unpushedFor, PROTECTION_UNKNOWN, type UnpushedBySlug, syncCalendarForCollection, syncCalendarGroup, syncDueCalendarCollections, syncNewCalendarCollections, shadowUpdates, unsyncedGroups, GOOGLE_CALENDAR_SYNC_TASK_ID, type CalendarCollectionSyncResult, type CalendarDeclaring, type ManualCalendarSyncDeps, type ManualCalendarSyncOutcome, } from './collectionSync.js';
|
|
20
|
+
export { googleCalendarSyncTaskDef, classifyDelete, classifyWrite, anySyncedCollectionSurvives, groupByCalendar, allUnpushed, orphanedCalendarId, pullableEvents, releaseOrphanedCalendarToken, unpushedFor, PROTECTION_UNKNOWN, type ClaimGuard, type UnpushedBySlug, syncCalendarForCollection, syncCalendarGroup, syncDueCalendarCollections, syncNewCalendarCollections, shadowUpdates, unsyncedGroups, GOOGLE_CALENDAR_SYNC_TASK_ID, type CalendarCollectionSyncResult, type CalendarDeclaring, type ManualCalendarSyncDeps, type ManualCalendarSyncOutcome, } from './collectionSync.js';
|
|
20
21
|
export { buildTaskPatch, canonicalTaskListId, completeTask, createTask, deleteTask, listTaskLists, listTasks, toTaskListSummary, toTaskSummary, uncompleteTask, updateTask, type CompleteTaskInput, type CreateTaskInput, type ListTasksInput, type TaskListSummary, type TaskSummary, type UpdateTaskInput, } from './tasks.js';
|
|
21
22
|
export { assertSafeMimeType, buildMultipartBody, createDriveFile, deleteDriveFile, isTextMimeType, listDriveFiles, pickBoundary, readDriveFile, toDriveFileSummary, type CreateDriveFileInput, type DriveFileSummary, type ListDriveFilesInput, type ReadDriveFileInput, } from './driveFile.js';
|
package/dist/google/index.js
CHANGED
|
@@ -841,34 +841,128 @@ function calendarSyncStatePath(workspaceRoot) {
|
|
|
841
841
|
return path.join(workspaceRoot ?? getWorkspaceRoot(), "data", "calendar", ".sync-state.json");
|
|
842
842
|
}
|
|
843
843
|
var calendarKey = canonicalCalendarId;
|
|
844
|
+
/** Read one map out of whatever is on disk. Tolerant on purpose: a file written
|
|
845
|
+
* before `lastSyncedAt` existed simply lacks the key, and a hand-edited one may
|
|
846
|
+
* hold anything. A dropped entry costs a full re-walk or a duplicate run, never
|
|
847
|
+
* data — throwing here would stop the sync entirely. */
|
|
848
|
+
function stringRecord(value) {
|
|
849
|
+
if (typeof value !== "object" || value === null) return {};
|
|
850
|
+
return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
|
|
851
|
+
}
|
|
844
852
|
async function readState$1(workspaceRoot) {
|
|
845
853
|
const stored = await readJsonOrNull(calendarSyncStatePath(workspaceRoot));
|
|
846
|
-
return
|
|
854
|
+
return {
|
|
855
|
+
tokens: stringRecord(stored?.tokens),
|
|
856
|
+
lastSyncedAt: stringRecord(stored?.lastSyncedAt)
|
|
857
|
+
};
|
|
847
858
|
}
|
|
848
859
|
var writeQueue$1 = { tail: Promise.resolve() };
|
|
849
|
-
|
|
860
|
+
/** Decide from the stored state and write the result, both inside the queue, so
|
|
861
|
+
* nothing in this process can read between the two. A `null` state skips the
|
|
862
|
+
* write; `result` is what the caller learns about the decision. */
|
|
863
|
+
async function updateStateWith(decide, workspaceRoot) {
|
|
850
864
|
const run = writeQueue$1.tail.then(async () => {
|
|
851
|
-
const state = await readState$1(workspaceRoot);
|
|
852
|
-
await writeJsonAtomicWithMode(calendarSyncStatePath(workspaceRoot),
|
|
865
|
+
const { state, result } = decide(await readState$1(workspaceRoot));
|
|
866
|
+
if (state) await writeJsonAtomicWithMode(calendarSyncStatePath(workspaceRoot), state, SYNC_STATE_MODE);
|
|
867
|
+
return result;
|
|
853
868
|
});
|
|
854
869
|
writeQueue$1.tail = run.catch(() => void 0);
|
|
855
870
|
return await run;
|
|
856
871
|
}
|
|
872
|
+
async function updateState$1(mutate, workspaceRoot) {
|
|
873
|
+
await updateStateWith((state) => ({
|
|
874
|
+
state: mutate(state),
|
|
875
|
+
result: void 0
|
|
876
|
+
}), workspaceRoot);
|
|
877
|
+
}
|
|
878
|
+
var withoutKey = (record, dropped) => Object.fromEntries(Object.entries(record).filter(([key]) => key !== dropped));
|
|
857
879
|
async function loadCalendarSyncToken(calendarId, workspaceRoot) {
|
|
858
880
|
return (await readState$1(workspaceRoot)).tokens[calendarKey(calendarId)] ?? null;
|
|
859
881
|
}
|
|
860
882
|
async function saveCalendarSyncToken(calendarId, syncToken, workspaceRoot) {
|
|
861
883
|
const stored = calendarKey(calendarId);
|
|
862
|
-
await updateState$1((
|
|
863
|
-
...
|
|
864
|
-
|
|
884
|
+
await updateState$1((state) => ({
|
|
885
|
+
...state,
|
|
886
|
+
tokens: {
|
|
887
|
+
...state.tokens,
|
|
888
|
+
[stored]: syncToken
|
|
889
|
+
}
|
|
865
890
|
}), workspaceRoot);
|
|
866
891
|
}
|
|
867
892
|
/** Drop one calendar's token — used when Google answers 410, so the next run
|
|
868
|
-
* starts a clean full sync.
|
|
893
|
+
* starts a clean full sync. Leaves the sync marker alone: "restart the walk"
|
|
894
|
+
* says nothing about "nobody is running". */
|
|
869
895
|
async function clearCalendarSyncToken(calendarId, workspaceRoot) {
|
|
870
896
|
const dropped = calendarKey(calendarId);
|
|
871
|
-
await updateState$1((
|
|
897
|
+
await updateState$1((state) => ({
|
|
898
|
+
...state,
|
|
899
|
+
tokens: withoutKey(state.tokens, dropped)
|
|
900
|
+
}), workspaceRoot);
|
|
901
|
+
}
|
|
902
|
+
async function loadCalendarLastSyncedAt(calendarId, workspaceRoot) {
|
|
903
|
+
return (await readState$1(workspaceRoot)).lastSyncedAt[calendarKey(calendarId)] ?? null;
|
|
904
|
+
}
|
|
905
|
+
/** Stamp when a sync of this calendar started, but only if `mayClaim` accepts
|
|
906
|
+
* what the marker currently says. Answers whether THIS call is the one that
|
|
907
|
+
* claimed it.
|
|
908
|
+
*
|
|
909
|
+
* The decision and the stamp share one pass through the queue on purpose.
|
|
910
|
+
* Deciding from a snapshot read earlier — as a caller filtering a whole map of
|
|
911
|
+
* calendars up front must — leaves both hosts believing the calendar is free,
|
|
912
|
+
* which is the race this exists to close (Codex review #2680). What remains is
|
|
913
|
+
* the read→write gap BETWEEN processes; closing that needs a real file lock
|
|
914
|
+
* (#2679), and this stays a soft dedup until then.
|
|
915
|
+
*
|
|
916
|
+
* The time is passed in rather than read here so the rule stays testable
|
|
917
|
+
* without freezing the clock. */
|
|
918
|
+
async function claimCalendarSyncIfDue(calendarId, startedAt, mayClaim, workspaceRoot) {
|
|
919
|
+
const stored = calendarKey(calendarId);
|
|
920
|
+
return await updateStateWith((state) => {
|
|
921
|
+
const claimed = mayClaim(state.lastSyncedAt[stored] ?? null);
|
|
922
|
+
return {
|
|
923
|
+
state: claimed ? {
|
|
924
|
+
...state,
|
|
925
|
+
lastSyncedAt: {
|
|
926
|
+
...state.lastSyncedAt,
|
|
927
|
+
[stored]: startedAt
|
|
928
|
+
}
|
|
929
|
+
} : null,
|
|
930
|
+
result: claimed
|
|
931
|
+
};
|
|
932
|
+
}, workspaceRoot);
|
|
933
|
+
}
|
|
934
|
+
/** Drop one calendar's marker, so the next tick — on either host — may sync it
|
|
935
|
+
* immediately instead of waiting the window out. */
|
|
936
|
+
async function clearCalendarLastSyncedAt(calendarId, workspaceRoot) {
|
|
937
|
+
const dropped = calendarKey(calendarId);
|
|
938
|
+
await updateState$1((state) => ({
|
|
939
|
+
...state,
|
|
940
|
+
lastSyncedAt: withoutKey(state.lastSyncedAt, dropped)
|
|
941
|
+
}), workspaceRoot);
|
|
942
|
+
}
|
|
943
|
+
//#endregion
|
|
944
|
+
//#region src/google/calendarSyncDue.ts
|
|
945
|
+
/** Slack between a tick and the marker it writes. Without it a lone host reads
|
|
946
|
+
* its OWN marker as "not due yet" — elapsed lands a hair under the interval
|
|
947
|
+
* every time — and syncs every other hour. */
|
|
948
|
+
var TICK_JITTER_TOLERANCE_MS = 300 * 1e3;
|
|
949
|
+
/** How long a fresh marker suppresses a sync: one interval, less the slack.
|
|
950
|
+
* The slack is halved rather than subtracted flat for a very short configured
|
|
951
|
+
* interval, where a fixed five minutes would swallow the window whole. */
|
|
952
|
+
function calendarSyncDueWindowMs(intervalMs) {
|
|
953
|
+
return intervalMs - Math.min(TICK_JITTER_TOLERANCE_MS, intervalMs / 2);
|
|
954
|
+
}
|
|
955
|
+
/** True iff no recent enough run stands in for this one.
|
|
956
|
+
*
|
|
957
|
+
* A marker that cannot be read as a time is treated as due: a stuck calendar is
|
|
958
|
+
* worse than a duplicate run, which the sync is built to tolerate anyway
|
|
959
|
+
* (writes are upserts by event id). */
|
|
960
|
+
function isCalendarSyncDue(lastSyncedAt, windowMs, now = Date.now()) {
|
|
961
|
+
if (!lastSyncedAt) return true;
|
|
962
|
+
const elapsed = now - Date.parse(lastSyncedAt);
|
|
963
|
+
if (!Number.isFinite(elapsed)) return true;
|
|
964
|
+
if (elapsed < -windowMs) return true;
|
|
965
|
+
return elapsed >= windowMs;
|
|
872
966
|
}
|
|
873
967
|
//#endregion
|
|
874
968
|
//#region src/google/calendarPushState.ts
|
|
@@ -1782,6 +1876,7 @@ async function restartFullSync(accessToken, calendarId, workspaceRoot) {
|
|
|
1782
1876
|
await clearCalendarShadow(calendarId, workspaceRoot);
|
|
1783
1877
|
return await syncCalendarEvents(accessToken, { calendarId });
|
|
1784
1878
|
}
|
|
1879
|
+
var ALWAYS_CLAIM = () => true;
|
|
1785
1880
|
/** Sync ONE calendar and fan its events out to every collection bound to it.
|
|
1786
1881
|
*
|
|
1787
1882
|
* The fan-out is not an optimisation, it is correctness: the sync token is
|
|
@@ -1796,9 +1891,59 @@ async function restartFullSync(accessToken, calendarId, workspaceRoot) {
|
|
|
1796
1891
|
* load the SAME stored token and walk the same window. That is idempotent —
|
|
1797
1892
|
* writes are upserts by event id — but it is a wasted full walk. Queued, the
|
|
1798
1893
|
* second pass resumes from the token the first just stored and fetches only
|
|
1799
|
-
* what is genuinely new.
|
|
1800
|
-
|
|
1801
|
-
|
|
1894
|
+
* what is genuinely new. That queue is module state, so it orders the doors
|
|
1895
|
+
* into THIS process only; `claimThenSync` is what other hosts can see. */
|
|
1896
|
+
async function syncCalendarGroup(calendarId, collections, workspaceRoot, mayClaim = ALWAYS_CLAIM) {
|
|
1897
|
+
return await withCalendarLock(calendarId, () => claimThenSync(calendarId, collections, workspaceRoot, mayClaim));
|
|
1898
|
+
}
|
|
1899
|
+
/** Take the workspace-shared marker BEFORE the calendar is touched, sync only if
|
|
1900
|
+
* this run got it, and drop it again if the run could not happen at all.
|
|
1901
|
+
*
|
|
1902
|
+
* Claiming first is the whole point: hosts tick in the same minute (interval
|
|
1903
|
+
* schedules align to wall-clock boundaries), so a marker written on completion
|
|
1904
|
+
* would leave the entire run — minutes, for a first full walk — open for a
|
|
1905
|
+
* second host to start alongside this one and lose the push baseline it writes.
|
|
1906
|
+
*
|
|
1907
|
+
* Released only when the sync could not run at all. A run that finished with
|
|
1908
|
+
* retryable per-collection errors KEEPS the marker: it already held the token
|
|
1909
|
+
* and the baseline back, so it replays on the next tick either way, and
|
|
1910
|
+
* releasing would hand a permanently-failing calendar back to both hosts at
|
|
1911
|
+
* once — the very overlap this guards (observed during Claude review). */
|
|
1912
|
+
async function claimThenSync(calendarId, collections, workspaceRoot, mayClaim) {
|
|
1913
|
+
if (!await claimCalendarSync(calendarId, workspaceRoot, mayClaim)) {
|
|
1914
|
+
log.info("google", "skipping a calendar this workspace synced recently — another host may be on it", { calendarId });
|
|
1915
|
+
return [];
|
|
1916
|
+
}
|
|
1917
|
+
try {
|
|
1918
|
+
return await syncCalendarGroupNow(calendarId, collections, workspaceRoot);
|
|
1919
|
+
} catch (error) {
|
|
1920
|
+
await releaseCalendarSyncClaim(calendarId, workspaceRoot);
|
|
1921
|
+
throw error;
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
/** Never throws, and fails OPEN: the marker guards against duplicate work, it is
|
|
1925
|
+
* not a precondition for syncing correctly. A workspace that cannot store it
|
|
1926
|
+
* syncs the way it did before #2678. */
|
|
1927
|
+
async function claimCalendarSync(calendarId, workspaceRoot, mayClaim) {
|
|
1928
|
+
try {
|
|
1929
|
+
return await claimCalendarSyncIfDue(calendarId, (/* @__PURE__ */ new Date()).toISOString(), mayClaim, workspaceRoot);
|
|
1930
|
+
} catch (error) {
|
|
1931
|
+
log.warn("google", "could not stamp the calendar sync marker — another host may sync this calendar in parallel", {
|
|
1932
|
+
calendarId,
|
|
1933
|
+
error: String(error)
|
|
1934
|
+
});
|
|
1935
|
+
return true;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
async function releaseCalendarSyncClaim(calendarId, workspaceRoot) {
|
|
1939
|
+
try {
|
|
1940
|
+
await clearCalendarLastSyncedAt(calendarId, workspaceRoot);
|
|
1941
|
+
} catch (error) {
|
|
1942
|
+
log.warn("google", "could not release the calendar sync marker after a failed run", {
|
|
1943
|
+
calendarId,
|
|
1944
|
+
error: String(error)
|
|
1945
|
+
});
|
|
1946
|
+
}
|
|
1802
1947
|
}
|
|
1803
1948
|
async function syncCalendarGroupNow(calendarId, collections, workspaceRoot) {
|
|
1804
1949
|
const unpushed = await pushAutoCollections(collections, workspaceRoot);
|
|
@@ -1959,6 +2104,7 @@ async function releaseOrphanedCalendarToken(deleted, workspaceRoot) {
|
|
|
1959
2104
|
if (orphaned === null) return null;
|
|
1960
2105
|
await clearCalendarSyncToken(orphaned, workspaceRoot);
|
|
1961
2106
|
await clearCalendarShadow(orphaned, workspaceRoot);
|
|
2107
|
+
await clearCalendarLastSyncedAt(orphaned, workspaceRoot);
|
|
1962
2108
|
log.info("google", "cleared the sync token of a calendar no collection reads any more", { calendarId: orphaned });
|
|
1963
2109
|
return orphaned;
|
|
1964
2110
|
} catch (error) {
|
|
@@ -1975,19 +2121,27 @@ async function declaringGroups(workspaceRoot) {
|
|
|
1975
2121
|
* Authoring the collection before linking the account is an expected state,
|
|
1976
2122
|
* not a failure. Checking once here keeps it a quiet skip instead of an
|
|
1977
2123
|
* access-token throw per calendar, every hour, until the user links (#2188).
|
|
1978
|
-
* A user-triggered sync answers differently — it says so out loud.
|
|
2124
|
+
* A user-triggered sync answers differently — it says so out loud.
|
|
2125
|
+
*
|
|
2126
|
+
* Deliberately not phrased as a per-host state: the link lives in
|
|
2127
|
+
* `~/.config/mulmo`, which carries no app name, so every host on the machine
|
|
2128
|
+
* shares one answer. Reading it as "linked on this host only" is what made the
|
|
2129
|
+
* calendar look implicitly exclusive when it never was (#2678). */
|
|
1979
2130
|
async function backgroundSyncAllowed(groups) {
|
|
1980
2131
|
if (groups.size === 0) return false;
|
|
1981
2132
|
if (await isGoogleLinked()) return true;
|
|
1982
|
-
log.info("google", "skipping calendar sync — no Google account linked
|
|
2133
|
+
log.info("google", "skipping calendar sync — no Google account linked", { calendars: groups.size });
|
|
1983
2134
|
return false;
|
|
1984
2135
|
}
|
|
1985
2136
|
/** Run each group, isolating failures per calendar — one unreachable calendar
|
|
1986
|
-
* (or a revoked grant) must not stop the others.
|
|
1987
|
-
|
|
2137
|
+
* (or a revoked grant) must not stop the others. `mayClaim` is evaluated per
|
|
2138
|
+
* calendar as it comes up, never once for the whole map: an earlier calendar's
|
|
2139
|
+
* full walk takes minutes, and a decision made before it started says nothing
|
|
2140
|
+
* about who holds this one now. */
|
|
2141
|
+
async function runCalendarGroups(groups, workspaceRoot, mayClaim = ALWAYS_CLAIM) {
|
|
1988
2142
|
const results = [];
|
|
1989
2143
|
for (const [calendarId, collections] of groups) try {
|
|
1990
|
-
results.push(...await syncCalendarGroup(calendarId, collections, workspaceRoot));
|
|
2144
|
+
results.push(...await syncCalendarGroup(calendarId, collections, workspaceRoot, mayClaim));
|
|
1991
2145
|
} catch (error) {
|
|
1992
2146
|
log.warn("google", "calendar sync failed", {
|
|
1993
2147
|
calendarId,
|
|
@@ -2003,10 +2157,19 @@ async function runCalendarGroups(groups, workspaceRoot) {
|
|
|
2003
2157
|
}
|
|
2004
2158
|
return results;
|
|
2005
2159
|
}
|
|
2006
|
-
/** Sync every collection
|
|
2007
|
-
|
|
2160
|
+
/** Sync every collection whose calendar is due — no host in this workspace has
|
|
2161
|
+
* started one within `intervalMs` (#2678). Without that gate this walked every
|
|
2162
|
+
* declaring group on every tick, so a second host registering the same task
|
|
2163
|
+
* simply doubled the runs, concurrently.
|
|
2164
|
+
*
|
|
2165
|
+
* Dueness is not decided here, only described: the guard is handed down and
|
|
2166
|
+
* evaluated where the marker is written, so the answer cannot go stale between
|
|
2167
|
+
* deciding and claiming (Codex review #2680). */
|
|
2168
|
+
async function syncDueCalendarCollections(workspaceRoot, intervalMs = DEFAULT_SYNC_INTERVAL_MS) {
|
|
2008
2169
|
const groups = await declaringGroups(workspaceRoot);
|
|
2009
|
-
|
|
2170
|
+
if (!await backgroundSyncAllowed(groups)) return [];
|
|
2171
|
+
const windowMs = calendarSyncDueWindowMs(intervalMs);
|
|
2172
|
+
return await runCalendarGroups(groups, workspaceRoot, (lastSyncedAt) => isCalendarSyncDue(lastSyncedAt, windowMs));
|
|
2010
2173
|
}
|
|
2011
2174
|
/** The groups whose calendar has never synced. A missing token IS the "created
|
|
2012
2175
|
* since the last sync" signal — nothing else distinguishes a new collection
|
|
@@ -2052,10 +2215,21 @@ async function syncCalendarForCollection(slug, workspaceRoot, deps = liveManualS
|
|
|
2052
2215
|
results: await deps.runGroups(new Map(owning), workspaceRoot)
|
|
2053
2216
|
};
|
|
2054
2217
|
}
|
|
2218
|
+
/** The interval this task is scheduled on right now. A non-interval schedule
|
|
2219
|
+
* cannot be reached — the factory below builds an interval one — but the type
|
|
2220
|
+
* allows it, so it falls back rather than asserting. */
|
|
2221
|
+
function scheduledIntervalMs(task) {
|
|
2222
|
+
return task.schedule.type === SCHEDULE_TYPES.interval ? task.schedule.intervalMs : DEFAULT_SYNC_INTERVAL_MS;
|
|
2223
|
+
}
|
|
2055
2224
|
/** Scheduler registration, shaped like `feedRefreshTaskDef` so hosts wire it
|
|
2056
|
-
* with a single line.
|
|
2225
|
+
* with a single line.
|
|
2226
|
+
*
|
|
2227
|
+
* `run` reads the interval back off the definition instead of closing over the
|
|
2228
|
+
* option: a host rewrites `schedule` from its own overrides file AFTER this
|
|
2229
|
+
* returns, and the due window has to follow it. Frozen at the default, a
|
|
2230
|
+
* shortened interval would tick often and skip nearly every tick. */
|
|
2057
2231
|
function googleCalendarSyncTaskDef(opts) {
|
|
2058
|
-
|
|
2232
|
+
const def = {
|
|
2059
2233
|
id: GOOGLE_CALENDAR_SYNC_TASK_ID,
|
|
2060
2234
|
name: "Google Calendar sync",
|
|
2061
2235
|
description: "Pulls changed Google Calendar events into any collection declaring `googleCalendar`, without invoking the LLM.",
|
|
@@ -2064,8 +2238,9 @@ function googleCalendarSyncTaskDef(opts) {
|
|
|
2064
2238
|
intervalMs: opts?.intervalMs ?? DEFAULT_SYNC_INTERVAL_MS
|
|
2065
2239
|
},
|
|
2066
2240
|
missedRunPolicy: MISSED_RUN_POLICIES.runOnce,
|
|
2067
|
-
run: () => syncDueCalendarCollections(opts?.workspaceRoot ?? getWorkspaceRoot()).then(() => {})
|
|
2241
|
+
run: () => syncDueCalendarCollections(opts?.workspaceRoot ?? getWorkspaceRoot(), scheduledIntervalMs(def)).then(() => {})
|
|
2068
2242
|
};
|
|
2243
|
+
return def;
|
|
2069
2244
|
}
|
|
2070
2245
|
//#endregion
|
|
2071
2246
|
//#region src/google/tasks.ts
|
|
@@ -2262,6 +2437,6 @@ async function deleteDriveFile(accessToken, input) {
|
|
|
2262
2437
|
await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}/${encodeURIComponent(input.fileId)}`, { method: "DELETE" });
|
|
2263
2438
|
}
|
|
2264
2439
|
//#endregion
|
|
2265
|
-
export { CANCELLED_EVENT_STATUS, DEFAULT_LIST_MAX_RESULTS, GOOGLE_AUTH_CANCELLED, GOOGLE_CALENDARLIST_SCOPE, GOOGLE_CALENDAR_SCOPE, GOOGLE_CALENDAR_SYNC_TASK_ID, GOOGLE_DRIVE_FILE_SCOPE, GOOGLE_SCOPES, GOOGLE_TASKS_SCOPE, GoogleApiError, HTTP_CONFLICT, HTTP_FORBIDDEN, HTTP_PRECONDITION_FAILED, MAX_LIST_RESULTS, PROTECTION_UNKNOWN, PUSHABLE_SOURCE_FIELDS, allUnpushed, anySyncedCollectionSurvives, assertSafeMimeType, authorizeGoogle, baselineRecord, brokerBaseUrl, brokerExchange, brokerRefresh, brokerStart, buildEventPatch, buildMultipartBody, buildTaskPatch, bySourceField, calendarApiError, calendarPushStatePath, calendarSyncStatePath, canonicalCalendarId, canonicalTaskListId, classifyDelete, classifyWrite, clearCalendarShadow, clearCalendarSyncToken, clientSecretPresence, collectCalendarPages, commitLinkedTokens, completeTask, configureGoogleHost, conflictingFields, createCalendarEvent, createDriveFile, createGoogleAuthFlow, createTask, deleteCalendarEvent, deleteDriveFile, deleteGoogleTokens, deleteTask, fieldText, findClientSecretPath, getCalendar, getCalendarColors, getCalendarEvent, getGoogleAccessToken, googleApiError, googleAuthFlow, googleCalendarSyncTaskDef, googleConfigDir, googleSecretsDir, googleTokenPath, groupByCalendar, isClientSettableEventId, isDeniedAccessRole, isGoogleApiError, isIsoDateTimeWithOffset, isTextMimeType, isUnpushed, legacyGoogleTokenPath, listCalendarEvents, listCalendars, listDriveFiles, listTaskLists, listTasks, loadCalendarShadow, loadCalendarSyncToken, loadClientSecret, loadGoogleTokens, locallyChangedFields, locallyDeletedIds, locallyEditedIds, mayAdoptExisting, mergeGoogleTokens, mergeIntoExisting, mergeShadow, orphanedCalendarId, pickBoundary, planRecord, pullableEvents, pushCalendarForCollection, pushCollectionNow, pushableMap, readDriveFile, releaseOrphanedCalendarToken, resolveEventSpan, saveCalendarShadow, saveCalendarSyncToken, saveGoogleTokens, shadowUpdates, syncCalendarEvents, syncCalendarForCollection, syncCalendarGroup, syncDueCalendarCollections, syncNewCalendarCollections, toCalendarSummary, toCollectionDateTime, toCollectionRecord, toDriveFileSummary, toEventSummary, toGoogleEventTime, toShadowEvent, toTaskListSummary, toTaskSummary, uncompleteTask, unlinkGoogle, unpushedFor, unsentLocalEdits, unsyncedGroups, updateCalendarEvent, updateTask, waitForAuthCode, withCalendarLock, withKeyedLock, zoneSuffixOf };
|
|
2440
|
+
export { CANCELLED_EVENT_STATUS, DEFAULT_LIST_MAX_RESULTS, GOOGLE_AUTH_CANCELLED, GOOGLE_CALENDARLIST_SCOPE, GOOGLE_CALENDAR_SCOPE, GOOGLE_CALENDAR_SYNC_TASK_ID, GOOGLE_DRIVE_FILE_SCOPE, GOOGLE_SCOPES, GOOGLE_TASKS_SCOPE, GoogleApiError, HTTP_CONFLICT, HTTP_FORBIDDEN, HTTP_PRECONDITION_FAILED, MAX_LIST_RESULTS, PROTECTION_UNKNOWN, PUSHABLE_SOURCE_FIELDS, allUnpushed, anySyncedCollectionSurvives, assertSafeMimeType, authorizeGoogle, baselineRecord, brokerBaseUrl, brokerExchange, brokerRefresh, brokerStart, buildEventPatch, buildMultipartBody, buildTaskPatch, bySourceField, calendarApiError, calendarPushStatePath, calendarSyncDueWindowMs, calendarSyncStatePath, canonicalCalendarId, canonicalTaskListId, claimCalendarSyncIfDue, classifyDelete, classifyWrite, clearCalendarLastSyncedAt, clearCalendarShadow, clearCalendarSyncToken, clientSecretPresence, collectCalendarPages, commitLinkedTokens, completeTask, configureGoogleHost, conflictingFields, createCalendarEvent, createDriveFile, createGoogleAuthFlow, createTask, deleteCalendarEvent, deleteDriveFile, deleteGoogleTokens, deleteTask, fieldText, findClientSecretPath, getCalendar, getCalendarColors, getCalendarEvent, getGoogleAccessToken, googleApiError, googleAuthFlow, googleCalendarSyncTaskDef, googleConfigDir, googleSecretsDir, googleTokenPath, groupByCalendar, isCalendarSyncDue, isClientSettableEventId, isDeniedAccessRole, isGoogleApiError, isIsoDateTimeWithOffset, isTextMimeType, isUnpushed, legacyGoogleTokenPath, listCalendarEvents, listCalendars, listDriveFiles, listTaskLists, listTasks, loadCalendarLastSyncedAt, loadCalendarShadow, loadCalendarSyncToken, loadClientSecret, loadGoogleTokens, locallyChangedFields, locallyDeletedIds, locallyEditedIds, mayAdoptExisting, mergeGoogleTokens, mergeIntoExisting, mergeShadow, orphanedCalendarId, pickBoundary, planRecord, pullableEvents, pushCalendarForCollection, pushCollectionNow, pushableMap, readDriveFile, releaseOrphanedCalendarToken, resolveEventSpan, saveCalendarShadow, saveCalendarSyncToken, saveGoogleTokens, shadowUpdates, syncCalendarEvents, syncCalendarForCollection, syncCalendarGroup, syncDueCalendarCollections, syncNewCalendarCollections, toCalendarSummary, toCollectionDateTime, toCollectionRecord, toDriveFileSummary, toEventSummary, toGoogleEventTime, toShadowEvent, toTaskListSummary, toTaskSummary, uncompleteTask, unlinkGoogle, unpushedFor, unsentLocalEdits, unsyncedGroups, updateCalendarEvent, updateTask, waitForAuthCode, withCalendarLock, withKeyedLock, zoneSuffixOf };
|
|
2266
2441
|
|
|
2267
2442
|
//# sourceMappingURL=index.js.map
|