@mulmoclaude/core 1.5.0 → 1.7.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 (69) hide show
  1. package/assets/helps/bug-report-faq.md +130 -0
  2. package/assets/helps/error-recovery.md +134 -1
  3. package/assets/helps/google-calendar-collection.md +57 -3
  4. package/assets/helps/google.md +3 -2
  5. package/assets/helps/index.md +2 -1
  6. package/dist/calendarGrid-CaS9er8i.cjs +576 -0
  7. package/dist/calendarGrid-CaS9er8i.cjs.map +1 -0
  8. package/dist/calendarGrid-CrGxeCOG.js +397 -0
  9. package/dist/calendarGrid-CrGxeCOG.js.map +1 -0
  10. package/dist/collection/index.cjs +44 -44
  11. package/dist/collection/index.cjs.map +1 -1
  12. package/dist/collection/index.js +2 -2
  13. package/dist/collection/registry/server/index.cjs +2 -2
  14. package/dist/collection/registry/server/index.js +2 -2
  15. package/dist/collection/server/index.cjs +4 -4
  16. package/dist/collection/server/index.js +3 -3
  17. package/dist/collection-watchers/index.cjs +5 -5
  18. package/dist/collection-watchers/index.cjs.map +1 -1
  19. package/dist/collection-watchers/index.js +4 -4
  20. package/dist/{discovery--kRUWed0.cjs → discovery-DYcR83qx.cjs} +64 -53
  21. package/dist/discovery-DYcR83qx.cjs.map +1 -0
  22. package/dist/{discovery-CKR4TOX3.js → discovery-_fjsluLx.js} +43 -32
  23. package/dist/discovery-_fjsluLx.js.map +1 -0
  24. package/dist/feeds/index.cjs +4 -4
  25. package/dist/feeds/index.js +2 -2
  26. package/dist/feeds/server/index.cjs +6 -6
  27. package/dist/feeds/server/index.js +4 -4
  28. package/dist/google/apiClient.d.ts +6 -0
  29. package/dist/google/calendar.d.ts +79 -8
  30. package/dist/google/calendarPushState.d.ts +17 -0
  31. package/dist/google/collectionPush.d.ts +64 -0
  32. package/dist/google/collectionSync.d.ts +10 -0
  33. package/dist/google/index.cjs +712 -36
  34. package/dist/google/index.cjs.map +1 -1
  35. package/dist/google/index.d.ts +7 -3
  36. package/dist/google/index.js +683 -37
  37. package/dist/google/index.js.map +1 -1
  38. package/dist/google/pushDateTime.d.ts +10 -0
  39. package/dist/google/pushPlan.d.ts +73 -0
  40. package/dist/{ingestTypes-xX8VpQqh.cjs → ingestTypes-D1GQdG8e.cjs} +3 -3
  41. package/dist/{ingestTypes-xX8VpQqh.cjs.map → ingestTypes-D1GQdG8e.cjs.map} +1 -1
  42. package/dist/{ingestTypes-L59cIGgX.js → ingestTypes-XReG-li7.js} +2 -2
  43. package/dist/{ingestTypes-L59cIGgX.js.map → ingestTypes-XReG-li7.js.map} +1 -1
  44. package/dist/{promptSafety-CQ5Un4wi.js → promptSafety-C--op_6a.js} +3 -269
  45. package/dist/promptSafety-C--op_6a.js.map +1 -0
  46. package/dist/{promptSafety-DfZYNtjK.cjs → promptSafety-FD8cQn3e.cjs} +8 -358
  47. package/dist/promptSafety-FD8cQn3e.cjs.map +1 -0
  48. package/dist/remote-host/index.cjs +8 -0
  49. package/dist/remote-host/index.cjs.map +1 -1
  50. package/dist/remote-host/index.d.ts +21 -0
  51. package/dist/remote-host/index.js +8 -1
  52. package/dist/remote-host/index.js.map +1 -1
  53. package/dist/{server-D_6FLygK.js → server-BlIrtGKl.js} +4 -4
  54. package/dist/{server-D_6FLygK.js.map → server-BlIrtGKl.js.map} +1 -1
  55. package/dist/{server-gEOeV_Nv.cjs → server-l4TZ5Lbf.cjs} +21 -21
  56. package/dist/{server-gEOeV_Nv.cjs.map → server-l4TZ5Lbf.cjs.map} +1 -1
  57. package/dist/workspace-setup/faq-entries.d.ts +24 -0
  58. package/dist/workspace-setup/index.d.ts +1 -0
  59. package/dist/workspace-setup/index.js +50 -1
  60. package/dist/workspace-setup/index.js.map +1 -1
  61. package/package.json +1 -1
  62. package/dist/discovery--kRUWed0.cjs.map +0 -1
  63. package/dist/discovery-CKR4TOX3.js.map +0 -1
  64. package/dist/ids-BnTh3Hl0.cjs +0 -226
  65. package/dist/ids-BnTh3Hl0.cjs.map +0 -1
  66. package/dist/ids-s4GfmE93.js +0 -131
  67. package/dist/ids-s4GfmE93.js.map +0 -1
  68. package/dist/promptSafety-CQ5Un4wi.js.map +0 -1
  69. package/dist/promptSafety-DfZYNtjK.cjs.map +0 -1
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_ids = require("../ids-BnTh3Hl0.cjs");
3
- const require_ingestTypes = require("../ingestTypes-xX8VpQqh.cjs");
2
+ const require_calendarGrid = require("../calendarGrid-CaS9er8i.cjs");
3
+ const require_ingestTypes = require("../ingestTypes-D1GQdG8e.cjs");
4
4
  exports.AGENT_INGEST_KIND = require_ingestTypes.AGENT_INGEST_KIND;
