@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.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { N as getWorkspaceRoot, b as writeItem, n as discoverCollections, u as deleteItem } from "../discovery-3K_Wtkvn.js";
|
|
1
2
|
import path, { join } from "node:path";
|
|
2
3
|
import { promises } from "node:fs";
|
|
3
4
|
import { constants, copyFile, mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
|
|
4
5
|
import { randomBytes } from "node:crypto";
|
|
5
6
|
import { homedir } from "node:os";
|
|
7
|
+
import { MISSED_RUN_POLICIES, SCHEDULE_TYPES } from "@receptron/task-scheduler";
|
|
6
8
|
import http from "node:http";
|
|
7
9
|
import { CodeChallengeMethod, OAuth2Client } from "google-auth-library";
|
|
8
10
|
//#region src/google/host.ts
|
|
@@ -607,12 +609,21 @@ var ERROR_BODY_MAX_CHARS = 300;
|
|
|
607
609
|
var HTTP_FORBIDDEN = 403;
|
|
608
610
|
var DEFAULT_LIST_MAX_RESULTS = 10;
|
|
609
611
|
var MAX_LIST_RESULTS = 50;
|
|
612
|
+
/** Carries the HTTP status so callers can branch on it (e.g. an expired
|
|
613
|
+
* calendar syncToken answers 410) without parsing the message. */
|
|
614
|
+
var GoogleApiError = class extends Error {
|
|
615
|
+
status;
|
|
616
|
+
constructor(status, message) {
|
|
617
|
+
super(message);
|
|
618
|
+
this.name = "GoogleApiError";
|
|
619
|
+
this.status = status;
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
var isGoogleApiError = (value) => value instanceof GoogleApiError;
|
|
610
623
|
/** 403 usually means the API is not enabled for the user's Cloud project —
|
|
611
624
|
* name the API so the agent's recovery guidance can be specific. */
|
|
612
625
|
var googleApiError = (apiLabel, status, body) => {
|
|
613
|
-
|
|
614
|
-
const detail = body ? ` — ${truncate(body, ERROR_BODY_MAX_CHARS)}` : "";
|
|
615
|
-
return /* @__PURE__ */ new Error(`${apiLabel}: HTTP ${status}${hint}${detail}`);
|
|
626
|
+
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)}` : ""}`);
|
|
616
627
|
};
|
|
617
628
|
async function googleRequest(apiLabel, accessToken, url, init = {}) {
|
|
618
629
|
const { contentType = "application/json", expectText = false, ...rest } = init;
|
|
@@ -645,7 +656,16 @@ var CALENDAR_API_LABEL = "Google Calendar API";
|
|
|
645
656
|
var DEFAULT_CALENDAR_ID = "primary";
|
|
646
657
|
var CALENDAR_LIST_PAGE_SIZE = 250;
|
|
647
658
|
var MAX_CALENDAR_LIST_PAGES = 40;
|
|
648
|
-
var
|
|
659
|
+
var EVENT_SYNC_PAGE_SIZE = 2500;
|
|
660
|
+
var MAX_EVENT_SYNC_PAGES = 200;
|
|
661
|
+
var HTTP_GONE = 410;
|
|
662
|
+
/** Resolve a declared calendarId to the one the API and the sync-token store
|
|
663
|
+
* both address. `||` (not `??`) so an empty string also falls back instead of
|
|
664
|
+
* building a malformed `/calendars//events` URL. Single-sourced because an
|
|
665
|
+
* omitted id and an explicit "primary" MUST agree everywhere — grouping them
|
|
666
|
+
* apart while they share a sync token silently loses events (#2184). */
|
|
667
|
+
var canonicalCalendarId = (calendarId) => calendarId || DEFAULT_CALENDAR_ID;
|
|
668
|
+
var eventsUrl = (calendarId) => `${CALENDAR_BASE_URL}/calendars/${encodeURIComponent(canonicalCalendarId(calendarId))}/events`;
|
|
649
669
|
var eventTime = (value) => {
|
|
650
670
|
if (!isRecord(value)) return "";
|
|
651
671
|
if (typeof value.dateTime === "string") return value.dateTime;
|
|
@@ -712,6 +732,54 @@ async function listCalendarEvents(accessToken, input = {}) {
|
|
|
712
732
|
});
|
|
713
733
|
return itemsOf(await googleRequest(CALENDAR_API_LABEL, accessToken, `${eventsUrl(input.calendarId)}?${params.toString()}`)).map(toEventSummary);
|
|
714
734
|
}
|
|
735
|
+
var GONE = Symbol("calendar-sync-gone");
|
|
736
|
+
async function fetchSyncPage(accessToken, calendarId, params) {
|
|
737
|
+
try {
|
|
738
|
+
return await googleRequest(CALENDAR_API_LABEL, accessToken, `${eventsUrl(calendarId)}?${params.toString()}`);
|
|
739
|
+
} catch (err) {
|
|
740
|
+
if (isGoogleApiError(err) && err.status === HTTP_GONE) return GONE;
|
|
741
|
+
throw err;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
/** Incremental sync over the events of one calendar (#2095).
|
|
745
|
+
*
|
|
746
|
+
* Deliberately separate from `listCalendarEvents`: Google forbids combining
|
|
747
|
+
* `syncToken` with `timeMin` / `timeMax` / `updatedMin` / `orderBy` / `q`,
|
|
748
|
+
* and that function always sends `timeMin` + `orderBy`. So this one sends
|
|
749
|
+
* neither — a sync covers the WHOLE calendar and the caller sorts / windows
|
|
750
|
+
* client-side. `showDeleted` must stay true or deletions would be invisible.
|
|
751
|
+
*
|
|
752
|
+
* `nextSyncToken` is only present on the LAST page, so every page must be
|
|
753
|
+
* walked before the token is worth storing. */
|
|
754
|
+
async function syncCalendarEvents(accessToken, input = {}) {
|
|
755
|
+
const events = [];
|
|
756
|
+
let pageToken;
|
|
757
|
+
let nextSyncToken;
|
|
758
|
+
for (let page = 0; page < MAX_EVENT_SYNC_PAGES; page += 1) {
|
|
759
|
+
const params = new URLSearchParams({
|
|
760
|
+
singleEvents: "true",
|
|
761
|
+
showDeleted: "true",
|
|
762
|
+
maxResults: String(input.maxResults ?? EVENT_SYNC_PAGE_SIZE)
|
|
763
|
+
});
|
|
764
|
+
if (input.syncToken) params.set("syncToken", input.syncToken);
|
|
765
|
+
if (pageToken) params.set("pageToken", pageToken);
|
|
766
|
+
const payload = await fetchSyncPage(accessToken, input.calendarId, params);
|
|
767
|
+
if (payload === GONE) return {
|
|
768
|
+
events: [],
|
|
769
|
+
fullResyncRequired: true
|
|
770
|
+
};
|
|
771
|
+
const record = asRecord(payload);
|
|
772
|
+
events.push(...itemsOf(payload).map(toEventSummary));
|
|
773
|
+
nextSyncToken = stringField(record, "nextSyncToken") || void 0;
|
|
774
|
+
pageToken = stringField(record, "nextPageToken") || void 0;
|
|
775
|
+
if (!pageToken) break;
|
|
776
|
+
}
|
|
777
|
+
return {
|
|
778
|
+
events,
|
|
779
|
+
nextSyncToken,
|
|
780
|
+
fullResyncRequired: false
|
|
781
|
+
};
|
|
782
|
+
}
|
|
715
783
|
/** Pagination loop for CalendarList.list, extracted so it can be tested without
|
|
716
784
|
* network. Stops at the last page (no token) or the runaway page cap. */
|
|
717
785
|
async function collectCalendarPages(fetchPage, maxPages = MAX_CALENDAR_LIST_PAGES) {
|
|
@@ -749,6 +817,214 @@ async function getCalendarColors(accessToken) {
|
|
|
749
817
|
};
|
|
750
818
|
}
|
|
751
819
|
//#endregion
|
|
820
|
+
//#region src/google/calendarSyncStore.ts
|
|
821
|
+
var SYNC_STATE_MODE = 384;
|
|
822
|
+
/** `<workspace>/data/calendar/.sync-state.json` */
|
|
823
|
+
function calendarSyncStatePath(workspaceRoot) {
|
|
824
|
+
return path.join(workspaceRoot ?? getWorkspaceRoot(), "data", "calendar", ".sync-state.json");
|
|
825
|
+
}
|
|
826
|
+
var calendarKey = canonicalCalendarId;
|
|
827
|
+
async function readState(workspaceRoot) {
|
|
828
|
+
const stored = await readJsonOrNull(calendarSyncStatePath(workspaceRoot));
|
|
829
|
+
return stored?.tokens ? stored : { tokens: {} };
|
|
830
|
+
}
|
|
831
|
+
var writeQueue = { tail: Promise.resolve() };
|
|
832
|
+
async function updateState(mutate, workspaceRoot) {
|
|
833
|
+
const run = writeQueue.tail.then(async () => {
|
|
834
|
+
const state = await readState(workspaceRoot);
|
|
835
|
+
await writeJsonAtomicWithMode(calendarSyncStatePath(workspaceRoot), { tokens: mutate(state.tokens) }, SYNC_STATE_MODE);
|
|
836
|
+
});
|
|
837
|
+
writeQueue.tail = run.catch(() => void 0);
|
|
838
|
+
return await run;
|
|
839
|
+
}
|
|
840
|
+
async function loadCalendarSyncToken(calendarId, workspaceRoot) {
|
|
841
|
+
return (await readState(workspaceRoot)).tokens[calendarKey(calendarId)] ?? null;
|
|
842
|
+
}
|
|
843
|
+
async function saveCalendarSyncToken(calendarId, syncToken, workspaceRoot) {
|
|
844
|
+
const stored = calendarKey(calendarId);
|
|
845
|
+
await updateState((tokens) => ({
|
|
846
|
+
...tokens,
|
|
847
|
+
[stored]: syncToken
|
|
848
|
+
}), workspaceRoot);
|
|
849
|
+
}
|
|
850
|
+
/** Drop one calendar's token — used when Google answers 410, so the next run
|
|
851
|
+
* starts a clean full sync. */
|
|
852
|
+
async function clearCalendarSyncToken(calendarId, workspaceRoot) {
|
|
853
|
+
const dropped = calendarKey(calendarId);
|
|
854
|
+
await updateState((tokens) => Object.fromEntries(Object.entries(tokens).filter(([key]) => key !== dropped)), workspaceRoot);
|
|
855
|
+
}
|
|
856
|
+
//#endregion
|
|
857
|
+
//#region src/google/collectionSync.ts
|
|
858
|
+
var GOOGLE_CALENDAR_SYNC_TASK_ID = "system:google-calendar-sync";
|
|
859
|
+
var DEFAULT_SYNC_INTERVAL_MS = 3600 * 1e3;
|
|
860
|
+
var CANCELLED_STATUS = "cancelled";
|
|
861
|
+
/** Project one Google event onto the collection's own field names. The
|
|
862
|
+
* primary field always takes the event id — upsert-by-id is what keeps the
|
|
863
|
+
* sync idempotent, so it is deliberately not remappable. */
|
|
864
|
+
function toCollectionRecord(event, map, primaryKey) {
|
|
865
|
+
const mapped = Object.entries(map).map(([field, source]) => [field, event[source]]);
|
|
866
|
+
return {
|
|
867
|
+
...Object.fromEntries(mapped),
|
|
868
|
+
[primaryKey]: event.id
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
function classifyWrite(eventId, kind) {
|
|
872
|
+
if (kind === "ok") return { kind: "written" };
|
|
873
|
+
if (kind === "invalid-id") return {
|
|
874
|
+
kind: "unwritable",
|
|
875
|
+
message: `write ${eventId}: invalid-id`
|
|
876
|
+
};
|
|
877
|
+
return {
|
|
878
|
+
kind: "error",
|
|
879
|
+
message: `write ${eventId}: ${kind}`
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
function classifyDelete(eventId, kind) {
|
|
883
|
+
if (kind === "ok") return { kind: "removed" };
|
|
884
|
+
if (kind === "not-found") return { kind: "skipped" };
|
|
885
|
+
if (kind === "invalid-id") return {
|
|
886
|
+
kind: "unwritable",
|
|
887
|
+
message: `delete ${eventId}: invalid-id`
|
|
888
|
+
};
|
|
889
|
+
return {
|
|
890
|
+
kind: "error",
|
|
891
|
+
message: `delete ${eventId}: ${kind}`
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
async function applyEvent(collection, event, workspaceRoot) {
|
|
895
|
+
const { slug, dataDir, schema } = collection;
|
|
896
|
+
try {
|
|
897
|
+
if (event.status === CANCELLED_STATUS) {
|
|
898
|
+
const deleted = await deleteItem(dataDir, event.id, {
|
|
899
|
+
workspaceRoot,
|
|
900
|
+
slug
|
|
901
|
+
});
|
|
902
|
+
return classifyDelete(event.id, deleted.kind);
|
|
903
|
+
}
|
|
904
|
+
const record = toCollectionRecord(event, schema.googleCalendar?.map ?? {}, schema.primaryKey);
|
|
905
|
+
const written = await writeItem(dataDir, event.id, record, {
|
|
906
|
+
refuseOverwrite: false,
|
|
907
|
+
workspaceRoot,
|
|
908
|
+
slug
|
|
909
|
+
});
|
|
910
|
+
return classifyWrite(event.id, written.kind);
|
|
911
|
+
} catch (error) {
|
|
912
|
+
return {
|
|
913
|
+
kind: "error",
|
|
914
|
+
message: `apply ${event.id}: ${String(error)}`
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
async function restartFullSync(accessToken, calendarId, workspaceRoot) {
|
|
919
|
+
await clearCalendarSyncToken(calendarId, workspaceRoot);
|
|
920
|
+
return await syncCalendarEvents(accessToken, { calendarId });
|
|
921
|
+
}
|
|
922
|
+
/** Sync ONE calendar and fan its events out to every collection bound to it.
|
|
923
|
+
*
|
|
924
|
+
* The fan-out is not an optimisation, it is correctness: the sync token is
|
|
925
|
+
* keyed by `calendarId`, so syncing collection-by-collection would let the
|
|
926
|
+
* first collection advance the shared token and leave every later collection
|
|
927
|
+
* on the same calendar reading an already-consumed window — silently missing
|
|
928
|
+
* those events forever. Fetch once, apply to all, then advance the token.
|
|
929
|
+
* (Codex + CodeRabbit review on #2184.) */
|
|
930
|
+
async function syncCalendarGroup(calendarId, collections, workspaceRoot) {
|
|
931
|
+
const accessToken = await getGoogleAccessToken();
|
|
932
|
+
const first = await syncCalendarEvents(accessToken, {
|
|
933
|
+
calendarId,
|
|
934
|
+
syncToken: await loadCalendarSyncToken(calendarId, workspaceRoot) ?? void 0
|
|
935
|
+
});
|
|
936
|
+
const result = first.fullResyncRequired ? await restartFullSync(accessToken, calendarId, workspaceRoot) : first;
|
|
937
|
+
const results = [];
|
|
938
|
+
for (const collection of collections) results.push(await applyEventsToCollection(collection, result.events, workspaceRoot));
|
|
939
|
+
const unwritable = results.flatMap((entry) => entry.unwritable);
|
|
940
|
+
if (unwritable.length > 0) log.warn("google", "skipping calendar events that can never be stored", {
|
|
941
|
+
calendarId,
|
|
942
|
+
unwritable
|
|
943
|
+
});
|
|
944
|
+
const failed = results.flatMap((entry) => entry.errors);
|
|
945
|
+
if (result.nextSyncToken && failed.length === 0) await saveCalendarSyncToken(calendarId, result.nextSyncToken, workspaceRoot);
|
|
946
|
+
else if (failed.length > 0) log.warn("google", "holding back calendar sync token after failed writes", {
|
|
947
|
+
calendarId,
|
|
948
|
+
failed: failed.length
|
|
949
|
+
});
|
|
950
|
+
return results;
|
|
951
|
+
}
|
|
952
|
+
async function applyEventsToCollection(collection, events, workspaceRoot) {
|
|
953
|
+
const outcomes = [];
|
|
954
|
+
for (const event of events) outcomes.push(await applyEvent(collection, event, workspaceRoot));
|
|
955
|
+
return {
|
|
956
|
+
slug: collection.slug,
|
|
957
|
+
written: outcomes.filter((outcome) => outcome.kind === "written").length,
|
|
958
|
+
removed: outcomes.filter((outcome) => outcome.kind === "removed").length,
|
|
959
|
+
unwritable: outcomes.flatMap((outcome) => outcome.kind === "unwritable" ? [outcome.message] : []),
|
|
960
|
+
errors: outcomes.flatMap((outcome) => outcome.kind === "error" ? [outcome.message] : [])
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
/** A refresh token is what lets the scheduler run unattended; without one
|
|
964
|
+
* there is nothing to sync with. */
|
|
965
|
+
async function isGoogleLinked() {
|
|
966
|
+
return Boolean((await loadGoogleTokens())?.refresh_token);
|
|
967
|
+
}
|
|
968
|
+
/** Group the declaring collections by the calendar they read, so each calendar
|
|
969
|
+
* is fetched exactly once.
|
|
970
|
+
*
|
|
971
|
+
* Keyed by the CANONICAL id, not the declared one: an omitted `calendarId` and
|
|
972
|
+
* an explicit `"primary"` address the same calendar and therefore share one
|
|
973
|
+
* sync token, so grouping them apart would let one group advance the token out
|
|
974
|
+
* from under the other — the very loss this grouping exists to prevent
|
|
975
|
+
* (Codex review #2184). */
|
|
976
|
+
function groupByCalendar(collections) {
|
|
977
|
+
const groups = /* @__PURE__ */ new Map();
|
|
978
|
+
for (const collection of collections) {
|
|
979
|
+
const key = canonicalCalendarId(collection.schema.googleCalendar?.calendarId);
|
|
980
|
+
groups.set(key, [...groups.get(key) ?? [], collection]);
|
|
981
|
+
}
|
|
982
|
+
return groups;
|
|
983
|
+
}
|
|
984
|
+
/** Sync every collection that declares `googleCalendar`. Failures are isolated
|
|
985
|
+
* per calendar — one unreachable calendar (or a revoked grant) must not stop
|
|
986
|
+
* the others. */
|
|
987
|
+
async function syncDueCalendarCollections(workspaceRoot) {
|
|
988
|
+
const declaring = (await discoverCollections({ workspaceRoot })).filter((collection) => collection.schema.googleCalendar);
|
|
989
|
+
if (declaring.length === 0) return [];
|
|
990
|
+
if (!await isGoogleLinked()) {
|
|
991
|
+
log.info("google", "skipping calendar sync — no Google account linked on this host", { collections: declaring.length });
|
|
992
|
+
return [];
|
|
993
|
+
}
|
|
994
|
+
const results = [];
|
|
995
|
+
for (const [calendarId, collections] of groupByCalendar(declaring)) try {
|
|
996
|
+
results.push(...await syncCalendarGroup(calendarId, collections, workspaceRoot));
|
|
997
|
+
} catch (error) {
|
|
998
|
+
log.warn("google", "calendar sync failed", {
|
|
999
|
+
calendarId,
|
|
1000
|
+
error: String(error)
|
|
1001
|
+
});
|
|
1002
|
+
results.push(...collections.map((collection) => ({
|
|
1003
|
+
slug: collection.slug,
|
|
1004
|
+
written: 0,
|
|
1005
|
+
removed: 0,
|
|
1006
|
+
unwritable: [],
|
|
1007
|
+
errors: [String(error)]
|
|
1008
|
+
})));
|
|
1009
|
+
}
|
|
1010
|
+
return results;
|
|
1011
|
+
}
|
|
1012
|
+
/** Scheduler registration, shaped like `feedRefreshTaskDef` so hosts wire it
|
|
1013
|
+
* with a single line. */
|
|
1014
|
+
function googleCalendarSyncTaskDef(opts) {
|
|
1015
|
+
return {
|
|
1016
|
+
id: GOOGLE_CALENDAR_SYNC_TASK_ID,
|
|
1017
|
+
name: "Google Calendar sync",
|
|
1018
|
+
description: "Pulls changed Google Calendar events into any collection declaring `googleCalendar`, without invoking the LLM.",
|
|
1019
|
+
schedule: {
|
|
1020
|
+
type: SCHEDULE_TYPES.interval,
|
|
1021
|
+
intervalMs: opts?.intervalMs ?? DEFAULT_SYNC_INTERVAL_MS
|
|
1022
|
+
},
|
|
1023
|
+
missedRunPolicy: MISSED_RUN_POLICIES.runOnce,
|
|
1024
|
+
run: () => syncDueCalendarCollections(opts?.workspaceRoot ?? getWorkspaceRoot()).then(() => {})
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
//#endregion
|
|
752
1028
|
//#region src/google/tasks.ts
|
|
753
1029
|
var TASKS_BASE_URL = "https://tasks.googleapis.com/tasks/v1";
|
|
754
1030
|
var TASKS_API_LABEL = "Google Tasks API";
|
|
@@ -897,6 +1173,6 @@ async function deleteDriveFile(accessToken, input) {
|
|
|
897
1173
|
await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}/${encodeURIComponent(input.fileId)}`, { method: "DELETE" });
|
|
898
1174
|
}
|
|
899
1175
|
//#endregion
|
|
900
|
-
export { DEFAULT_LIST_MAX_RESULTS, GOOGLE_AUTH_CANCELLED, GOOGLE_CALENDARLIST_SCOPE, GOOGLE_CALENDAR_SCOPE, GOOGLE_DRIVE_FILE_SCOPE, GOOGLE_SCOPES, GOOGLE_TASKS_SCOPE, MAX_LIST_RESULTS, assertSafeMimeType, authorizeGoogle, brokerBaseUrl, brokerExchange, brokerRefresh, brokerStart, buildMultipartBody, calendarApiError, clientSecretPresence, collectCalendarPages, commitLinkedTokens, completeTask, configureGoogleHost, createCalendarEvent, createDriveFile, createGoogleAuthFlow, createTask, deleteDriveFile, deleteGoogleTokens, deleteTask, findClientSecretPath, getCalendarColors, getGoogleAccessToken, googleApiError, googleAuthFlow, googleConfigDir, googleSecretsDir, googleTokenPath, isIsoDateTimeWithOffset, isTextMimeType, legacyGoogleTokenPath, listCalendarEvents, listCalendars, listDriveFiles, listTaskLists, listTasks, loadClientSecret, loadGoogleTokens, mergeGoogleTokens, pickBoundary, readDriveFile, saveGoogleTokens, toCalendarSummary, toDriveFileSummary, toEventSummary, toTaskListSummary, toTaskSummary, unlinkGoogle, waitForAuthCode };
|
|
1176
|
+
export { 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, MAX_LIST_RESULTS, assertSafeMimeType, authorizeGoogle, brokerBaseUrl, brokerExchange, brokerRefresh, brokerStart, buildMultipartBody, calendarApiError, calendarSyncStatePath, canonicalCalendarId, classifyDelete, classifyWrite, clearCalendarSyncToken, clientSecretPresence, collectCalendarPages, commitLinkedTokens, completeTask, configureGoogleHost, createCalendarEvent, createDriveFile, createGoogleAuthFlow, createTask, deleteDriveFile, deleteGoogleTokens, deleteTask, findClientSecretPath, getCalendarColors, getGoogleAccessToken, googleApiError, googleAuthFlow, googleCalendarSyncTaskDef, googleConfigDir, googleSecretsDir, googleTokenPath, groupByCalendar, isGoogleApiError, isIsoDateTimeWithOffset, isTextMimeType, legacyGoogleTokenPath, listCalendarEvents, listCalendars, listDriveFiles, listTaskLists, listTasks, loadCalendarSyncToken, loadClientSecret, loadGoogleTokens, mergeGoogleTokens, pickBoundary, readDriveFile, saveCalendarSyncToken, saveGoogleTokens, syncCalendarEvents, syncCalendarGroup, syncDueCalendarCollections, toCalendarSummary, toCollectionRecord, toDriveFileSummary, toEventSummary, toTaskListSummary, toTaskSummary, unlinkGoogle, waitForAuthCode };
|
|
901
1177
|
|
|
902
1178
|
//# sourceMappingURL=index.js.map
|