@mulmoclaude/core 1.11.0 → 1.13.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/error-recovery.md +98 -0
- package/dist/collection/core/project.d.ts +11 -0
- package/dist/collection/index.cjs +3 -2
- package/dist/collection/index.js +3 -3
- package/dist/collection/registry/server/index.cjs +3 -3
- package/dist/collection/registry/server/index.js +3 -3
- package/dist/collection/server/index.cjs +2 -2
- package/dist/collection/server/index.js +2 -2
- package/dist/collection-watchers/index.cjs +3 -3
- package/dist/collection-watchers/index.js +3 -3
- package/dist/{discovery-B4CZvrXR.js → discovery-D-ZJ6a6z.js} +39 -18
- package/dist/discovery-D-ZJ6a6z.js.map +1 -0
- package/dist/{discovery-NRy3tyUA.cjs → discovery-DAoMnBYM.cjs} +39 -18
- package/dist/discovery-DAoMnBYM.cjs.map +1 -0
- package/dist/{dist-pWpC-b04.cjs → dist-21pMsI1a.cjs} +45 -1
- package/dist/{dist-pWpC-b04.cjs.map → dist-21pMsI1a.cjs.map} +1 -1
- package/dist/{dist-Cwk0e12G.js → dist-DP1FTGnO.js} +16 -2
- package/dist/{dist-Cwk0e12G.js.map → dist-DP1FTGnO.js.map} +1 -1
- package/dist/feeds/server/engine.d.ts +27 -0
- package/dist/feeds/server/index.cjs +72 -7
- package/dist/feeds/server/index.cjs.map +1 -1
- package/dist/feeds/server/index.d.ts +1 -1
- package/dist/feeds/server/index.js +71 -8
- package/dist/feeds/server/index.js.map +1 -1
- package/dist/google/calendarStateLock.d.ts +23 -0
- package/dist/google/calendarSyncDue.d.ts +10 -0
- package/dist/google/calendarSyncStore.d.ts +20 -1
- package/dist/google/collectionProjection.d.ts +0 -8
- package/dist/google/collectionSync.d.ts +104 -15
- package/dist/google/index.cjs +512 -89
- package/dist/google/index.cjs.map +1 -1
- package/dist/google/index.d.ts +6 -3
- package/dist/google/index.js +501 -91
- package/dist/google/index.js.map +1 -1
- package/dist/notifier/index.cjs +1 -1
- package/dist/notifier/index.js +1 -1
- package/dist/notifier/store.d.ts +4 -4
- package/dist/{notifier-tMsAXyXp.cjs → notifier-C3IhYxqh.cjs} +39 -25
- package/dist/notifier-C3IhYxqh.cjs.map +1 -0
- package/dist/{notifier-BdA5qzhe.js → notifier-CMFlw74b.js} +39 -25
- package/dist/notifier-CMFlw74b.js.map +1 -0
- package/dist/project-B9NSyr6L.js +28 -0
- package/dist/project-B9NSyr6L.js.map +1 -0
- package/dist/project-CCtT_fQr.cjs +39 -0
- package/dist/project-CCtT_fQr.cjs.map +1 -0
- package/dist/remote-host/server/index.cjs +1 -1
- package/dist/remote-host/server/index.js +1 -1
- package/dist/remote-view/index.cjs +1 -1
- package/dist/remote-view/index.js +1 -1
- package/dist/scheduler/index.cjs +1 -1
- package/dist/scheduler/index.js +1 -1
- package/dist/{server-7U-3DE2e.cjs → server-B_b0bLSW.cjs} +3 -3
- package/dist/{server-7U-3DE2e.cjs.map → server-B_b0bLSW.cjs.map} +1 -1
- package/dist/{server-DdyIvFhz.js → server-Ci_A3chZ.js} +3 -3
- package/dist/{server-DdyIvFhz.js.map → server-Ci_A3chZ.js.map} +1 -1
- package/dist/utils/index.cjs +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/whisper/index.cjs +1 -1
- package/dist/whisper/index.js +1 -1
- package/dist/wiki/index.cjs +1 -1
- package/dist/wiki/index.js +1 -1
- package/package.json +1 -1
- package/dist/discovery-B4CZvrXR.js.map +0 -1
- package/dist/discovery-NRy3tyUA.cjs.map +0 -1
- package/dist/notifier-BdA5qzhe.js.map +0 -1
- package/dist/notifier-tMsAXyXp.cjs.map +0 -1
- package/dist/project-BWI5w_BT.cjs +0 -17
- package/dist/project-BWI5w_BT.cjs.map +0 -1
- package/dist/project-bU98ycsy.js +0 -12
- package/dist/project-bU98ycsy.js.map +0 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { SystemTaskDef } from '../scheduler/adapter.js';
|
|
2
|
+
import { CollectionItem } from '../collection/core/schema.js';
|
|
2
3
|
import { LoadedCollection } from '../collection/server/discoveredCollection.js';
|
|
3
4
|
import { DeleteItemResult, WriteItemResult } from '../collection/server/io.js';
|
|
4
5
|
import { CalendarEventSummary } from './calendar.js';
|
|
6
|
+
import { CalendarPushOutcome } from './collectionPush.js';
|
|
5
7
|
import { ShadowEvent } from './calendarPushState.js';
|
|
6
8
|
export declare const GOOGLE_CALENDAR_SYNC_TASK_ID = "system:google-calendar-sync";
|
|
7
9
|
export interface CalendarCollectionSyncResult {
|
|
@@ -11,17 +13,24 @@ export interface CalendarCollectionSyncResult {
|
|
|
11
13
|
/** Events that can NEVER be stored — e.g. an id the record-file sanitiser
|
|
12
14
|
* rejects. Reported and skipped rather than retried; see `classifyWrite`. */
|
|
13
15
|
unwritable: string[];
|
|
16
|
+
/** Events left alone because the record they would overwrite holds an edit
|
|
17
|
+
* Google has not seen. NOT an error — the token still advances past them; only
|
|
18
|
+
* the baseline is held back, so the next push reports the conflict (#2684). */
|
|
19
|
+
withheld: string[];
|
|
14
20
|
/** Retryable failures. Any of these hold the sync token back. */
|
|
15
21
|
errors: string[];
|
|
16
22
|
}
|
|
17
23
|
/** `skipped` is a benign no-op; `unwritable` can never succeed so it must NOT
|
|
18
|
-
* hold the token; `error` is retryable and does hold it
|
|
24
|
+
* hold the token; `error` is retryable and does hold it; `withheld` is a
|
|
25
|
+
* deliberate refusal to overwrite a local edit. */
|
|
19
26
|
type ApplyOutcome = {
|
|
20
27
|
kind: "written";
|
|
21
28
|
} | {
|
|
22
29
|
kind: "removed";
|
|
23
30
|
} | {
|
|
24
31
|
kind: "skipped";
|
|
32
|
+
} | {
|
|
33
|
+
kind: "withheld";
|
|
25
34
|
} | {
|
|
26
35
|
kind: "unwritable";
|
|
27
36
|
message: string;
|
|
@@ -31,6 +40,24 @@ type ApplyOutcome = {
|
|
|
31
40
|
};
|
|
32
41
|
export declare function classifyWrite(eventId: string, kind: WriteItemResult["kind"]): ApplyOutcome;
|
|
33
42
|
export declare function classifyDelete(eventId: string, kind: DeleteItemResult["kind"]): ApplyOutcome;
|
|
43
|
+
/** Whether this record still says what the workspace last saw Google say.
|
|
44
|
+
*
|
|
45
|
+
* Compared against the baseline as it stood when this run STARTED READING, not
|
|
46
|
+
* as it stands now: a full re-walk clears the baseline before writing a new one
|
|
47
|
+
* (`restartFullSync`), so reading it live would answer "no baseline" for every
|
|
48
|
+
* event and protect nothing exactly when the window is widest (#2684).
|
|
49
|
+
*
|
|
50
|
+
* A record with no baseline at all is NOT withheld — that is an event this
|
|
51
|
+
* workspace has never held, so there is no local edit to lose. */
|
|
52
|
+
export declare function unsentEditGuard(schema: LoadedCollection["schema"], baseline: Record<string, ShadowEvent>): (existing: CollectionItem, eventId: string) => boolean;
|
|
53
|
+
/** What the pull may do to the record behind one event.
|
|
54
|
+
*
|
|
55
|
+
* The guard runs BEFORE the status is consulted, and that order is the whole
|
|
56
|
+
* fix: "is there something local to lose here?" outranks "what did Google do
|
|
57
|
+
* to it?". Asking about the status first is how a cancellation kept deleting
|
|
58
|
+
* records that held an edit Google had never seen (#2688), long after the
|
|
59
|
+
* same guard had been put in front of the overwrite (#2684). */
|
|
60
|
+
export declare function applyPlanFor(existing: CollectionItem | null, event: CalendarEventSummary, hasUnsentEdit: (existing: CollectionItem, eventId: string) => boolean): "withhold" | "delete" | "write";
|
|
34
61
|
/** The events of a window a pull may act on.
|
|
35
62
|
*
|
|
36
63
|
* A record the push just refused to send is edited on BOTH sides. Writing
|
|
@@ -50,15 +77,15 @@ export type UnpushedBySlug = ReadonlyMap<string, ReadonlySet<string> | null>;
|
|
|
50
77
|
* protection — would overwrite the very edits this exists to protect; the read
|
|
51
78
|
* that failed is no evidence that the pull's own writes would fail too, so they
|
|
52
79
|
* would land (CodeRabbit review #2666). */
|
|
53
|
-
export declare const PROTECTION_UNKNOWN = "could not work out which records to protect
|
|
54
|
-
/** What ONE collection's pull must leave alone: only what ITS OWN push
|
|
80
|
+
export declare const PROTECTION_UNKNOWN = "could not work out which records to protect from the pull";
|
|
81
|
+
/** What ONE collection's pull must leave alone: only what ITS OWN push did not
|
|
55
82
|
* send.
|
|
56
83
|
*
|
|
57
|
-
* Scoped per collection because a calendar can back several of them, and
|
|
58
|
-
*
|
|
59
|
-
* group starved a collection that never even declares `autoPush`:
|
|
60
|
-
*
|
|
61
|
-
*
|
|
84
|
+
* Scoped per collection because a calendar can back several of them, and one
|
|
85
|
+
* collection's unsent edit says nothing about the others. Sharing one set across
|
|
86
|
+
* the group starved a collection that never even declares `autoPush`: a
|
|
87
|
+
* neighbour's conflict froze its records — and the sync token still advanced, so
|
|
88
|
+
* Google never resent them (Codex review #2666). */
|
|
62
89
|
export declare const unpushedFor: (unpushed: UnpushedBySlug, slug: string) => ReadonlySet<string> | null;
|
|
63
90
|
/** What the calendar's BASELINE must leave alone: the union over every
|
|
64
91
|
* collection.
|
|
@@ -74,6 +101,38 @@ export declare const unpushedFor: (unpushed: UnpushedBySlug, slug: string) => Re
|
|
|
74
101
|
* A `null` (unknown) entry contributes nothing, because that collection reports
|
|
75
102
|
* a retryable error instead — which stops the baseline being saved at all. */
|
|
76
103
|
export declare const allUnpushed: (unpushed: UnpushedBySlug) => ReadonlySet<string>;
|
|
104
|
+
/** The I/O the protection rule crosses, injected so every branch of it can be
|
|
105
|
+
* exercised with fakes instead of a workspace on disk and a live Google grant.
|
|
106
|
+
* The rule is what #2666 and #2683 both got wrong, so it is worth pinning. */
|
|
107
|
+
export interface PullProtectionDeps {
|
|
108
|
+
pushNow: (collection: LoadedCollection, workspaceRoot: string) => Promise<CalendarPushOutcome>;
|
|
109
|
+
unsentEdits: (collection: LoadedCollection, workspaceRoot: string) => Promise<string[]>;
|
|
110
|
+
}
|
|
111
|
+
/** What ONE collection's pull must leave alone, pushing it first if it asked to
|
|
112
|
+
* be pushed.
|
|
113
|
+
*
|
|
114
|
+
* A collection WITHOUT `autoPush` never pushes, so its local edits are unsent by
|
|
115
|
+
* definition — the same state a failed push leaves behind, and it needs the same
|
|
116
|
+
* protection. Pulling over them destroys the edit AND advances the baseline past
|
|
117
|
+
* it, after which no conflict can be detected any more (#2683). "The push did not
|
|
118
|
+
* run" is the condition that matters here; why it did not run is not.
|
|
119
|
+
*
|
|
120
|
+
* MUST run inside the calendar lock the caller already holds — hence
|
|
121
|
+
* `pushCollectionNow` rather than `pushCalendarForCollection`, which would take
|
|
122
|
+
* the same non-reentrant lock and wait on itself forever.
|
|
123
|
+
*
|
|
124
|
+
* A failed push must not stop the pull: the pull is what keeps the collection
|
|
125
|
+
* fresh, and a revoked write grant is no reason to freeze reading. */
|
|
126
|
+
export declare function pullProtectionFor(collection: LoadedCollection, workspaceRoot: string, deps?: PullProtectionDeps): Promise<ReadonlySet<string> | null>;
|
|
127
|
+
/** Push the `autoPush` collections in this group, and answer with what every
|
|
128
|
+
* collection's pull must leave alone, keyed by collection. */
|
|
129
|
+
export declare function pushAndProtect(collections: readonly LoadedCollection[], workspaceRoot: string, deps?: PullProtectionDeps): Promise<UnpushedBySlug>;
|
|
130
|
+
/** Whether a run may take the calendar, given what the shared marker says.
|
|
131
|
+
*
|
|
132
|
+
* The scheduled door defers to it; every user-facing door claims regardless,
|
|
133
|
+
* because a Refresh click that silently returns nothing reads as an empty
|
|
134
|
+
* calendar, not as "another host has this". */
|
|
135
|
+
export type ClaimGuard = (lastSyncedAt: string | null) => boolean;
|
|
77
136
|
/** Sync ONE calendar and fan its events out to every collection bound to it.
|
|
78
137
|
*
|
|
79
138
|
* The fan-out is not an optimisation, it is correctness: the sync token is
|
|
@@ -88,8 +147,17 @@ export declare const allUnpushed: (unpushed: UnpushedBySlug) => ReadonlySet<stri
|
|
|
88
147
|
* load the SAME stored token and walk the same window. That is idempotent —
|
|
89
148
|
* writes are upserts by event id — but it is a wasted full walk. Queued, the
|
|
90
149
|
* second pass resumes from the token the first just stored and fetches only
|
|
91
|
-
* what is genuinely new.
|
|
92
|
-
|
|
150
|
+
* what is genuinely new. That queue is module state, so it orders the doors
|
|
151
|
+
* into THIS process only; `claimThenSync` is what other hosts can see. */
|
|
152
|
+
export declare function syncCalendarGroup(calendarId: string | undefined, collections: readonly LoadedCollection[], workspaceRoot: string, mayClaim?: ClaimGuard): Promise<CalendarCollectionSyncResult[]>;
|
|
153
|
+
/** Every event whose baseline must NOT advance: what the push could not send,
|
|
154
|
+
* plus what the apply refused to overwrite.
|
|
155
|
+
*
|
|
156
|
+
* The two must agree exactly. The apply's refusals only became visible after it
|
|
157
|
+
* ran, so they join the union here rather than at push time — leaving them out
|
|
158
|
+
* would advance the baseline past a record the pull deliberately left holding a
|
|
159
|
+
* local edit, which is the silent overwrite this whole path exists to stop. */
|
|
160
|
+
export declare const heldBack: (unpushed: UnpushedBySlug, results: readonly CalendarCollectionSyncResult[]) => ReadonlySet<string>;
|
|
93
161
|
/** The baseline this window establishes: what Google now says per event, and
|
|
94
162
|
* `null` for a cancelled one so a recreate cannot resume from a dead baseline.
|
|
95
163
|
*
|
|
@@ -98,8 +166,17 @@ export declare function syncCalendarGroup(calendarId: string | undefined, collec
|
|
|
98
166
|
* keeps the local one would make the next push read a plain one-sided edit —
|
|
99
167
|
* no conflict to detect any more — and quietly overwrite Google. Held back, the
|
|
100
168
|
* baseline stays older than both sides, so the conflict keeps being reported
|
|
101
|
-
* until someone resolves it (#2620).
|
|
102
|
-
|
|
169
|
+
* until someone resolves it (#2620).
|
|
170
|
+
*
|
|
171
|
+
* `held` carries what those events must KEEP. Omitting them is enough on an
|
|
172
|
+
* incremental run, where the file is merged rather than replaced — but a full
|
|
173
|
+
* re-walk CLEARS the baseline first (`restartFullSync`), and there omission
|
|
174
|
+
* drops the entry for good. The next push would then read a conflicted record
|
|
175
|
+
* as a brand-new create, hit Google's duplicate-id 409 and refuse it, instead
|
|
176
|
+
* of reporting the conflict it actually is. Re-stating the pre-run value makes
|
|
177
|
+
* a held-back event behave the same either way (observed during Claude review;
|
|
178
|
+
* no bot flagged it). */
|
|
179
|
+
export declare function shadowUpdates(events: readonly CalendarEventSummary[], unpushed?: ReadonlySet<string>, held?: Record<string, ShadowEvent>): Record<string, ShadowEvent | null>;
|
|
103
180
|
/** Liveness of the collections a sync just wrote to, checked against the skill
|
|
104
181
|
* dir `deleteCollection` removes. `exists` is injected so the rule is testable
|
|
105
182
|
* without a filesystem. An empty group has no survivor by definition. */
|
|
@@ -139,8 +216,15 @@ export declare function orphanedCalendarId(deleted: CalendarDeclaring, remaining
|
|
|
139
216
|
* Returns the cleared calendar id, or null when nothing was cleared. Never
|
|
140
217
|
* throws: a failed cleanup must not fail the delete it follows. */
|
|
141
218
|
export declare function releaseOrphanedCalendarToken(deleted: CalendarDeclaring, workspaceRoot: string): Promise<string | null>;
|
|
142
|
-
/** Sync every collection
|
|
143
|
-
|
|
219
|
+
/** Sync every collection whose calendar is due — no host in this workspace has
|
|
220
|
+
* started one within `intervalMs` (#2678). Without that gate this walked every
|
|
221
|
+
* declaring group on every tick, so a second host registering the same task
|
|
222
|
+
* simply doubled the runs, concurrently.
|
|
223
|
+
*
|
|
224
|
+
* Dueness is not decided here, only described: the guard is handed down and
|
|
225
|
+
* evaluated where the marker is written, so the answer cannot go stale between
|
|
226
|
+
* deciding and claiming (Codex review #2680). */
|
|
227
|
+
export declare function syncDueCalendarCollections(workspaceRoot: string, intervalMs?: number): Promise<CalendarCollectionSyncResult[]>;
|
|
144
228
|
/** The groups whose calendar has never synced. A missing token IS the "created
|
|
145
229
|
* since the last sync" signal — nothing else distinguishes a new collection
|
|
146
230
|
* from an edited one on the write path this feeds (#2427).
|
|
@@ -184,7 +268,12 @@ export interface ManualCalendarSyncDeps {
|
|
|
184
268
|
* calendar sends them fixing the wrong thing. */
|
|
185
269
|
export declare function syncCalendarForCollection(slug: string, workspaceRoot: string, deps?: ManualCalendarSyncDeps): Promise<ManualCalendarSyncOutcome>;
|
|
186
270
|
/** Scheduler registration, shaped like `feedRefreshTaskDef` so hosts wire it
|
|
187
|
-
* with a single line.
|
|
271
|
+
* with a single line.
|
|
272
|
+
*
|
|
273
|
+
* `run` reads the interval back off the definition instead of closing over the
|
|
274
|
+
* option: a host rewrites `schedule` from its own overrides file AFTER this
|
|
275
|
+
* returns, and the due window has to follow it. Frozen at the default, a
|
|
276
|
+
* shortened interval would tick often and skip nearly every tick. */
|
|
188
277
|
export declare function googleCalendarSyncTaskDef(opts?: {
|
|
189
278
|
workspaceRoot?: string;
|
|
190
279
|
intervalMs?: number;
|