5
5
  exports.DEFAULT_FEED_MAX_ITEMS = require_ingestTypes.DEFAULT_FEED_MAX_ITEMS;
6
- exports.FEED_SCHEDULES = require_ids.FEED_SCHEDULES;
7
- exports.INGEST_KINDS = require_ids.INGEST_KINDS;
6
+ exports.FEED_SCHEDULES = require_calendarGrid.FEED_SCHEDULES;
7
+ exports.INGEST_KINDS = require_calendarGrid.INGEST_KINDS;
8
8
  exports.isFeedSchedule = require_ingestTypes.isFeedSchedule;
@@ -1,3 +1,3 @@
1
- import { l as FEED_SCHEDULES, u as INGEST_KINDS } from "../ids-s4GfmE93.js";
2
- import { n as DEFAULT_FEED_MAX_ITEMS, r as isFeedSchedule, t as AGENT_INGEST_KIND } from "../ingestTypes-L59cIGgX.js";
1
+ import { C as FEED_SCHEDULES, w as INGEST_KINDS } from "../calendarGrid-CrGxeCOG.js";
2
+ import { n as DEFAULT_FEED_MAX_ITEMS, r as isFeedSchedule, t as AGENT_INGEST_KIND } from "../ingestTypes-XReG-li7.js";
3
3
  export { AGENT_INGEST_KIND, DEFAULT_FEED_MAX_ITEMS, FEED_SCHEDULES, INGEST_KINDS, isFeedSchedule };
@@ -1,10 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rolldown_runtime = require("../../rolldown-runtime-D6vf50IK.cjs");
3
- const require_ids = require("../../ids-BnTh3Hl0.cjs");
4
- const require_discovery = require("../../discovery--kRUWed0.cjs");
5
- const require_server = require("../../server-gEOeV_Nv.cjs");
3
+ const require_calendarGrid = require("../../calendarGrid-CaS9er8i.cjs");
4
+ const require_discovery = require("../../discovery-DYcR83qx.cjs");
5
+ const require_server = require("../../server-l4TZ5Lbf.cjs");
6
6
  const require_feeds_paths = require("../paths.cjs");
