@mulmoclaude/core 0.24.0 → 0.25.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/assets/helps/google-calendar-collection.md +79 -0
- package/assets/helps/index.md +1 -0
- package/dist/collection/core/schemaZ.d.ts +29 -0
- package/dist/collection/index.cjs +15 -14
- package/dist/collection/index.cjs.map +1 -1
- package/dist/collection/index.js +2 -1
- package/dist/collection/index.js.map +1 -1
- package/dist/collection/paths.cjs +5 -55
- package/dist/collection/paths.js +1 -51
- package/dist/collection/registry/server/index.cjs +20 -19
- package/dist/collection/registry/server/index.cjs.map +1 -1
- package/dist/collection/registry/server/index.js +3 -2
- package/dist/collection/registry/server/index.js.map +1 -1
- package/dist/collection/server/index.cjs +41 -40
- package/dist/collection/server/index.js +4 -3
- package/dist/collection-watchers/index.cjs +12 -11
- package/dist/collection-watchers/index.cjs.map +1 -1
- package/dist/collection-watchers/index.js +3 -2
- package/dist/collection-watchers/index.js.map +1 -1
- package/dist/discovery-3K_Wtkvn.js +1636 -0
- package/dist/discovery-3K_Wtkvn.js.map +1 -0
- package/dist/discovery-BZ2ms1tE.cjs +1865 -0
- package/dist/discovery-BZ2ms1tE.cjs.map +1 -0
- package/dist/feeds/index.cjs +4 -4
- package/dist/feeds/index.js +2 -2
- package/dist/feeds/server/index.cjs +16 -15
- package/dist/feeds/server/index.cjs.map +1 -1
- package/dist/feeds/server/index.js +4 -3
- package/dist/feeds/server/index.js.map +1 -1
- package/dist/google/apiClient.d.ts +8 -1
- package/dist/google/calendar.d.ts +35 -0
- package/dist/google/calendarSyncStore.d.ts +7 -0
- package/dist/google/collectionSync.d.ts +70 -0
- package/dist/google/index.cjs +296 -4
- package/dist/google/index.cjs.map +1 -1
- package/dist/google/index.d.ts +4 -2
- package/dist/google/index.js +281 -5
- package/dist/google/index.js.map +1 -1
- package/dist/ids-BR4U1dJD.cjs +150 -0
- package/dist/ids-BR4U1dJD.cjs.map +1 -0
- package/dist/ids-D4e10971.js +85 -0
- package/dist/ids-D4e10971.js.map +1 -0
- package/dist/{ingestTypes-CFEgTItj.js → ingestTypes-BsGOjR2l.js} +2 -2
- package/dist/{ingestTypes-CFEgTItj.js.map → ingestTypes-BsGOjR2l.js.map} +1 -1
- package/dist/{ingestTypes-Stc7iQyA.cjs → ingestTypes-CygUXsjf.cjs} +3 -3
- package/dist/{ingestTypes-Stc7iQyA.cjs.map → ingestTypes-CygUXsjf.cjs.map} +1 -1
- package/dist/{promptSafety-BLUcImzd.js → promptSafety-Boz5qOs-.js} +2 -84
- package/dist/promptSafety-Boz5qOs-.js.map +1 -0
- package/dist/{promptSafety-DI_1Rvhk.cjs → promptSafety-Co4cYQ_Z.cjs} +1 -149
- package/dist/promptSafety-Co4cYQ_Z.cjs.map +1 -0
- package/dist/remote-host/server/index.cjs +6 -4
- package/dist/remote-host/server/index.cjs.map +1 -1
- package/dist/remote-host/server/index.js +6 -4
- package/dist/remote-host/server/index.js.map +1 -1
- package/dist/scheduler/index.cjs +3 -1
- package/dist/scheduler/index.cjs.map +1 -1
- package/dist/scheduler/index.js +3 -1
- package/dist/scheduler/index.js.map +1 -1
- package/dist/{server-CkysfLh4.cjs → server-DLRz-U7F.cjs} +76 -1884
- package/dist/server-DLRz-U7F.cjs.map +1 -0
- package/dist/{server-BpEo0FOd.js → server-DZH7OjOD.js} +5 -1609
- package/dist/server-DZH7OjOD.js.map +1 -0
- package/dist/skill-bridge/index.cjs +2 -2
- package/dist/skill-bridge/index.cjs.map +1 -1
- package/dist/skill-bridge/index.js +1 -1
- package/dist/templatePath-27vUZowm.cjs +75 -0
- package/dist/templatePath-27vUZowm.cjs.map +1 -0
- package/dist/templatePath-k_WNbL_Q.js +52 -0
- package/dist/templatePath-k_WNbL_Q.js.map +1 -0
- package/package.json +1 -1
- package/dist/collection/paths.cjs.map +0 -1
- package/dist/collection/paths.js.map +0 -1
- package/dist/promptSafety-BLUcImzd.js.map +0 -1
- package/dist/promptSafety-DI_1Rvhk.cjs.map +0 -1
- package/dist/server-BpEo0FOd.js.map +0 -1
- package/dist/server-CkysfLh4.cjs.map +0 -1
package/dist/google/index.cjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_rolldown_runtime = require("../rolldown-runtime-D6vf50IK.cjs");
|
|
3
|
+
const require_discovery = require("../discovery-BZ2ms1tE.cjs");
|
|
3
4
|
let node_path = require("node:path");
|
|
4
5
|
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
5
6
|
let node_fs = require("node:fs");
|
|
6
7
|
let node_fs_promises = require("node:fs/promises");
|
|
7
8
|
let node_crypto = require("node:crypto");
|
|
8
9
|
let node_os = require("node:os");
|
|
10
|
+
let _receptron_task_scheduler = require("@receptron/task-scheduler");
|
|
9
11
|
let node_http = require("node:http");
|
|
10
12
|
node_http = require_rolldown_runtime.__toESM(node_http, 1);
|
|
11
13
|
let google_auth_library = require("google-auth-library");
|
|
@@ -611,12 +613,21 @@ var ERROR_BODY_MAX_CHARS = 300;
|
|
|
611
613
|
var HTTP_FORBIDDEN = 403;
|
|
612
614
|
var DEFAULT_LIST_MAX_RESULTS = 10;
|
|
613
615
|
var MAX_LIST_RESULTS = 50;
|
|
616
|
+
/** Carries the HTTP status so callers can branch on it (e.g. an expired
|
|
617
|
+
* calendar syncToken answers 410) without parsing the message. */
|
|
618
|
+
var GoogleApiError = class extends Error {
|
|
619
|
+
status;
|
|
620
|
+
constructor(status, message) {
|
|
621
|
+
super(message);
|
|
622
|
+
this.name = "GoogleApiError";
|
|
623
|
+
this.status = status;
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
var isGoogleApiError = (value) => value instanceof GoogleApiError;
|
|
614
627
|
/** 403 usually means the API is not enabled for the user's Cloud project —
|
|
615
628
|
* name the API so the agent's recovery guidance can be specific. */
|
|
616
629
|
var googleApiError = (apiLabel, status, body) => {
|
|
617
|
-
|
|
618
|
-
const detail = body ? ` — ${truncate(body, ERROR_BODY_MAX_CHARS)}` : "";
|
|
619
|
-
return /* @__PURE__ */ new Error(`${apiLabel}: HTTP ${status}${hint}${detail}`);
|
|
630
|
+
return new GoogleApiError(status, `${apiLabel}: HTTP ${status}${status === HTTP_FORBIDDEN ? ` (is the ${apiLabel} enabled for the Cloud project?)` : ""}${body ? ` — ${truncate(body, ERROR_BODY_MAX_CHARS)}` : ""}`);
|
|
620
631
|
};
|
|
621
632
|
async function googleRequest(apiLabel, accessToken, url, init = {}) {
|
|
622
633
|
const { contentType = "application/json", expectText = false, ...rest } = init;
|
|
@@ -649,7 +660,16 @@ var CALENDAR_API_LABEL = "Google Calendar API";
|
|
|
649
660
|
var DEFAULT_CALENDAR_ID = "primary";
|
|
650
661
|
var CALENDAR_LIST_PAGE_SIZE = 250;
|
|
651
662
|
var MAX_CALENDAR_LIST_PAGES = 40;
|
|
652
|
-
var
|
|
663
|
+
var EVENT_SYNC_PAGE_SIZE = 2500;
|
|
664
|
+
var MAX_EVENT_SYNC_PAGES = 200;
|
|
665
|
+
var HTTP_GONE = 410;
|
|
666
|
+
/** Resolve a declared calendarId to the one the API and the sync-token store
|
|
667
|
+
* both address. `||` (not `??`) so an empty string also falls back instead of
|
|
668
|
+
* building a malformed `/calendars//events` URL. Single-sourced because an
|
|
669
|
+
* omitted id and an explicit "primary" MUST agree everywhere — grouping them
|
|
670
|
+
* apart while they share a sync token silently loses events (#2184). */
|
|
671
|
+
var canonicalCalendarId = (calendarId) => calendarId || DEFAULT_CALENDAR_ID;
|
|
672
|
+
var eventsUrl = (calendarId) => `${CALENDAR_BASE_URL}/calendars/${encodeURIComponent(canonicalCalendarId(calendarId))}/events`;
|
|
653
673
|
var eventTime = (value) => {
|
|
654
674
|
if (!isRecord(value)) return "";
|
|
655
675
|
if (typeof value.dateTime === "string") return value.dateTime;
|
|
@@ -716,6 +736,54 @@ async function listCalendarEvents(accessToken, input = {}) {
|
|
|
716
736
|
});
|
|
717
737
|
return itemsOf(await googleRequest(CALENDAR_API_LABEL, accessToken, `${eventsUrl(input.calendarId)}?${params.toString()}`)).map(toEventSummary);
|
|
718
738
|
}
|
|
739
|
+
var GONE = Symbol("calendar-sync-gone");
|
|
740
|
+
async function fetchSyncPage(accessToken, calendarId, params) {
|
|
741
|
+
try {
|
|
742
|
+
return await googleRequest(CALENDAR_API_LABEL, accessToken, `${eventsUrl(calendarId)}?${params.toString()}`);
|
|
743
|
+
} catch (err) {
|
|
744
|
+
if (isGoogleApiError(err) && err.status === HTTP_GONE) return GONE;
|
|
745
|
+
throw err;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
/** Incremental sync over the events of one calendar (#2095).
|
|
749
|
+
*
|
|
750
|
+
* Deliberately separate from `listCalendarEvents`: Google forbids combining
|
|
751
|
+
* `syncToken` with `timeMin` / `timeMax` / `updatedMin` / `orderBy` / `q`,
|
|
752
|
+
* and that function always sends `timeMin` + `orderBy`. So this one sends
|
|
753
|
+
* neither — a sync covers the WHOLE calendar and the caller sorts / windows
|
|
754
|
+
* client-side. `showDeleted` must stay true or deletions would be invisible.
|
|
755
|
+
*
|
|
756
|
+
* `nextSyncToken` is only present on the LAST page, so every page must be
|
|
757
|
+
* walked before the token is worth storing. */
|
|
758
|
+
async function syncCalendarEvents(accessToken, input = {}) {
|
|
759
|
+
const events = [];
|
|
760
|
+
let pageToken;
|
|
761
|
+
let nextSyncToken;
|
|
762
|
+
for (let page = 0; page < MAX_EVENT_SYNC_PAGES; page += 1) {
|
|
763
|
+
const params = new URLSearchParams({
|
|
764
|
+
singleEvents: "true",
|
|
765
|
+
showDeleted: "true",
|
|
766
|
+
maxResults: String(input.maxResults ?? EVENT_SYNC_PAGE_SIZE)
|
|
767
|
+
});
|
|
768
|
+
if (input.syncToken) params.set("syncToken", input.syncToken);
|
|
769
|
+
if (pageToken) params.set("pageToken", pageToken);
|
|
770
|
+
const payload = await fetchSyncPage(accessToken, input.calendarId, params);
|
|
771
|
+
if (payload === GONE) return {
|
|
772
|
+
events: [],
|
|
773
|
+
fullResyncRequired: true
|
|
774
|
+
};
|
|
775
|
+
const record = asRecord(payload);
|
|
776
|
+
events.push(...itemsOf(payload).map(toEventSummary));
|
|
777
|
+
nextSyncToken = stringField(record, "nextSyncToken") || void 0;
|
|
778
|
+
pageToken = stringField(record, "nextPageToken") || void 0;
|
|
779
|
+
if (!pageToken) break;
|
|
780
|
+
}
|
|
781
|
+
return {
|
|
782
|
+
events,
|
|
783
|
+
nextSyncToken,
|
|
784
|
+
fullResyncRequired: false
|
|
785
|
+
};
|
|
786
|
+
}
|
|
719
787
|
/** Pagination loop for CalendarList.list, extracted so it can be tested without
|
|
720
788
|
* network. Stops at the last page (no token) or the runaway page cap. */
|
|
721
789
|
async function collectCalendarPages(fetchPage, maxPages = MAX_CALENDAR_LIST_PAGES) {
|
|
@@ -753,6 +821,214 @@ async function getCalendarColors(accessToken) {
|
|
|
753
821
|
};
|
|
754
822
|
}
|
|
755
823
|
//#endregion
|
|
824
|
+
//#region src/google/calendarSyncStore.ts
|
|
825
|
+
var SYNC_STATE_MODE = 384;
|
|
826
|
+
/** `<workspace>/data/calendar/.sync-state.json` */
|
|
827
|
+
function calendarSyncStatePath(workspaceRoot) {
|
|
828
|
+
return node_path.default.join(workspaceRoot ?? require_discovery.getWorkspaceRoot(), "data", "calendar", ".sync-state.json");
|
|
829
|
+
}
|
|
830
|
+
var calendarKey = canonicalCalendarId;
|
|
831
|
+
async function readState(workspaceRoot) {
|
|
832
|
+
const stored = await readJsonOrNull(calendarSyncStatePath(workspaceRoot));
|
|
833
|
+
return stored?.tokens ? stored : { tokens: {} };
|
|
834
|
+
}
|
|
835
|
+
var writeQueue = { tail: Promise.resolve() };
|
|
836
|
+
async function updateState(mutate, workspaceRoot) {
|
|
837
|
+
const run = writeQueue.tail.then(async () => {
|
|
838
|
+
const state = await readState(workspaceRoot);
|
|
839
|
+
await writeJsonAtomicWithMode(calendarSyncStatePath(workspaceRoot), { tokens: mutate(state.tokens) }, SYNC_STATE_MODE);
|
|
840
|
+
});
|
|
841
|
+
writeQueue.tail = run.catch(() => void 0);
|
|
842
|
+
return await run;
|
|
843
|
+
}
|
|
844
|
+
async function loadCalendarSyncToken(calendarId, workspaceRoot) {
|
|
845
|
+
return (await readState(workspaceRoot)).tokens[calendarKey(calendarId)] ?? null;
|
|
846
|
+
}
|
|
847
|
+
async function saveCalendarSyncToken(calendarId, syncToken, workspaceRoot) {
|
|
848
|
+
const stored = calendarKey(calendarId);
|
|
849
|
+
await updateState((tokens) => ({
|
|
850
|
+
...tokens,
|
|
851
|
+
[stored]: syncToken
|
|
852
|
+
}), workspaceRoot);
|
|
853
|
+
}
|
|
854
|
+
/** Drop one calendar's token — used when Google answers 410, so the next run
|
|
855
|
+
* starts a clean full sync. */
|
|
856
|
+
async function clearCalendarSyncToken(calendarId, workspaceRoot) {
|
|
857
|
+
const dropped = calendarKey(calendarId);
|
|
858
|
+
await updateState((tokens) => Object.fromEntries(Object.entries(tokens).filter(([key]) => key !== dropped)), workspaceRoot);
|
|
859
|
+
}
|
|
860
|
+
//#endregion
|
|
861
|
+
//#region src/google/collectionSync.ts
|
|
862
|
+
var GOOGLE_CALENDAR_SYNC_TASK_ID = "system:google-calendar-sync";
|
|
863
|
+
var DEFAULT_SYNC_INTERVAL_MS = 3600 * 1e3;
|
|
864
|
+
var CANCELLED_STATUS = "cancelled";
|
|
865
|
+
/** Project one Google event onto the collection's own field names. The
|
|
866
|
+
* primary field always takes the event id — upsert-by-id is what keeps the
|
|
867
|
+
* sync idempotent, so it is deliberately not remappable. */
|
|
868
|
+
function toCollectionRecord(event, map, primaryKey) {
|
|
869
|
+
const mapped = Object.entries(map).map(([field, source]) => [field, event[source]]);
|
|
870
|
+
return {
|
|
871
|
+
...Object.fromEntries(mapped),
|
|
872
|
+
[primaryKey]: event.id
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
function classifyWrite(eventId, kind) {
|
|
876
|
+
if (kind === "ok") return { kind: "written" };
|
|
877
|
+
if (kind === "invalid-id") return {
|
|
878
|
+
kind: "unwritable",
|
|
879
|
+
message: `write ${eventId}: invalid-id`
|
|
880
|
+
};
|
|
881
|
+
return {
|
|
882
|
+
kind: "error",
|
|
883
|
+
message: `write ${eventId}: ${kind}`
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
function classifyDelete(eventId, kind) {
|
|
887
|
+
if (kind === "ok") return { kind: "removed" };
|
|
888
|
+
if (kind === "not-found") return { kind: "skipped" };
|
|
889
|
+
if (kind === "invalid-id") return {
|
|
890
|
+
kind: "unwritable",
|
|
891
|
+
message: `delete ${eventId}: invalid-id`
|
|
892
|
+
};
|
|
893
|
+
return {
|
|
894
|
+
kind: "error",
|
|
895
|
+
message: `delete ${eventId}: ${kind}`
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
async function applyEvent(collection, event, workspaceRoot) {
|
|
899
|
+
const { slug, dataDir, schema } = collection;
|
|
900
|
+
try {
|
|
901
|
+
if (event.status === CANCELLED_STATUS) {
|
|
902
|
+
const deleted = await require_discovery.deleteItem(dataDir, event.id, {
|
|
903
|
+
workspaceRoot,
|
|
904
|
+
slug
|
|
905
|
+
});
|
|
906
|
+
return classifyDelete(event.id, deleted.kind);
|
|
907
|
+
}
|
|
908
|
+
const record = toCollectionRecord(event, schema.googleCalendar?.map ?? {}, schema.primaryKey);
|
|
909
|
+
const written = await require_discovery.writeItem(dataDir, event.id, record, {
|
|
910
|
+
refuseOverwrite: false,
|
|
911
|
+
workspaceRoot,
|
|
912
|
+
slug
|
|
913
|
+
});
|
|
914
|
+
return classifyWrite(event.id, written.kind);
|
|
915
|
+
} catch (error) {
|
|
916
|
+
return {
|
|
917
|
+
kind: "error",
|
|
918
|
+
message: `apply ${event.id}: ${String(error)}`
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
async function restartFullSync(accessToken, calendarId, workspaceRoot) {
|
|
923
|
+
await clearCalendarSyncToken(calendarId, workspaceRoot);
|
|
924
|
+
return await syncCalendarEvents(accessToken, { calendarId });
|
|
925
|
+
}
|
|
926
|
+
/** Sync ONE calendar and fan its events out to every collection bound to it.
|
|
927
|
+
*
|
|
928
|
+
* The fan-out is not an optimisation, it is correctness: the sync token is
|
|
929
|
+
* keyed by `calendarId`, so syncing collection-by-collection would let the
|
|
930
|
+
* first collection advance the shared token and leave every later collection
|
|
931
|
+
* on the same calendar reading an already-consumed window — silently missing
|
|
932
|
+
* those events forever. Fetch once, apply to all, then advance the token.
|
|
933
|
+
* (Codex + CodeRabbit review on #2184.) */
|
|
934
|
+
async function syncCalendarGroup(calendarId, collections, workspaceRoot) {
|
|
935
|
+
const accessToken = await getGoogleAccessToken();
|
|
936
|
+
const first = await syncCalendarEvents(accessToken, {
|
|
937
|
+
calendarId,
|
|
938
|
+
syncToken: await loadCalendarSyncToken(calendarId, workspaceRoot) ?? void 0
|
|
939
|
+
});
|
|
940
|
+
const result = first.fullResyncRequired ? await restartFullSync(accessToken, calendarId, workspaceRoot) : first;
|
|
941
|
+
const results = [];
|
|
942
|
+
for (const collection of collections) results.push(await applyEventsToCollection(collection, result.events, workspaceRoot));
|
|
943
|
+
const unwritable = results.flatMap((entry) => entry.unwritable);
|
|
944
|
+
if (unwritable.length > 0) log.warn("google", "skipping calendar events that can never be stored", {
|
|
945
|
+
calendarId,
|
|
946
|
+
unwritable
|
|
947
|
+
});
|
|
948
|
+
const failed = results.flatMap((entry) => entry.errors);
|
|
949
|
+
if (result.nextSyncToken && failed.length === 0) await saveCalendarSyncToken(calendarId, result.nextSyncToken, workspaceRoot);
|
|
950
|
+
else if (failed.length > 0) log.warn("google", "holding back calendar sync token after failed writes", {
|
|
951
|
+
calendarId,
|
|
952
|
+
failed: failed.length
|
|
953
|
+
});
|
|
954
|
+
return results;
|
|
955
|
+
}
|
|
956
|
+
async function applyEventsToCollection(collection, events, workspaceRoot) {
|
|
957
|
+
const outcomes = [];
|
|
958
|
+
for (const event of events) outcomes.push(await applyEvent(collection, event, workspaceRoot));
|
|
959
|
+
return {
|
|
960
|
+
slug: collection.slug,
|
|
961
|
+
written: outcomes.filter((outcome) => outcome.kind === "written").length,
|
|
962
|
+
removed: outcomes.filter((outcome) => outcome.kind === "removed").length,
|
|
963
|
+
unwritable: outcomes.flatMap((outcome) => outcome.kind === "unwritable" ? [outcome.message] : []),
|
|
964
|
+
errors: outcomes.flatMap((outcome) => outcome.kind === "error" ? [outcome.message] : [])
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
/** A refresh token is what lets the scheduler run unattended; without one
|
|
968
|
+
* there is nothing to sync with. */
|
|
969
|
+
async function isGoogleLinked() {
|
|
970
|
+
return Boolean((await loadGoogleTokens())?.refresh_token);
|
|
971
|
+
}
|
|
972
|
+
/** Group the declaring collections by the calendar they read, so each calendar
|
|
973
|
+
* is fetched exactly once.
|
|
974
|
+
*
|
|
975
|
+
* Keyed by the CANONICAL id, not the declared one: an omitted `calendarId` and
|
|
976
|
+
* an explicit `"primary"` address the same calendar and therefore share one
|
|
977
|
+
* sync token, so grouping them apart would let one group advance the token out
|
|
978
|
+
* from under the other — the very loss this grouping exists to prevent
|
|
979
|
+
* (Codex review #2184). */
|
|
980
|
+
function groupByCalendar(collections) {
|
|
981
|
+
const groups = /* @__PURE__ */ new Map();
|
|
982
|
+
for (const collection of collections) {
|
|
983
|
+
const key = canonicalCalendarId(collection.schema.googleCalendar?.calendarId);
|
|
984
|
+
groups.set(key, [...groups.get(key) ?? [], collection]);
|
|
985
|
+
}
|
|
986
|
+
return groups;
|
|
987
|
+
}
|
|
988
|
+
/** Sync every collection that declares `googleCalendar`. Failures are isolated
|
|
989
|
+
* per calendar — one unreachable calendar (or a revoked grant) must not stop
|
|
990
|
+
* the others. */
|
|
991
|
+
async function syncDueCalendarCollections(workspaceRoot) {
|
|
992
|
+
const declaring = (await require_discovery.discoverCollections({ workspaceRoot })).filter((collection) => collection.schema.googleCalendar);
|
|
993
|
+
if (declaring.length === 0) return [];
|
|
994
|
+
if (!await isGoogleLinked()) {
|
|
995
|
+
log.info("google", "skipping calendar sync — no Google account linked on this host", { collections: declaring.length });
|
|
996
|
+
return [];
|
|
997
|
+
}
|
|
998
|
+
const results = [];
|
|
999
|
+
for (const [calendarId, collections] of groupByCalendar(declaring)) try {
|
|
1000
|
+
results.push(...await syncCalendarGroup(calendarId, collections, workspaceRoot));
|
|
1001
|
+
} catch (error) {
|
|
1002
|
+
log.warn("google", "calendar sync failed", {
|
|
1003
|
+
calendarId,
|
|
1004
|
+
error: String(error)
|
|
1005
|
+
});
|
|
1006
|
+
results.push(...collections.map((collection) => ({
|
|
1007
|
+
slug: collection.slug,
|
|
1008
|
+
written: 0,
|
|
1009
|
+
removed: 0,
|
|
1010
|
+
unwritable: [],
|
|
1011
|
+
errors: [String(error)]
|
|
1012
|
+
})));
|
|
1013
|
+
}
|
|
1014
|
+
return results;
|
|
1015
|
+
}
|
|
1016
|
+
/** Scheduler registration, shaped like `feedRefreshTaskDef` so hosts wire it
|
|
1017
|
+
* with a single line. */
|
|
1018
|
+
function googleCalendarSyncTaskDef(opts) {
|
|
1019
|
+
return {
|
|
1020
|
+
id: GOOGLE_CALENDAR_SYNC_TASK_ID,
|
|
1021
|
+
name: "Google Calendar sync",
|
|
1022
|
+
description: "Pulls changed Google Calendar events into any collection declaring `googleCalendar`, without invoking the LLM.",
|
|
1023
|
+
schedule: {
|
|
1024
|
+
type: _receptron_task_scheduler.SCHEDULE_TYPES.interval,
|
|
1025
|
+
intervalMs: opts?.intervalMs ?? DEFAULT_SYNC_INTERVAL_MS
|
|
1026
|
+
},
|
|
1027
|
+
missedRunPolicy: _receptron_task_scheduler.MISSED_RUN_POLICIES.runOnce,
|
|
1028
|
+
run: () => syncDueCalendarCollections(opts?.workspaceRoot ?? require_discovery.getWorkspaceRoot()).then(() => {})
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
//#endregion
|
|
756
1032
|
//#region src/google/tasks.ts
|
|
757
1033
|
var TASKS_BASE_URL = "https://tasks.googleapis.com/tasks/v1";
|
|
758
1034
|
var TASKS_API_LABEL = "Google Tasks API";
|
|
@@ -905,9 +1181,11 @@ exports.DEFAULT_LIST_MAX_RESULTS = DEFAULT_LIST_MAX_RESULTS;
|
|
|
905
1181
|
exports.GOOGLE_AUTH_CANCELLED = GOOGLE_AUTH_CANCELLED;
|
|
906
1182
|
exports.GOOGLE_CALENDARLIST_SCOPE = GOOGLE_CALENDARLIST_SCOPE;
|
|
907
1183
|
exports.GOOGLE_CALENDAR_SCOPE = GOOGLE_CALENDAR_SCOPE;
|
|
1184
|
+
exports.GOOGLE_CALENDAR_SYNC_TASK_ID = GOOGLE_CALENDAR_SYNC_TASK_ID;
|
|
908
1185
|
exports.GOOGLE_DRIVE_FILE_SCOPE = GOOGLE_DRIVE_FILE_SCOPE;
|
|
909
1186
|
exports.GOOGLE_SCOPES = GOOGLE_SCOPES;
|
|
910
1187
|
exports.GOOGLE_TASKS_SCOPE = GOOGLE_TASKS_SCOPE;
|
|
1188
|
+
exports.GoogleApiError = GoogleApiError;
|
|
911
1189
|
exports.MAX_LIST_RESULTS = MAX_LIST_RESULTS;
|
|
912
1190
|
exports.assertSafeMimeType = assertSafeMimeType;
|
|
913
1191
|
exports.authorizeGoogle = authorizeGoogle;
|
|
@@ -917,6 +1195,11 @@ exports.brokerRefresh = brokerRefresh;
|
|
|
917
1195
|
exports.brokerStart = brokerStart;
|
|
918
1196
|
exports.buildMultipartBody = buildMultipartBody;
|
|
919
1197
|
exports.calendarApiError = calendarApiError;
|
|
1198
|
+
exports.calendarSyncStatePath = calendarSyncStatePath;
|
|
1199
|
+
exports.canonicalCalendarId = canonicalCalendarId;
|
|
1200
|
+
exports.classifyDelete = classifyDelete;
|
|
1201
|
+
exports.classifyWrite = classifyWrite;
|
|
1202
|
+
exports.clearCalendarSyncToken = clearCalendarSyncToken;
|
|
920
1203
|
exports.clientSecretPresence = clientSecretPresence;
|
|
921
1204
|
exports.collectCalendarPages = collectCalendarPages;
|
|
922
1205
|
exports.commitLinkedTokens = commitLinkedTokens;
|
|
@@ -934,9 +1217,12 @@ exports.getCalendarColors = getCalendarColors;
|
|
|
934
1217
|
exports.getGoogleAccessToken = getGoogleAccessToken;
|
|
935
1218
|
exports.googleApiError = googleApiError;
|
|
936
1219
|
exports.googleAuthFlow = googleAuthFlow;
|
|
1220
|
+
exports.googleCalendarSyncTaskDef = googleCalendarSyncTaskDef;
|
|
937
1221
|
exports.googleConfigDir = googleConfigDir;
|
|
938
1222
|
exports.googleSecretsDir = googleSecretsDir;
|
|
939
1223
|
exports.googleTokenPath = googleTokenPath;
|
|
1224
|
+
exports.groupByCalendar = groupByCalendar;
|
|
1225
|
+
exports.isGoogleApiError = isGoogleApiError;
|
|
940
1226
|
exports.isIsoDateTimeWithOffset = isIsoDateTimeWithOffset;
|
|
941
1227
|
exports.isTextMimeType = isTextMimeType;
|
|
942
1228
|
exports.legacyGoogleTokenPath = legacyGoogleTokenPath;
|
|
@@ -945,13 +1231,19 @@ exports.listCalendars = listCalendars;
|
|
|
945
1231
|
exports.listDriveFiles = listDriveFiles;
|
|
946
1232
|
exports.listTaskLists = listTaskLists;
|
|
947
1233
|
exports.listTasks = listTasks;
|
|
1234
|
+
exports.loadCalendarSyncToken = loadCalendarSyncToken;
|
|
948
1235
|
exports.loadClientSecret = loadClientSecret;
|
|
949
1236
|
exports.loadGoogleTokens = loadGoogleTokens;
|
|
950
1237
|
exports.mergeGoogleTokens = mergeGoogleTokens;
|
|
951
1238
|
exports.pickBoundary = pickBoundary;
|
|
952
1239
|
exports.readDriveFile = readDriveFile;
|
|
1240
|
+
exports.saveCalendarSyncToken = saveCalendarSyncToken;
|
|
953
1241
|
exports.saveGoogleTokens = saveGoogleTokens;
|
|
1242
|
+
exports.syncCalendarEvents = syncCalendarEvents;
|
|
1243
|
+
exports.syncCalendarGroup = syncCalendarGroup;
|
|
1244
|
+
exports.syncDueCalendarCollections = syncDueCalendarCollections;
|
|
954
1245
|
exports.toCalendarSummary = toCalendarSummary;
|
|
1246
|
+
exports.toCollectionRecord = toCollectionRecord;
|
|
955
1247
|
exports.toDriveFileSummary = toDriveFileSummary;
|
|
956
1248
|
exports.toEventSummary = toEventSummary;
|
|
957
1249
|
exports.toTaskListSummary = toTaskListSummary;
|