7
- const require_ingestTypes = require("../../ingestTypes-xX8VpQqh.cjs");
7
+ const require_ingestTypes = require("../../ingestTypes-D1GQdG8e.cjs");
8
8
  const require_notifier = require("../../notifier-tMsAXyXp.cjs");
9
9
  let node_path = require("node:path");
10
10
  node_path = require_rolldown_runtime.__toESM(node_path, 1);
@@ -912,8 +912,8 @@ exports.DEFAULT_FEED_MAX_ITEMS = require_ingestTypes.DEFAULT_FEED_MAX_ITEMS;
912
912
  exports.DEFAULT_FEED_REFRESH_INTERVAL_MS = DEFAULT_FEED_REFRESH_INTERVAL_MS;
913
913
  exports.FEEDS_DIR = require_feeds_paths.FEEDS_DIR;
914
914
  exports.FEED_REFRESH_TASK_ID = FEED_REFRESH_TASK_ID;
915
- exports.FEED_SCHEDULES = require_ids.FEED_SCHEDULES;
916
- exports.INGEST_KINDS = require_ids.INGEST_KINDS;
915
+ exports.FEED_SCHEDULES = require_calendarGrid.FEED_SCHEDULES;
916
+ exports.INGEST_KINDS = require_calendarGrid.INGEST_KINDS;
917
917
  exports.configureFeedsHost = configureFeedsHost;
918
918
  exports.feedDir = require_feeds_paths.feedDir;
919
919
  exports.feedRefreshTaskDef = feedRefreshTaskDef;
@@ -1,8 +1,8 @@
1
- import { l as FEED_SCHEDULES, u as INGEST_KINDS } from "../../ids-s4GfmE93.js";
2
- import { N as listItems, V as resolveDataDir, W as safeSlugName, d as storeFor, et as createForwardingLogger, n as discoverCollections, tt as createHostSlot } from "../../discovery-CKR4TOX3.js";
3
- import { A as buildCollectionActionSeedPrompt, P as readSkillTemplate, j as promptPathsFor } from "../../server-D_6FLygK.js";
1
+ import { C as FEED_SCHEDULES, w as INGEST_KINDS } from "../../calendarGrid-CrGxeCOG.js";
2
+ import { N as listItems, V as resolveDataDir, W as safeSlugName, d as storeFor, et as createForwardingLogger, n as discoverCollections, tt as createHostSlot } from "../../discovery-_fjsluLx.js";
3
+ import { A as buildCollectionActionSeedPrompt, P as readSkillTemplate, j as promptPathsFor } from "../../server-BlIrtGKl.js";
4
4
  import { FEEDS_DIR, feedDir, feedStatePath, feedsRoot, ingestStateDir, ingestStatePath } from "../paths.js";
5
- import { n as DEFAULT_FEED_MAX_ITEMS, r as isFeedSchedule, t as AGENT_INGEST_KIND } from "../../ingestTypes-L59cIGgX.js";
5
+ import { n as DEFAULT_FEED_MAX_ITEMS, r as isFeedSchedule, t as AGENT_INGEST_KIND } from "../../ingestTypes-XReG-li7.js";
6
6
  import { d as publish, n as clear } from "../../notifier-BdA5qzhe.js";
7
7
  import path from "node:path";
8
8
  import { createHash } from "node:crypto";
@@ -1,4 +1,5 @@
1
1
  export declare const GOOGLE_API_TIMEOUT_MS: number;
2
+ export declare const HTTP_FORBIDDEN = 403;
2
3
  export declare const DEFAULT_LIST_MAX_RESULTS = 10;
3
4
  export declare const MAX_LIST_RESULTS = 50;
4
5
  /** Carries the HTTP status so callers can branch on it (e.g. an expired
@@ -18,6 +19,11 @@ export interface GoogleRequestInit {
18
19
  contentType?: string;
19
20
  /** Response is not JSON (Drive media download) — return the raw text. */
20
21
  expectText?: boolean;
22
+ /** Extra request headers, merged after the defaults. Used for conditional
23
+ * writes (`If-Match: <etag>`), which is how a caller makes a PATCH fail with
24
+ * 412 instead of silently overwriting a newer version. Cannot replace
25
+ * `Authorization`. */
26
+ extraHeaders?: Record<string, string>;
21
27
  }
22
28
  export declare function googleRequest(apiLabel: string, accessToken: string, url: string, init?: GoogleRequestInit): Promise<unknown>;
23
29
  export declare const stringField: (record: Record<string, unknown>, key: string) => string;
@@ -1,28 +1,72 @@
1
+ /** An `If-Match` write whose etag no longer matches — someone else changed the
2
+ * event since it was read. */
3
+ export declare const HTTP_PRECONDITION_FAILED = 412;
4
+ /** An `insert` whose caller-supplied event id is already taken. */
5
+ export declare const HTTP_CONFLICT = 409;
6
+ /** Google's status for an event that was deleted; a sync window reports it so
7
+ * consumers can remove their copy. Single-sourced — the pull and the push both
8
+ * branch on it. */
9
+ export declare const CANCELLED_EVENT_STATUS = "cancelled";
1
10
  /** Resolve a declared calendarId to the one the API and the sync-token store
2
11
  * both address. `||` (not `??`) so an empty string also falls back instead of
3
12
  * building a malformed `/calendars//events` URL. Single-sourced because an
4
13
  * omitted id and an explicit "primary" MUST agree everywhere — grouping them
5
14
  * apart while they share a sync token silently loses events (#2184). */
6
15
  export declare const canonicalCalendarId: (calendarId: string | undefined) => string;
7
- export interface CalendarEventInput {
8
- summary: string;
16
+ /** One end of an event as the API expresses it: a timed instant, or a whole day.
17
+ *
18
+ * `timeZone` names the zone a `dateTime` WITHOUT an offset is read in — the
19
+ * shape a collection push needs, since the stored clock carries no offset
20
+ * (`pushDateTime.ts`). A `dateTime` that already ends in `Z`/`+09:00` needs no
21
+ * `timeZone`. `date` is an all-day event, whose `end` Google treats as
22
+ * EXCLUSIVE. */
23
+ export type CalendarEventTime = {
24
+ dateTime: string;
25
+ timeZone?: string;
26
+ } | {
27
+ date: string;
28
+ };
29
+ /** The two ways to state an event's span. The flat pair is what the `google`
30
+ * tool passes (RFC3339 with an offset); the structured pair is what a push
31
+ * needs to express an all-day event or an explicit zone. Expressed as a union
32
+ * so "neither given" cannot typecheck on a create. */
33
+ export type CalendarEventSpan = {
9
34
  startDateTime: string;
10
35
  endDateTime: string;
36
+ } | {
37
+ start: CalendarEventTime;
38
+ end: CalendarEventTime;
39
+ };
40
+ export type CalendarEventInput = {
41
+ summary: string;
11
42
  description?: string;
12
43
  /** Calendar to create the event on; defaults to the user's primary. */
13
44
  calendarId?: string;
14
45
  /** Event colour (Google event palette id "1".."11"); omit to inherit the calendar's colour. */
15
46
  colorId?: string;
16
- }
47
+ /** Caller-chosen event id. Google requires base32hex (`0-9a-v`), 5-1024 chars,
48
+ * and answers 409 when it is already taken. A collection push sets this so a
49
+ * locally-created record keeps its own record id as the event id, instead of
50
+ * being re-keyed to Google's after the fact — a re-key that is missed leaves
51
+ * a duplicate record on the next pull. */
52
+ eventId?: string;
53
+ } & CalendarEventSpan;
17
54
  export interface UpdateCalendarEventInput {
18
55
  eventId: string;
19
56
  summary?: string;
20
57
  startDateTime?: string;
21
58
  endDateTime?: string;
59
+ /** Structured span; wins over the flat pair when both are present. */
60
+ start?: CalendarEventTime;
61
+ end?: CalendarEventTime;
22
62
  /** `""` clears the description; omit to leave it untouched. */
23
63
  description?: string;
24
64
  calendarId?: string;
25
65
  colorId?: string;
66
+ /** Etag of the version this edit was computed against. Sent as `If-Match`, so
67
+ * Google answers 412 rather than letting the PATCH clobber a change that
68
+ * landed after the caller read the event. Omit for an unconditional write. */
69
+ ifMatch?: string;
26
70
  }
27
71
  export interface DeleteCalendarEventInput {
28
72
  eventId: string;
@@ -55,6 +99,10 @@ export interface CalendarSummary {
55
99
  foregroundColor: string;
56
100
  /** Calendar palette id backing background/foregroundColor. */
57
101
  colorId: string;
102
+ /** IANA zone (`Asia/Tokyo`). The zone a pushed `dateTime` without an offset is
103
+ * read in: the collection stores the clock the user reads off THIS calendar,
104
+ * so this is the only correct zone to re-attach. `""` when Google omits it. */
105
+ timeZone: string;
58
106
  }
59
107
  export interface CalendarColorEntry {
60
108
  background: string;
@@ -71,17 +119,31 @@ export declare const toCalendarSummary: (value: unknown) => CalendarSummary;
71
119
  /** Kept as a named export for the existing unit tests / callers; the shared
72
120
  * helper now carries the wording. */
73
121
  export declare const calendarApiError: (status: number, body: string) => Error;
122
+ /** Normalise either spelling of a span into the pair the API body carries. */
123
+ export declare const resolveEventSpan: (span: CalendarEventSpan) => {
124
+ start: CalendarEventTime;
125
+ end: CalendarEventTime;
126
+ };
74
127
  export declare function createCalendarEvent(accessToken: string, input: CalendarEventInput): Promise<CalendarEventSummary>;
75
- /** PATCH body for an event edit — only the fields the caller actually supplied.
76
- *
77
- * `undefined` means "leave as is" and `""` means "clear it", so the two cannot
78
- * be collapsed: dropping `description: ""` would silently ignore a request to
79
- * empty the body text. Pure so the distinction is testable without network. */
80
128
  export declare const buildEventPatch: (input: UpdateCalendarEventInput) => Record<string, unknown>;
81
129
  /** Edit an existing event in place. PATCH, not PUT — a PUT would need the whole
82
130
  * event and would drop every field the caller never read (attendees,
83
131
  * reminders, recurrence). */
84
132
  export declare function updateCalendarEvent(accessToken: string, input: UpdateCalendarEventInput): Promise<CalendarEventSummary>;
133
+ /** An event plus the version token a conditional write needs. */
134
+ export interface FetchedCalendarEvent {
135
+ event: CalendarEventSummary;
136
+ /** Google's `etag` for this version, `""` when absent. Sent back as
137
+ * `If-Match` so a PATCH cannot overwrite a version we never read. */
138
+ etag: string;
139
+ }
140
+ /** Read ONE event, or null when it is gone (404 / 410).
141
+ *
142
+ * A push needs Google's current value per changed record to tell a local-only
143
+ * edit from a both-sides conflict. Deliberately not `syncCalendarEvents`: that
144
+ * consumes the calendar's sync token, and the token's window is served once —
145
+ * spending it here would make the next pull miss everything in it. */
146
+ export declare function getCalendarEvent(accessToken: string, input: DeleteCalendarEventInput): Promise<FetchedCalendarEvent | null>;
85
147
  /** Remove an event. Google answers 204 with no body, so there is nothing to
86
148
  * return; a second delete of the same id answers 410, which surfaces as a
87
149
  * GoogleApiError rather than being swallowed. */
@@ -126,6 +188,15 @@ export declare function collectCalendarPages(fetchPage: (pageToken?: string) =>
126
188
  /** The calendars the user has added/subscribed to (primary + secondary +
127
189
  * shared), each with its id, name and colour, following pagination. Needs the
128
190
  * calendar-list read scope (GOOGLE_CALENDARLIST_SCOPE). */
191
+ /** One calendar's own resource, addressed by id rather than looked up in the
192
+ * user's list.
193
+ *
194
+ * `calendarList` only holds calendars the user has ADDED; a calendar shared
195
+ * with them can be readable and writable by id without appearing there. This is
196
+ * how a push learns such a calendar's `timeZone`. Note the resource carries no
197
+ * `accessRole` — that is a calendarList property — so reachability here says
198
+ * nothing about writability. */
199
+ export declare function getCalendar(accessToken: string, calendarId: string | undefined): Promise<CalendarSummary>;
129
200
  export declare function listCalendars(accessToken: string): Promise<CalendarSummary[]>;
130
201
  /** Resolve a `colorId` (on an event or calendar) to its hex background/foreground. */
131
202
  export declare function getCalendarColors(accessToken: string): Promise<CalendarColors>;
@@ -0,0 +1,17 @@
1
+ import { CalendarEventSummary } from './calendar.js';
2
+ /** The pushable subset of an event. `htmlLink` and `status` are read-only on
3
+ * Google's side, so a baseline for them could never be acted on. */
4
+ export type ShadowEvent = Pick<CalendarEventSummary, "summary" | "start" | "end" | "colorId">;
5
+ /** `<workspace>/data/calendar/.push-state.json` */
6
+ export declare function calendarPushStatePath(workspaceRoot?: string): string;
7
+ export declare const toShadowEvent: (event: CalendarEventSummary) => ShadowEvent;
8
+ export declare function loadCalendarShadow(calendarId?: string, workspaceRoot?: string): Promise<Record<string, ShadowEvent>>;
9
+ /** Apply a batch to one calendar's baseline. `null` removes an entry (the event
10
+ * was cancelled), so a cancel-then-recreate cannot resume from a stale one.
11
+ * Merged rather than replaced: a sync window describes only what changed. */
12
+ export declare function mergeShadow(current: Record<string, ShadowEvent>, updates: Record<string, ShadowEvent | null>): Record<string, ShadowEvent>;
13
+ export declare function saveCalendarShadow(calendarId: string | undefined, updates: Record<string, ShadowEvent | null>, workspaceRoot?: string): Promise<void>;
14
+ /** Drop one calendar's whole baseline. Called wherever the sync token is
15
+ * cleared: a baseline outliving its token would let a push diff against events
16
+ * the workspace no longer holds. */
17
+ export declare function clearCalendarShadow(calendarId?: string, workspaceRoot?: string): Promise<void>;
@@ -0,0 +1,64 @@
1
+ import { LoadedCollection } from '../collection/server/discoveredCollection.js';
2
+ /** What the up-front writability gate knows about the target calendar. */
3
+ export interface CalendarWriteTarget {
4
+ /** The role from the user's calendar LIST, or null when the calendar is not in
5
+ * it. Null means unknown, never read-only: `calendarList` holds only the
6
+ * calendars the user has added, so one shared with write access can be absent
7
+ * from it (Codex review). */
8
+ accessRole: string | null;
9
+ /** IANA zone for rebuilding a zone-less stored clock, `""` when unreported. */
10
+ timeZone: string;
11
+ }
12
+ /** Whether to refuse the whole push before touching a single event.
13
+ *
14
+ * Only on POSITIVE evidence of a non-writable role. An unlisted calendar is
15
+ * unknown and must fall through: hard-denying it would block the feature
16
+ * outright for a calendar the user can in fact write to. Those attempts surface
17
+ * Google's own 403 per record if the write really is not allowed. */
18
+ export declare const isDeniedAccessRole: (accessRole: string | null) => boolean;
19
+ export interface CalendarCollectionPushResult {
20
+ slug: string;
21
+ created: number;
22
+ updated: number;
23
+ /** Edited on both sides; skipped so neither version is destroyed. */
24
+ conflicts: number;
25
+ /** Records deleted locally. Reported only — v1 never deletes in Google. */
26
+ localDeletes: number;
27
+ /** Records that cannot be pushed as they stand, each with the reason. */
28
+ skipped: string[];
29
+ errors: string[];
30
+ }
31
+ export type CalendarPushOutcome = {
32
+ kind: "pushed";
33
+ result: CalendarCollectionPushResult;
34
+ } | {
35
+ kind: "not-a-calendar";
36
+ } | {
37
+ kind: "not-linked";
38
+ } | {
39
+ kind: "read-only";
40
+ accessRole: string;
41
+ }
42
+ /** The push never got as far as a record: a revoked grant, an unreachable
43
+ * Calendar API, a workspace read failure. Typed rather than left to throw so
44
+ * the route answers in the same shape as the other setup problems instead of
45
+ * an opaque 500. */
46
+ | {
47
+ kind: "failed";
48
+ message: string;
49
+ };
50
+ /** The I/O a push crosses, injected so every outcome can be exercised with fakes
51
+ * instead of a workspace on disk and a live Google grant. */
52
+ export interface CalendarPushDeps {
53
+ findCollection: (slug: string, workspaceRoot: string) => Promise<LoadedCollection | null>;
54
+ isLinked: () => Promise<boolean>;
55
+ accessToken: () => Promise<string>;
56
+ calendarMeta: (accessToken: string, calendarId: string | undefined) => Promise<CalendarWriteTarget>;
57
+ }
58
+ /** Push ONE collection's records to the calendar it declares.
59
+ *
60
+ * "Does this collection sync at all" is answered before "is Google linked", for
61
+ * the reason `syncCalendarForCollection` gives: telling someone to link their
62
+ * account for a collection that never declared a calendar sends them fixing the
63
+ * wrong thing. */
64
+ export declare function pushCalendarForCollection(slug: string, workspaceRoot: string, deps?: CalendarPushDeps): Promise<CalendarPushOutcome>;
@@ -4,6 +4,7 @@ import { DeleteItemResult, WriteItemResult } from '../collection/server/io.js';
4
4
  import { CollectionFieldSpec, CollectionItem } from '../collection/core/schema.js';
5
5
  import { GOOGLE_CALENDAR_SOURCE_FIELDS } from '../collection/core/schemaZ.js';
6
6
  import { CalendarEventSummary } from './calendar.js';
7
+ import { ShadowEvent } from './calendarPushState.js';
7
8
  export declare const GOOGLE_CALENDAR_SYNC_TASK_ID = "system:google-calendar-sync";
8
9
  export interface CalendarCollectionSyncResult {
9
10
  slug: string;
@@ -61,7 +62,16 @@ export declare function withKeyedLock<T>(locks: Map<string, Promise<unknown>>, k
61
62
  * writes are upserts by event id — but it is a wasted full walk. Queued, the
62
63
  * second pass resumes from the token the first just stored and fetches only
63
64
  * what is genuinely new. */
65
+ /** Serialise anything that touches ONE calendar's sync state.
66
+ *
67
+ * Shared with the push path (#2598), not only the pull doors: a push that
68
+ * overtakes an in-flight pull would record a baseline for events the pull is
69
+ * still writing, so the next push would diff against a future it never saw. */
70
+ export declare function withCalendarLock<T>(calendarId: string | undefined, run: () => Promise<T>): Promise<T>;
64
71
  export declare function syncCalendarGroup(calendarId: string | undefined, collections: readonly LoadedCollection[], workspaceRoot: string): Promise<CalendarCollectionSyncResult[]>;
72
+ /** The baseline this window establishes: what Google now says per event, and
73
+ * `null` for a cancelled one so a recreate cannot resume from a dead baseline. */
74
+ export declare function shadowUpdates(events: readonly CalendarEventSummary[]): Record<string, ShadowEvent | null>;
65
75
  /** Liveness of the collections a sync just wrote to, checked against the skill
66
76
  * dir `deleteCollection` removes. `exists` is injected so the rule is testable
67
77
  * without a filesystem. An empty group has no survivor by definition. */