@mulmoclaude/core 5.0.1 → 5.3.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 (41) hide show
  1. package/assets/helps/error-recovery.md +78 -0
  2. package/assets/helps/google-calendar-collection.md +130 -15
  3. package/assets/helps/google.md +14 -4
  4. package/dist/collection/core/schemaZ.d.ts +15 -2
  5. package/dist/collection/core/viewChatPolicy.d.ts +6 -3
  6. package/dist/collection/index.cjs +6 -3
  7. package/dist/collection/index.cjs.map +1 -1
  8. package/dist/collection/index.js +6 -3
  9. package/dist/collection/index.js.map +1 -1
  10. package/dist/collection/registry/server/index.cjs +2 -2
  11. package/dist/collection/registry/server/index.js +2 -2
  12. package/dist/collection/server/index.cjs +2 -2
  13. package/dist/collection/server/index.js +2 -2
  14. package/dist/collection-watchers/index.cjs +2 -2
  15. package/dist/collection-watchers/index.js +2 -2
  16. package/dist/{discovery-CBjI1lx9.js → discovery-B_64PBZU.js} +14 -3
  17. package/dist/{discovery-CBjI1lx9.js.map → discovery-B_64PBZU.js.map} +1 -1
  18. package/dist/{discovery-DCMW05Bp.cjs → discovery-CDXYswtw.cjs} +14 -3
  19. package/dist/{discovery-DCMW05Bp.cjs.map → discovery-CDXYswtw.cjs.map} +1 -1
  20. package/dist/feeds/server/index.cjs +2 -2
  21. package/dist/feeds/server/index.js +2 -2
  22. package/dist/google/calendar.d.ts +24 -0
  23. package/dist/google/collectionPush.d.ts +45 -2
  24. package/dist/google/deletePlan.d.ts +32 -0
  25. package/dist/google/eventDerived.d.ts +19 -0
  26. package/dist/google/eventSpanInput.d.ts +58 -0
  27. package/dist/google/index.cjs +286 -18
  28. package/dist/google/index.cjs.map +1 -1
  29. package/dist/google/index.d.ts +5 -2
  30. package/dist/google/index.js +272 -19
  31. package/dist/google/index.js.map +1 -1
  32. package/dist/remote-view/index.cjs +7 -7
  33. package/dist/remote-view/index.cjs.map +1 -1
  34. package/dist/remote-view/index.d.ts +3 -3
  35. package/dist/remote-view/index.js +7 -7
  36. package/dist/remote-view/index.js.map +1 -1
  37. package/dist/{server-BMZ_BcPH.js → server-7POrhHKv.js} +2 -2
  38. package/dist/{server-BMZ_BcPH.js.map → server-7POrhHKv.js.map} +1 -1
  39. package/dist/{server-rA9FSkl2.cjs → server-DfYwSX8b.cjs} +2 -2
  40. package/dist/{server-rA9FSkl2.cjs.map → server-DfYwSX8b.cjs.map} +1 -1
  41. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../src/google/host.ts","../../src/google/datetime.ts","../../src/google/paths.ts","../../src/google/util.ts","../../src/google/clientSecret.ts","../../src/google/fsJson.ts","../../src/google/tokenStore.ts","../../src/google/broker.ts","../../src/google/auth.ts","../../src/google/authFlow.ts","../../src/google/apiClient.ts","../../src/google/calendar.ts","../../src/google/calendarStateLock.ts","../../src/google/calendarSyncStore.ts","../../src/google/calendarSyncDue.ts","../../src/google/calendarBackfillState.ts","../../src/google/calendarPushState.ts","../../src/google/calendarLock.ts","../../src/google/pushDateTime.ts","../../src/google/collectionDateTime.ts","../../src/google/collectionProjection.ts","../../src/google/pushPlan.ts","../../src/google/collectionPush.ts","../../src/google/collectionSync.ts","../../src/google/tasks.ts","../../src/google/driveFile.ts"],"sourcesContent":["// Host binding for the Google engine. The engine logs through the host's\n// logger, but a package-level import of the host logger would be an uphill\n// dependency — so the host injects it once at startup (same pattern as\n// `collection/server/host.ts`). The default is silent so the engine works\n// unconfigured in unit tests.\n\nimport { createForwardingLogger, createHostSlot, type StructuredLogger } from \"../host/hostSlot.js\";\n\n/** Public alias of the shared `StructuredLogger` — keeps the domain surface name-stable. */\nexport type GoogleLogger = StructuredLogger;\n\nconst hostSlot = createHostSlot<GoogleLogger>(\"@mulmoclaude/core/google: configureGoogleHost()\");\n\nexport function configureGoogleHost(binding: { log: GoogleLogger }): void {\n hostSlot.set(binding.log);\n}\n\nexport const log: GoogleLogger = createForwardingLogger(() => hostSlot.peek());\n","// Strict RFC3339 date-time validation shared by every surface that feeds\n// Calendar `dateTime`/`timeMin` values (agent tool args, remote-host command\n// params). Calendar rejects date-only / offset-less values with an opaque\n// 400, so callers validate here and return an actionable message instead.\n\n// Fractional seconds are normalized away first — an optional `(\\.\\d+)?`\n// group inside the main pattern trips security/detect-unsafe-regex.\nconst ISO_DATE_TIME_WITH_OFFSET_RE = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:Z|[+-](\\d{2}):(\\d{2}))$/;\nconst FRACTIONAL_SECONDS_RE = /\\.\\d+(?=Z|[+-])/;\n\nconst MAX_HOUR = 23;\nconst MAX_MINUTE = 59;\nconst MAX_SECOND = 59;\n\n// JS Date normalizes overflowed components (2026-02-31 parses as\n// 2026-03-03), so `new Date(value)` alone cannot reject impossible dates —\n// a UTC round-trip of the raw components can.\nconst isRealCalendarDate = (year: number, month: number, day: number): boolean => {\n const roundTrip = new Date(Date.UTC(year, month - 1, day));\n return roundTrip.getUTCFullYear() === year && roundTrip.getUTCMonth() === month - 1 && roundTrip.getUTCDate() === day;\n};\n\nexport const isIsoDateTimeWithOffset = (value: string): boolean => {\n const match = ISO_DATE_TIME_WITH_OFFSET_RE.exec(value.replace(FRACTIONAL_SECONDS_RE, \"\"));\n if (!match) return false;\n const [year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0] = match.slice(1, 7).map(Number);\n const timeInRange = hour <= MAX_HOUR && minute <= MAX_MINUTE && second <= MAX_SECOND;\n // RFC3339 bounds the offset to 00-23:59; groups 7/8 are undefined for \"Z\".\n const offsetInRange = match[7] === undefined || (Number(match[7]) <= MAX_HOUR && Number(match[8]) <= MAX_MINUTE);\n return isRealCalendarDate(year, month, day) && timeInRange && offsetInRange;\n};\n","// Google OAuth material lives OUTSIDE the workspace: the client secret is\n// machine-only (never synced) and the refresh token must survive workspace\n// resets — same reasoning as the gcloud / gh CLI model. The dir is the\n// host-NEUTRAL `~/.config/mulmo` because this engine is shared by both\n// MulmoClaude and MulmoTerminal, which deliberately share one grant per\n// machine. The `home` parameter exists so tests can thread a fake home.\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { mulmoConfigDir } from \"../global-config/paths.js\";\n\n/** Same directory as every other cross-app per-user file — see\n * `../global-config/paths.ts`, which owns the spelling. */\nexport function googleConfigDir(home?: string): string {\n return mulmoConfigDir(home);\n}\n\n/** Pre-0.20.1 token dir (mulmoclaude-branded); reads migrate away from it. */\nexport function legacyGoogleTokenPath(home?: string): string {\n return join(home ?? homedir(), \".config\", \"mulmoclaude\", \"google-token.json\");\n}\n\nexport function googleTokenPath(home?: string): string {\n return join(googleConfigDir(home), \"google-token.json\");\n}\n\nexport function googleSecretsDir(home?: string): string {\n return join(home ?? homedir(), \".secrets\");\n}\n","// Time + type helpers kept local so the Google engine carries no host\n// dependency. `errorMessage` / `truncate` now come from the canonical core\n// helpers (#2217): the local copies had drifted — `errorMessage` collapsed\n// gRPC-shaped errors to \"unknown error\" instead of surfacing their `details`,\n// and `truncate` dropped the guard that keeps output within `max`.\nexport { errorMessage } from \"../utils/errors.js\";\nexport { truncate } from \"../utils/text.js\";\n\nexport const ONE_SECOND_MS = 1_000;\nexport const ONE_MINUTE_MS = 60_000;\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","// Loads the Google OAuth **desktop-app** client credentials a user may have\n// downloaded from the Cloud Console into `~/.secrets/client_secret_*.json`.\n// Files are discovered by prefix so nobody has to rename Google's long default\n// filename.\n//\n// Only `{\"installed\": …}` (desktop) files count. A `{\"web\": …}` client cannot\n// drive the loopback consent this engine runs — and one legitimately sits in\n// the same directory for anyone who also deploys the broker — so web clients\n// are skipped rather than treated as a competing choice.\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { isRecord } from \"./util.js\";\nimport { googleSecretsDir } from \"./paths.js\";\n\nexport interface InstalledClientSecret {\n client_id: string;\n client_secret: string;\n}\n\n// Empty strings must not count: they would satisfy a `typeof` check, get\n// picked as the client, and fail at Google with an opaque invalid_client.\nconst isNonEmptyString = (value: unknown): value is string => typeof value === \"string\" && value !== \"\";\n\nconst isInstalledClientSecret = (value: unknown): value is { installed: InstalledClientSecret } => {\n if (!isRecord(value) || !isRecord(value.installed)) return false;\n return isNonEmptyString(value.installed.client_id) && isNonEmptyString(value.installed.client_secret);\n};\n\nconst isClientSecretFileName = (name: string): boolean => name.startsWith(\"client_secret_\") && name.endsWith(\".json\");\n\nconst readIfDesktopClient = async (filePath: string): Promise<string | null> => {\n try {\n const parsed: unknown = JSON.parse(await readFile(filePath, \"utf-8\"));\n return isInstalledClientSecret(parsed) ? filePath : null;\n } catch {\n // Unreadable or malformed: not a usable desktop client, and refusing the\n // whole directory over one stray file would be worse than ignoring it.\n return null;\n }\n};\n\n/** Absolute paths of every desktop-app client JSON in `~/.secrets/`. */\nasync function listDesktopClientSecretFiles(home?: string): Promise<string[]> {\n const dir = googleSecretsDir(home);\n const entries = await readdir(dir).catch((): string[] => []);\n const candidates = entries.filter(isClientSecretFileName).sort();\n const checked = await Promise.all(candidates.map((name) => readIfDesktopClient(join(dir, name))));\n return checked.filter((path): path is string => path !== null);\n}\n\n/** `missing` is the ordinary case — the broker supplies the client, so no user\n * action is needed. `ambiguous` (2+ desktop clients) still needs a human:\n * a stored refresh token pairs with exactly one client_id, so picking for\n * them could silently break the link. */\nexport type ClientSecretPresence = \"found\" | \"missing\" | \"ambiguous\";\n\nexport async function clientSecretPresence(home?: string): Promise<ClientSecretPresence> {\n const matches = await listDesktopClientSecretFiles(home);\n if (matches.length === 0) return \"missing\";\n return matches.length === 1 ? \"found\" : \"ambiguous\";\n}\n\nexport async function findClientSecretPath(home?: string): Promise<string> {\n const dir = googleSecretsDir(home);\n const matches = await listDesktopClientSecretFiles(home);\n const [first, ...rest] = matches;\n if (!first) {\n throw new Error(`no desktop-app client_secret_*.json found in ${dir} — this host links through the sign-in service instead`);\n }\n if (rest.length > 0) {\n const names = matches.map((path) => path.slice(dir.length + 1)).join(\", \");\n throw new Error(`multiple desktop-app client_secret_*.json files found in ${dir} (${names}) — keep exactly one`);\n }\n return first;\n}\n\nexport async function loadClientSecret(home?: string): Promise<InstalledClientSecret> {\n const filePath = await findClientSecretPath(home);\n const parsed: unknown = JSON.parse(await readFile(filePath, \"utf-8\"));\n if (!isInstalledClientSecret(parsed)) {\n throw new Error(`${filePath} is not a desktop-app OAuth client secret (missing \"installed\" with client_id / client_secret)`);\n }\n return { client_id: parsed.installed.client_id, client_secret: parsed.installed.client_secret };\n}\n","// Minimal JSON file I/O for the token store. The token file must be 0600, so\n// the write goes through the shared atomic writer with an explicit `mode`.\nimport { promises as fsp } from \"node:fs\";\nimport { writeFileAtomic } from \"../files/atomic.js\";\n\nconst JSON_INDENT = 2;\n\nexport async function readJsonOrNull<T>(filePath: string): Promise<T | null> {\n try {\n const raw = await fsp.readFile(filePath, \"utf-8\");\n const parsed: T = JSON.parse(raw);\n return parsed;\n } catch {\n return null;\n }\n}\n\n/** tmp-write + rename so readers never see a half-written file; `mode` applies\n * to the tmp file and survives the rename. A single-writer token file, so the\n * staging name is stable (`uniqueTmp: false`). */\nexport async function writeJsonAtomicWithMode(filePath: string, data: unknown, mode: number): Promise<void> {\n await writeFileAtomic(filePath, JSON.stringify(data, null, JSON_INDENT), { mode, uniqueTmp: false });\n}\n","// Persistence for the Google OAuth tokens (refresh + access) at\n// `~/.config/mulmo/google-token.json`, mode 600. Google omits\n// `refresh_token` from refresh responses, so merges must preserve the one we\n// already hold — losing it forces the user through the browser consent again.\nimport { constants as fsConstants, copyFile, mkdir, rm, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { Credentials } from \"google-auth-library\";\nimport { readJsonOrNull, writeJsonAtomicWithMode } from \"./fsJson.js\";\nimport { googleTokenPath, legacyGoogleTokenPath } from \"./paths.js\";\n\nconst TOKEN_FILE_MODE = 0o600;\n\n/** Which client minted the grant: `local` = the user's own client JSON in\n * `~/.secrets/`, `broker` = the mulmoserver broker. Renewal must use the same\n * path, since only that client's secret can refresh its tokens. Absent on\n * tokens written before the broker existed — those are all `local`. */\nexport type IssuedVia = \"local\" | \"broker\";\n\nexport interface StoredGoogleTokens extends Credentials {\n issuedVia?: IssuedVia;\n}\n\nexport function mergeGoogleTokens(existing: StoredGoogleTokens | null, incoming: StoredGoogleTokens): StoredGoogleTokens {\n const merged = { ...existing, ...incoming };\n if (!incoming.refresh_token && existing?.refresh_token) merged.refresh_token = existing.refresh_token;\n // A broker refresh response carries no marker; keep the one from the link.\n if (!incoming.issuedVia && existing?.issuedVia) merged.issuedVia = existing.issuedVia;\n return merged;\n}\n\nconst fileExists = async (filePath: string): Promise<boolean> =>\n await stat(filePath).then(\n () => true,\n () => false,\n );\n\n// Tokens written before 0.20.1 live under the mulmoclaude-branded dir; move\n// them once. COPYFILE_EXCL makes the create atomic-and-non-clobbering — an\n// exists+rename sequence could overwrite a token a concurrent process wrote\n// to the new path in between (TOCTOU). The legacy file is deleted only after\n// a successful copy; on EEXIST (new path won a race, or both files already\n// exist) it is left for any older install still reading it. copyFile\n// preserves the 600 mode.\nasync function migrateLegacyTokenFile(home?: string): Promise<void> {\n const current = googleTokenPath(home);\n const legacy = legacyGoogleTokenPath(home);\n if (!(await fileExists(legacy))) return;\n await mkdir(path.dirname(current), { recursive: true });\n try {\n await copyFile(legacy, current, fsConstants.COPYFILE_EXCL);\n } catch {\n return;\n }\n await rm(legacy, { force: true });\n}\n\nexport async function loadGoogleTokens(home?: string): Promise<StoredGoogleTokens | null> {\n await migrateLegacyTokenFile(home).catch(() => undefined);\n const current = await readJsonOrNull<StoredGoogleTokens>(googleTokenPath(home));\n if (current) return current;\n // Migration is best-effort — a valid legacy token must still count as\n // linked even when the move failed (permissions, read-only fs, …).\n return await readJsonOrNull<StoredGoogleTokens>(legacyGoogleTokenPath(home));\n}\n\nexport async function saveGoogleTokens(incoming: StoredGoogleTokens, home?: string): Promise<StoredGoogleTokens> {\n const merged = mergeGoogleTokens(await loadGoogleTokens(home), incoming);\n await writeJsonAtomicWithMode(googleTokenPath(home), merged, TOKEN_FILE_MODE);\n return merged;\n}\n\nexport async function deleteGoogleTokens(home?: string): Promise<void> {\n await rm(googleTokenPath(home), { force: true });\n}\n","// Client for the mulmoserver OAuth broker (receptron/mulmoserver#54).\n//\n// Why a broker: Google requires a client_secret at the token endpoint even for\n// PKCE flows, so a user without their own Cloud project cannot complete a link\n// on their own. The broker holds the secret and applies it — it never stores or\n// returns it, and tokens still live only on this machine.\n//\n// What proves this host started the flow is the PKCE code_verifier: anyone can\n// mint a `state` at /googleOAuthStart, so the verifier — not the state — is the\n// authorization. See the broker's pkce.ts for the same reasoning server-side.\nimport type { Credentials } from \"google-auth-library\";\nimport { fetchWithTimeout } from \"../utils/fetch.js\";\nimport { errorMessage, isRecord, ONE_SECOND_MS } from \"./util.js\";\n\nconst DEFAULT_BROKER_BASE_URL = \"https://asia-northeast1-mulmoserver.cloudfunctions.net\";\nconst BROKER_TIMEOUT_MS = 20 * ONE_SECOND_MS;\n\n// Trailing slashes are stripped by hand: `/\\/+$/` backtracks super-linearly on\n// a long run of slashes, which lint rejects.\nconst withoutTrailingSlashes = (url: string): string => {\n let end = url.length;\n while (end > 0 && url[end - 1] === \"/\") end -= 1;\n return url.slice(0, end);\n};\n\n/** `MULMO_GOOGLE_BROKER_URL` lets a fork / staging deploy point elsewhere\n * without a code change; unset means the shipped broker. */\nexport const brokerBaseUrl = (override: string | undefined = process.env.MULMO_GOOGLE_BROKER_URL): string =>\n withoutTrailingSlashes(override ?? DEFAULT_BROKER_BASE_URL);\n\nexport interface BrokerStartResponse {\n authUrl: string;\n state: string;\n}\n\nconst LOOPBACK_HOSTNAMES = new Set([\"127.0.0.1\", \"localhost\", \"::1\", \"[::1]\"]);\n\n/** Codes, PKCE verifiers and refresh tokens travel to the broker, so cleartext\n * is refused outright — an override pointed at `http://…` would put them on\n * the wire. Loopback stays allowed: that is where tests stub the broker, and\n * it never leaves the machine. */\nconst assertSecureBrokerUrl = (url: string): void => {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(`invalid Google sign-in service URL: ${url}`);\n }\n if (parsed.protocol === \"https:\") return;\n if (parsed.protocol === \"http:\" && LOOPBACK_HOSTNAMES.has(parsed.hostname)) return;\n throw new Error(`the Google sign-in service must be reached over HTTPS (got ${parsed.protocol}//${parsed.host})`);\n};\n\nconst brokerFetch = async (url: string, init: { method?: string; body?: string } = {}): Promise<unknown> => {\n assertSecureBrokerUrl(url);\n let response: Response;\n try {\n response = await fetchWithTimeout(url, {\n ...init,\n timeoutMs: BROKER_TIMEOUT_MS,\n headers: { \"Content-Type\": \"application/json\" },\n });\n } catch (err) {\n throw new Error(`Google sign-in service unreachable — check the network connection and retry (${errorMessage(err)})`);\n }\n if (!response.ok) {\n // The broker answers deliberately opaque errors (it must not help probe\n // codes / refresh tokens), so there is nothing more specific to surface.\n throw new Error(`Google sign-in service returned HTTP ${response.status}`);\n }\n try {\n return await response.json();\n } catch {\n // A proxy / captive portal answering 200 with HTML would otherwise surface\n // as a raw SyntaxError the agent can't act on.\n throw new Error(\"Google sign-in service returned a malformed response\");\n }\n};\n\nconst GOOGLE_CONSENT_HOST = \"accounts.google.com\";\n\n/** The returned URL is opened in the user's browser, so it must be Google's\n * own consent page and nothing else — a mis-pointed or hostile broker (the\n * base URL is overridable) could otherwise hand back a phishing page wearing\n * a sign-in flow. Cheap to assert, and Google's endpoint host is fixed. */\nconst assertGoogleConsentUrl = (authUrl: string): void => {\n let parsed: URL;\n try {\n parsed = new URL(authUrl);\n } catch {\n throw new Error(\"Google sign-in service returned an unusable authorization URL\");\n }\n if (parsed.protocol !== \"https:\" || parsed.hostname !== GOOGLE_CONSENT_HOST) {\n throw new Error(`Google sign-in service returned an authorization URL that is not Google's consent page (${parsed.protocol}//${parsed.host})`);\n }\n};\n\nexport async function brokerStart(port: number, codeChallenge: string, baseUrl = brokerBaseUrl()): Promise<BrokerStartResponse> {\n const params = new URLSearchParams({ port: String(port), code_challenge: codeChallenge });\n const payload = await brokerFetch(`${baseUrl}/googleOAuthStart?${params.toString()}`);\n const record = isRecord(payload) ? payload : {};\n if (typeof record.auth_url !== \"string\" || typeof record.state !== \"string\") {\n throw new Error(\"Google sign-in service returned an unexpected response (missing auth_url / state)\");\n }\n assertGoogleConsentUrl(record.auth_url);\n return { authUrl: record.auth_url, state: record.state };\n}\n\nconst toCredentials = (payload: unknown, existingRefreshToken?: string): Credentials => {\n const record = isRecord(payload) ? payload : {};\n if (typeof record.access_token !== \"string\") {\n throw new Error(\"Google sign-in service returned no access token\");\n }\n // The refresh endpoint does not echo the refresh_token back — keep the one we\n // already hold so a renewal can't silently unlink the account.\n const refreshToken = typeof record.refresh_token === \"string\" ? record.refresh_token : existingRefreshToken;\n return {\n access_token: record.access_token,\n ...(refreshToken ? { refresh_token: refreshToken } : {}),\n ...(typeof record.expiry_date === \"number\" ? { expiry_date: record.expiry_date } : {}),\n };\n};\n\nexport async function brokerExchange(input: { code: string; state: string; codeVerifier: string }, baseUrl = brokerBaseUrl()): Promise<Credentials> {\n const payload = await brokerFetch(`${baseUrl}/googleOAuthExchange`, {\n method: \"POST\",\n body: JSON.stringify({ code: input.code, state: input.state, code_verifier: input.codeVerifier }),\n });\n const credentials = toCredentials(payload);\n if (!credentials.refresh_token) {\n throw new Error(\"Google returned no refresh token — remove this app under Google Account → Security → Third-party access, then retry\");\n }\n return credentials;\n}\n\nexport async function brokerRefresh(refreshToken: string, baseUrl = brokerBaseUrl()): Promise<Credentials> {\n const payload = await brokerFetch(`${baseUrl}/googleOAuthRefresh`, {\n method: \"POST\",\n body: JSON.stringify({ refresh_token: refreshToken }),\n });\n return toCredentials(payload, refreshToken);\n}\n","// Google OAuth for the host machine, independent of Firebase Auth (which\n// discards refresh tokens). Two ways to reach a linked account:\n//\n// - LOCAL: the user dropped their own desktop-app client JSON in\n// `~/.secrets/`. Everything (consent, exchange, refresh) happens on this\n// machine — nothing but Google is contacted.\n// - BROKER (the default): no client JSON, so the mulmoserver broker applies\n// the client secret for the exchange / refresh it cannot be done without.\n// Tokens still only ever live here. See broker.ts.\n//\n// Either way the loopback listener, the PKCE verifier, and the token file are\n// this machine's. `issuedVia` on the stored token records which path minted it\n// so renewals take the matching one.\nimport { randomBytes } from \"node:crypto\";\nimport http from \"node:http\";\nimport { CodeChallengeMethod, OAuth2Client, type Credentials } from \"google-auth-library\";\nimport { brokerExchange, brokerRefresh, brokerStart } from \"./broker.js\";\nimport { log } from \"./host.js\";\nimport { errorMessage, ONE_MINUTE_MS, ONE_SECOND_MS } from \"./util.js\";\nimport { fetchWithTimeout } from \"../utils/fetch.js\";\nimport { clientSecretPresence, loadClientSecret, type InstalledClientSecret } from \"./clientSecret.js\";\nimport { deleteGoogleTokens, loadGoogleTokens, saveGoogleTokens, type IssuedVia } from \"./tokenStore.js\";\n\nexport const GOOGLE_CALENDAR_SCOPE = \"https://www.googleapis.com/auth/calendar.events\";\n/** Minimal read scope for the calendar list + per-calendar colours.\n * `calendar.events` already covers reading/writing events on any calendar, but\n * discovering WHICH calendars the user has (CalendarList.list) needs a\n * calendar-list read scope — this is the narrowest one that grants it. Kept in\n * step with the broker's consent scopes so local- and broker-linked accounts\n * grant the same set. */\nexport const GOOGLE_CALENDARLIST_SCOPE = \"https://www.googleapis.com/auth/calendar.calendarlist.readonly\";\nexport const GOOGLE_TASKS_SCOPE = \"https://www.googleapis.com/auth/tasks\";\nexport const GOOGLE_DRIVE_FILE_SCOPE = \"https://www.googleapis.com/auth/drive.file\";\n/** Requested at consent as one set — matches the scopes registered on the\n * OAuth consent screen, so a single re-link covers every supported API. */\nexport const GOOGLE_SCOPES = [GOOGLE_CALENDAR_SCOPE, GOOGLE_CALENDARLIST_SCOPE, GOOGLE_TASKS_SCOPE, GOOGLE_DRIVE_FILE_SCOPE];\nconst CALLBACK_PATH = \"/oauth2callback\";\nconst AUTH_TIMEOUT_MS = 5 * ONE_MINUTE_MS;\nconst STATE_BYTES = 16;\n/** Renew a minute early so a call can't start with a token that expires\n * mid-flight. */\nconst EXPIRY_MARGIN_MS = ONE_MINUTE_MS;\n\nexport interface AuthorizeGoogleOptions {\n home?: string;\n /** Called with the consent URL; open it in a browser (and/or print it). */\n onAuthUrl?: (url: string) => void;\n timeoutMs?: number;\n /** Abort a still-pending consent (a settings-UI restart, an explicit cancel).\n * Rejects the flow with {@link GOOGLE_AUTH_CANCELLED} and closes the loopback. */\n signal?: AbortSignal;\n}\n\nconst createClient = (secret: InstalledClientSecret, redirectUri?: string): OAuth2Client =>\n new OAuth2Client({ clientId: secret.client_id, clientSecret: secret.client_secret, ...(redirectUri !== undefined ? { redirectUri } : {}) });\n\nconst persistRotatedTokens = (client: OAuth2Client, home?: string): void => {\n client.on(\"tokens\", (tokens) => {\n saveGoogleTokens(tokens, home).catch((err: unknown) => {\n log.error(\"google\", \"failed to persist rotated tokens\", { error: String(err) });\n });\n });\n};\n\nconst REVOKED_GRANT_MESSAGE = \"could not obtain a Google access token — the grant may have been revoked; re-link the account\";\n\nconst localAccessToken = async (saved: Credentials, home?: string): Promise<string> => {\n const presence = await clientSecretPresence(home);\n // Two desktop clients: the token is still renewable — the engine just can't\n // tell which client minted it. The fix is to remove the duplicate, so raise\n // the loader's own wording rather than sending the user through a re-link.\n if (presence === \"ambiguous\") await loadClientSecret(home);\n // No client at all: the token is bound to the client_id that minted it, so\n // nothing — not even the broker — can renew it. Re-linking is the only way\n // out; the loader's \"no desktop client\" wording would read like a setup\n // problem instead.\n if (presence === \"missing\") {\n throw new Error(\n \"the saved Google link was created with an OAuth client that is no longer configured on this host — ask the user to link their Google account again in this app's settings\",\n );\n }\n const client = createClient(await loadClientSecret(home));\n client.setCredentials(saved);\n persistRotatedTokens(client, home);\n const { token } = await client.getAccessToken();\n if (!token) throw new Error(REVOKED_GRANT_MESSAGE);\n return token;\n};\n\n// The broker mints access tokens because only it holds the client secret. The\n// refreshed token is written back so the next call can reuse it until expiry\n// instead of hitting the broker every time.\nconst brokerAccessToken = async (saved: Credentials, home?: string): Promise<string> => {\n if (typeof saved.expiry_date === \"number\" && saved.access_token && saved.expiry_date - EXPIRY_MARGIN_MS > Date.now()) {\n return saved.access_token;\n }\n const refreshed = await brokerRefresh(saved.refresh_token ?? \"\");\n if (!refreshed.access_token) throw new Error(REVOKED_GRANT_MESSAGE);\n await saveGoogleTokens(refreshed, home);\n return refreshed.access_token;\n};\n\nexport async function getGoogleAccessToken(home?: string): Promise<string> {\n const saved = await loadGoogleTokens(home);\n if (!saved?.refresh_token) {\n // Host-neutral wording — this engine ships to multiple hosts whose link\n // flows differ (#2128); each host's own help carries the specific steps.\n throw new Error(\"Google account not linked on this host — ask the user to link their Google account in this app's settings, then retry\");\n }\n // Tokens written before the broker existed carry no marker; they were all\n // minted from a local client, so that stays the default.\n return saved.issuedVia === \"broker\" ? await brokerAccessToken(saved, home) : await localAccessToken(saved, home);\n}\n\nconst REVOKE_URL = \"https://oauth2.googleapis.com/revoke\";\n\n/** The revoke POST, injectable for tests. */\nexport type RevokeFetch = typeof fetchWithTimeout;\n\n/** Revoke the grant at Google (best-effort) and delete the local token file.\n * Revoke failures are logged but never block the local delete — Google may\n * already consider the token invalid, and keeping the file would leave the\n * user unable to unlink. */\nexport async function unlinkGoogle(home?: string, revokeFetch: RevokeFetch = fetchWithTimeout): Promise<void> {\n const saved = await loadGoogleTokens(home);\n const token = saved?.refresh_token ?? saved?.access_token;\n if (token) {\n try {\n const response = await revokeFetch(REVOKE_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({ token }).toString(),\n });\n if (!response.ok) log.warn(\"google\", \"token revoke returned non-ok\", { status: response.status });\n } catch (err) {\n log.warn(\"google\", \"token revoke failed, deleting local tokens anyway\", { error: errorMessage(err) });\n }\n }\n await deleteGoogleTokens(home);\n}\n\nconst startLoopbackServer = (): Promise<{ server: http.Server; port: number }> =>\n new Promise((resolve, reject) => {\n const server = http.createServer();\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => {\n const address = server.address();\n if (address === null || typeof address === \"string\") {\n reject(new Error(\"loopback server has no port\"));\n return;\n }\n resolve({ server, port: address.port });\n });\n });\n\n// State is validated before error/code — a callback that can't prove it\n// belongs to this flow must not influence it (its `error` text would\n// otherwise reach the terminal attacker-controlled).\nconst authCodeFromCallback = (url: URL, expectedState: string): string => {\n if (url.searchParams.get(\"state\") !== expectedState) throw new Error(\"OAuth state mismatch — possible CSRF, aborting\");\n const error = url.searchParams.get(\"error\");\n if (error) throw new Error(`Google authorization failed: ${error}`);\n const code = url.searchParams.get(\"code\");\n if (!code) throw new Error(\"authorization callback carried no code\");\n return code;\n};\n\nconst respondHtml = (res: http.ServerResponse, status: number, message: string): void => {\n res.writeHead(status, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(`<html><body><h3>${message}</h3></body></html>`);\n};\n\n/** Reject reason when a pending flow is torn down (a settings-UI restart or an\n * explicit cancel), kept distinct from a real authorization error so callers\n * can treat it as \"user changed their mind\", not \"linking failed\". */\nexport const GOOGLE_AUTH_CANCELLED = \"authorization cancelled\";\n\ninterface AuthCodeSettle {\n resolve: (code: string) => void;\n reject: (err: unknown) => void;\n}\n\n// Answers one loopback request. A terminal callback settles the flow; a\n// wrong-path or wrong-state request is answered but leaves the flow waiting —\n// a drive-by localhost probe or stale tab must not abort a pending consent.\nconst handleCallbackRequest = (req: http.IncomingMessage, res: http.ServerResponse, expectedState: string, settle: AuthCodeSettle): void => {\n const url = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n if (url.pathname !== CALLBACK_PATH) {\n res.writeHead(404);\n res.end();\n return;\n }\n if (url.searchParams.get(\"state\") !== expectedState) {\n respondHtml(res, 400, \"Invalid authorization callback. You can close this tab.\");\n return;\n }\n try {\n const code = authCodeFromCallback(url, expectedState);\n respondHtml(res, 200, \"Authorization complete — you can close this tab.\");\n settle.resolve(code);\n } catch (err) {\n // Static text only — the failure detail echoes query-string content, which\n // must not be reflected into HTML. The CLI prints the detail.\n respondHtml(res, 400, \"Authorization failed — see the terminal for details. You can close this tab.\");\n settle.reject(err);\n }\n};\n\n// Resolves with the OAuth code from the loopback callback, or rejects on the\n// timeout or on `signal` firing (a restart / cancel) — whichever comes first.\nexport const waitForAuthCode = (server: http.Server, expectedState: string, timeoutMs: number, signal?: AbortSignal): Promise<string> =>\n new Promise<string>((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error(`authorization timed out after ${timeoutMs / ONE_SECOND_MS}s`)), timeoutMs);\n const settle: AuthCodeSettle = {\n resolve: (code) => {\n clearTimeout(timer);\n resolve(code);\n },\n reject: (err) => {\n clearTimeout(timer);\n reject(err instanceof Error ? err : new Error(String(err)));\n },\n };\n if (signal) {\n if (signal.aborted) {\n settle.reject(new Error(GOOGLE_AUTH_CANCELLED));\n return;\n }\n signal.addEventListener(\"abort\", () => settle.reject(new Error(GOOGLE_AUTH_CANCELLED)), { once: true });\n }\n server.on(\"request\", (req, res) => handleCallbackRequest(req, res, expectedState, settle));\n });\n\n// `access_type: offline` + `prompt: consent` force Google to return a refresh\n// token on every run (repeat consents otherwise omit it).\nconst buildConsentUrl = (client: OAuth2Client, codeChallenge: string, state: string): string =>\n client.generateAuthUrl({\n access_type: \"offline\",\n prompt: \"consent\",\n scope: GOOGLE_SCOPES,\n code_challenge_method: CodeChallengeMethod.S256,\n code_challenge: codeChallenge,\n state,\n });\n\n// PKCE material is generated by an OAuth2Client with no credentials — the\n// helper is pure crypto, and in broker mode there is no client to construct.\nconst generatePkce = async (): Promise<{ codeVerifier: string; codeChallenge: string }> => {\n const { codeVerifier, codeChallenge } = await new OAuth2Client().generateCodeVerifierAsync();\n if (!codeChallenge) throw new Error(\"failed to derive a PKCE code challenge\");\n return { codeVerifier, codeChallenge };\n};\n\nconst authorizeWithLocalClient = async (\n secret: InstalledClientSecret,\n server: http.Server,\n port: number,\n opts: AuthorizeGoogleOptions,\n): Promise<Credentials> => {\n const client = createClient(secret, `http://127.0.0.1:${port}${CALLBACK_PATH}`);\n const { codeVerifier, codeChallenge } = await client.generateCodeVerifierAsync();\n if (!codeChallenge) throw new Error(\"failed to derive a PKCE code challenge\");\n const state = randomBytes(STATE_BYTES).toString(\"hex\");\n opts.onAuthUrl?.(buildConsentUrl(client, codeChallenge, state));\n const code = await waitForAuthCode(server, state, opts.timeoutMs ?? AUTH_TIMEOUT_MS, opts.signal);\n const { tokens } = await client.getToken({ code, codeVerifier });\n if (!tokens.refresh_token) {\n throw new Error(\"Google returned no refresh token — remove this app under Google Account → Security → Third-party access, then retry\");\n }\n return tokens;\n};\n\n// The broker signs `state` with a key it never releases, so it — not this\n// host — builds the authorization URL. `state` then round-trips through the\n// browser and comes back to our loopback, where waitForAuthCode matches it.\nconst authorizeWithBroker = async (server: http.Server, port: number, opts: AuthorizeGoogleOptions): Promise<Credentials> => {\n const { codeVerifier, codeChallenge } = await generatePkce();\n const { authUrl, state } = await brokerStart(port, codeChallenge);\n opts.onAuthUrl?.(authUrl);\n const code = await waitForAuthCode(server, state, opts.timeoutMs ?? AUTH_TIMEOUT_MS, opts.signal);\n return await brokerExchange({ code, state, codeVerifier });\n};\n\n// Persists the freshly linked tokens — unless a restart/cancel aborted this run\n// after the code came back. `waitForAuthCode` only guards the wait; the token\n// exchange and this save run past it, so an abandoned flow could otherwise\n// clobber the link the user actually kept. This re-checks the signal right\n// before the one persistent side effect.\nexport const commitLinkedTokens = async (tokens: Credentials, issuedVia: IssuedVia, opts: AuthorizeGoogleOptions): Promise<Credentials> => {\n if (opts.signal?.aborted) throw new Error(GOOGLE_AUTH_CANCELLED);\n return await saveGoogleTokens({ ...tokens, issuedVia }, opts.home);\n};\n\nexport async function authorizeGoogle(opts: AuthorizeGoogleOptions = {}): Promise<Credentials> {\n // A user-supplied client wins: it keeps the whole flow on this machine, and\n // silently preferring the broker would ignore a deliberate setup. Two of\n // them is unresolvable rather than a reason to fall back — the user meant to\n // use one of theirs, and a broker link would quietly not be it.\n const presence = await clientSecretPresence(opts.home);\n if (presence === \"ambiguous\") {\n // Same wording the loader raises, so the CLI and the settings UI (which\n // disables linking in this state) agree on the fix.\n await loadClientSecret(opts.home);\n }\n const useLocalClient = presence === \"found\";\n const { server, port } = await startLoopbackServer();\n try {\n const issuedVia: IssuedVia = useLocalClient ? \"local\" : \"broker\";\n const tokens = useLocalClient\n ? await authorizeWithLocalClient(await loadClientSecret(opts.home), server, port, opts)\n : await authorizeWithBroker(server, port, opts);\n return await commitLinkedTokens(tokens, issuedVia, opts);\n } finally {\n server.close();\n }\n}\n","// In-flight manager for the settings-UI OAuth flow. authorizeGoogle() resolves\n// only after the user finishes the browser consent, so the HTTP layer starts it\n// in the background, returns the consent URL immediately, and reports progress\n// via status polling. start() always reflects the latest intent: a new call\n// aborts any pending flow (which closes its loopback listener) before launching\n// a fresh one, so a user who abandoned the browser consent can just link again\n// instead of waiting out the server-side timeout.\nimport { log } from \"./host.js\";\nimport { errorMessage } from \"./util.js\";\nimport { authorizeGoogle } from \"./auth.js\";\n\nexport interface GoogleAuthFlowStatus {\n pending: boolean;\n lastError: string | null;\n}\n\nexport interface GoogleAuthFlow {\n start: () => Promise<{ authUrl: string }>;\n cancel: () => void;\n status: () => GoogleAuthFlowStatus;\n}\n\nexport const createGoogleAuthFlow = (authorize: typeof authorizeGoogle): GoogleAuthFlow => {\n let flowRunning = false;\n let lastError: string | null = null;\n let active: AbortController | null = null;\n\n const launch = (): Promise<{ authUrl: string }> => {\n const controller = new AbortController();\n active = controller;\n flowRunning = true;\n return new Promise((resolve, reject) => {\n authorize({ onAuthUrl: (url) => resolve({ authUrl: url }), signal: controller.signal })\n .then(() => log.info(\"google\", \"authorize flow completed\"))\n .catch((err: unknown) => {\n // An aborted flow is a user-initiated restart, not a failure to\n // surface — only real errors reach lastError.\n if (!controller.signal.aborted) {\n lastError = errorMessage(err);\n log.warn(\"google\", \"authorize flow failed\", { error: lastError });\n }\n reject(err instanceof Error ? err : new Error(String(err)));\n })\n .finally(() => {\n // A restart may have already installed a newer controller; only the\n // current flow clears the shared state so it can't strand the new one.\n if (active === controller) {\n flowRunning = false;\n active = null;\n }\n });\n });\n };\n\n const cancel = (): void => {\n active?.abort();\n };\n\n const start = (): Promise<{ authUrl: string }> => {\n cancel();\n lastError = null;\n return launch();\n };\n\n const status = (): GoogleAuthFlowStatus => ({ pending: flowRunning, lastError });\n\n return { start, cancel, status };\n};\n\nexport const googleAuthFlow = createGoogleAuthFlow(authorizeGoogle);\n","// Shared REST plumbing for the Google APIs this engine wraps (Calendar,\n// Tasks, Drive). Plain fetch instead of the `googleapis` SDK — a handful of\n// endpoints don't justify the dependency (see\n// plans/done/feat-google-oauth-calendar.md).\nimport { errorMessage, isRecord, ONE_SECOND_MS, truncate } from \"./util.js\";\nimport { fetchWithTimeout } from \"../utils/fetch.js\";\n\nexport const GOOGLE_API_TIMEOUT_MS = 30 * ONE_SECOND_MS;\nconst ERROR_BODY_MAX_CHARS = 300;\nexport const HTTP_FORBIDDEN = 403;\nexport const DEFAULT_LIST_MAX_RESULTS = 10;\nexport const MAX_LIST_RESULTS = 50;\n\n/** Carries the HTTP status so callers can branch on it (e.g. an expired\n * calendar syncToken answers 410) without parsing the message. */\nexport class GoogleApiError extends Error {\n readonly status: number;\n constructor(status: number, message: string) {\n super(message);\n this.name = \"GoogleApiError\";\n this.status = status;\n }\n}\n\nexport const isGoogleApiError = (value: unknown): value is GoogleApiError => value instanceof GoogleApiError;\n\n/** 403 usually means the API is not enabled for the user's Cloud project —\n * name the API so the agent's recovery guidance can be specific. */\nexport const googleApiError = (apiLabel: string, status: number, body: string): GoogleApiError => {\n const hint = status === HTTP_FORBIDDEN ? ` (is the ${apiLabel} enabled for the Cloud project?)` : \"\";\n const detail = body ? ` — ${truncate(body, ERROR_BODY_MAX_CHARS)}` : \"\";\n return new GoogleApiError(status, `${apiLabel}: HTTP ${status}${hint}${detail}`);\n};\n\nexport interface GoogleRequestInit {\n method?: string;\n body?: string;\n /** Overrides the default JSON content type (multipart upload, …). */\n contentType?: string;\n /** Response is not JSON (Drive media download) — return the raw text. */\n expectText?: boolean;\n /** Extra request headers, merged after the defaults. Used for conditional\n * writes (`If-Match: <etag>`), which is how a caller makes a PATCH fail with\n * 412 instead of silently overwriting a newer version. Cannot replace\n * `Authorization`. */\n extraHeaders?: Record<string, string>;\n}\n\nexport async function googleRequest(apiLabel: string, accessToken: string, url: string, init: GoogleRequestInit = {}): Promise<unknown> {\n const { contentType = \"application/json\", expectText = false, extraHeaders, ...rest } = init;\n const response = await fetchWithTimeout(url, {\n ...rest,\n timeoutMs: GOOGLE_API_TIMEOUT_MS,\n headers: { ...extraHeaders, Authorization: `Bearer ${accessToken}`, \"Content-Type\": contentType },\n });\n if (!response.ok) {\n const body = await response.text().catch((err: unknown) => errorMessage(err));\n throw googleApiError(apiLabel, response.status, body);\n }\n if (expectText) return await response.text();\n // 204 (Drive delete, …) has no body to parse.\n if (response.status === 204) return {};\n return await response.json();\n}\n\nexport const stringField = (record: Record<string, unknown>, key: string): string => (typeof record[key] === \"string\" ? record[key] : \"\");\n\nexport const asRecord = (value: unknown): Record<string, unknown> => (isRecord(value) ? value : {});\n\nexport const itemsOf = (value: unknown): unknown[] => {\n const record = asRecord(value);\n return Array.isArray(record.items) ? record.items : [];\n};\n","// Google Calendar v3 REST calls. Events read/write against any calendar the\n// user can access (default: their primary); the calendar list and colour\n// palette let callers show non-primary calendars and their colours.\nimport { asRecord, googleApiError, googleRequest, isGoogleApiError, itemsOf, stringField, DEFAULT_LIST_MAX_RESULTS } from \"./apiClient.js\";\nimport { isRecord } from \"./util.js\";\n\nconst CALENDAR_BASE_URL = \"https://www.googleapis.com/calendar/v3\";\nconst CALENDAR_API_LABEL = \"Google Calendar API\";\nconst DEFAULT_CALENDAR_ID = \"primary\";\n// CalendarList.list is paginated; page through nextPageToken so an account\n// with many subscribed calendars isn't silently truncated. The page cap is a\n// runaway guard — 250 * 40 = 10k calendars, far beyond any real account.\nconst CALENDAR_LIST_PAGE_SIZE = 250;\nconst MAX_CALENDAR_LIST_PAGES = 40;\n// Events.list caps maxResults at 2500. The page cap is a runaway guard —\n// 2500 * 200 = 500k events, far beyond any real calendar's history.\nconst EVENT_SYNC_PAGE_SIZE = 2500;\nconst MAX_EVENT_SYNC_PAGES = 200;\n// `getCalendarMeta` reads the list envelope, never the events in it. Google's\n// accepted range for maxResults starts at 1, so this is the smallest page it\n// will serve.\nconst CALENDAR_META_PAGE_SIZE = 1;\n// An expired/invalidated calendar syncToken.\nconst HTTP_GONE = 410;\nconst HTTP_NOT_FOUND = 404;\n// A GET on an event that is not there: 404 when it never existed on this\n// calendar, 410 when it was deleted and the tombstone is no longer served.\nconst EVENT_ABSENT_STATUSES: readonly number[] = [HTTP_NOT_FOUND, HTTP_GONE];\n/** An `If-Match` write whose etag no longer matches — someone else changed the\n * event since it was read. */\nexport const HTTP_PRECONDITION_FAILED = 412;\n/** An `insert` whose caller-supplied event id is already taken. */\nexport const HTTP_CONFLICT = 409;\n/** Google's status for an event that was deleted; a sync window reports it so\n * consumers can remove their copy. Single-sourced — the pull and the push both\n * branch on it. */\nexport const CANCELLED_EVENT_STATUS = \"cancelled\";\n\n/** Resolve a declared calendarId to the one the API and the sync-token store\n * both address. `||` (not `??`) so an empty string also falls back instead of\n * building a malformed `/calendars//events` URL. Single-sourced because an\n * omitted id and an explicit \"primary\" MUST agree everywhere — grouping them\n * apart while they share a sync token silently loses events (#2184). */\nexport const canonicalCalendarId = (calendarId: string | undefined): string => calendarId || DEFAULT_CALENDAR_ID;\n\nconst eventsUrl = (calendarId: string | undefined): string => `${CALENDAR_BASE_URL}/calendars/${encodeURIComponent(canonicalCalendarId(calendarId))}/events`;\n\n/** One end of an event as the API expresses it: a timed instant, or a whole day.\n *\n * `timeZone` names the zone a `dateTime` WITHOUT an offset is read in — the\n * shape a collection push needs, since the stored clock carries no offset\n * (`pushDateTime.ts`). A `dateTime` that already ends in `Z`/`+09:00` needs no\n * `timeZone`. `date` is an all-day event, whose `end` Google treats as\n * EXCLUSIVE. */\nexport type CalendarEventTime = { dateTime: string; timeZone?: string } | { date: string };\n\n/** The two ways to state an event's span. The flat pair is what the `google`\n * tool passes (RFC3339 with an offset); the structured pair is what a push\n * needs to express an all-day event or an explicit zone. Expressed as a union\n * so \"neither given\" cannot typecheck on a create. */\nexport type CalendarEventSpan = { startDateTime: string; endDateTime: string } | { start: CalendarEventTime; end: CalendarEventTime };\n\nexport type CalendarEventInput = {\n summary: string;\n description?: string | undefined;\n location?: string | undefined;\n /** Calendar to create the event on; defaults to the user's primary. */\n calendarId?: string | undefined;\n /** Event colour (Google event palette id \"1\"..\"11\"); omit to inherit the calendar's colour. */\n colorId?: string | undefined;\n /** Caller-chosen event id. Google requires base32hex (`0-9a-v`), 5-1024 chars,\n * and answers 409 when it is already taken. A collection push sets this so a\n * locally-created record keeps its own record id as the event id, instead of\n * being re-keyed to Google's after the fact — a re-key that is missed leaves\n * a duplicate record on the next pull. */\n eventId?: string | undefined;\n} & CalendarEventSpan;\n\nexport interface UpdateCalendarEventInput {\n eventId: string;\n summary?: string | undefined;\n startDateTime?: string | undefined;\n endDateTime?: string | undefined;\n /** Structured span; wins over the flat pair when both are present. */\n start?: CalendarEventTime | undefined;\n end?: CalendarEventTime | undefined;\n /** `\"\"` clears the description; omit to leave it untouched. */\n description?: string | undefined;\n /** `\"\"` clears the location; omit to leave it untouched. */\n location?: string | undefined;\n calendarId?: string | undefined;\n colorId?: string | undefined;\n /** Etag of the version this edit was computed against. Sent as `If-Match`, so\n * Google answers 412 rather than letting the PATCH clobber a change that\n * landed after the caller read the event. Omit for an unconditional write. */\n ifMatch?: string | undefined;\n}\n\nexport interface DeleteCalendarEventInput {\n eventId: string;\n calendarId?: string | undefined;\n}\n\nexport interface ListEventsInput {\n timeMin?: string | undefined;\n maxResults?: number | undefined;\n /** Calendar to read; defaults to the user's primary. */\n calendarId?: string | undefined;\n}\n\nexport interface CalendarEventSummary {\n id: string;\n summary: string;\n start: string;\n end: string;\n htmlLink: string;\n status: string;\n /** Google event palette id (\"1\"..\"11\"), or \"\" when the event inherits the calendar's colour. */\n colorId: string;\n /** The event body, RAW. Google stores limited HTML here, and normalising it on\n * the way in would silently strip the user's formatting on the way back out —\n * a mirrored calendar has to survive the round trip, so any prettifying\n * belongs to whatever displays it. */\n description: string;\n location: string;\n /** The series an expanded instance belongs to, \"\" for a one-off. The sync\n * expands recurrences, so a weekly meeting arrives once per occurrence; with\n * no key pointing back, one edit to the series is indistinguishable from a\n * batch of unrelated changes. */\n recurringEventId: string;\n /** Where an expanded instance sat BEFORE anyone moved it, so a dragged\n * occurrence reads as a move rather than a delete plus an insert. Flattened\n * to the instant like `start`/`end`, so an all-day series yields the date. */\n originalStartTime: string;\n /** Google's last-modified time (RFC3339). Google sets it on every write, so\n * it is readable and never pushable. */\n updated: string;\n /** \"transparent\" when the event does not consume the attendee's time. Google\n * omits the default, so \"\" reads as opaque. */\n transparency: string;\n /** Which KIND of entry this is, and how the ones Google generates get kept out\n * of a mirror. Google's whole set: \"default\", \"birthday\", \"focusTime\",\n * \"fromGmail\", \"outOfOffice\", \"workingLocation\". */\n eventType: string;\n /** Google Meet URL, \"\" when nothing is attached. */\n hangoutLink: string;\n}\n\nexport interface CalendarSummary {\n id: string;\n summary: string;\n description: string;\n /** True only for the user's primary calendar. */\n primary: boolean;\n accessRole: string;\n backgroundColor: string;\n foregroundColor: string;\n /** Calendar palette id backing background/foregroundColor. */\n colorId: string;\n /** IANA zone (`Asia/Tokyo`). The zone a pushed `dateTime` without an offset is\n * read in: the collection stores the clock the user reads off THIS calendar,\n * so this is the only correct zone to re-attach. `\"\"` when Google omits it. */\n timeZone: string;\n}\n\nexport interface CalendarColorEntry {\n background: string;\n foreground: string;\n}\n\n/** Palettes from Google's `/colors` endpoint: `event` maps an event's `colorId`\n * and `calendar` maps a calendar's `colorId` to hex background/foreground. */\nexport interface CalendarColors {\n event: Record<string, CalendarColorEntry>;\n calendar: Record<string, CalendarColorEntry>;\n}\n\n// All-day events carry `date`, timed events carry `dateTime`.\nconst eventTime = (value: unknown): string => {\n if (!isRecord(value)) return \"\";\n if (typeof value.dateTime === \"string\") return value.dateTime;\n if (typeof value.date === \"string\") return value.date;\n return \"\";\n};\n\nexport const toEventSummary = (value: unknown): CalendarEventSummary => {\n const record = asRecord(value);\n return {\n id: stringField(record, \"id\"),\n summary: stringField(record, \"summary\"),\n start: eventTime(record.start),\n end: eventTime(record.end),\n htmlLink: stringField(record, \"htmlLink\"),\n status: stringField(record, \"status\"),\n colorId: stringField(record, \"colorId\"),\n description: stringField(record, \"description\"),\n location: stringField(record, \"location\"),\n recurringEventId: stringField(record, \"recurringEventId\"),\n originalStartTime: eventTime(record.originalStartTime),\n updated: stringField(record, \"updated\"),\n transparency: stringField(record, \"transparency\"),\n eventType: stringField(record, \"eventType\"),\n hangoutLink: stringField(record, \"hangoutLink\"),\n };\n};\n\nexport const toCalendarSummary = (value: unknown): CalendarSummary => {\n const record = asRecord(value);\n return {\n id: stringField(record, \"id\"),\n summary: stringField(record, \"summary\"),\n description: stringField(record, \"description\"),\n primary: record.primary === true,\n accessRole: stringField(record, \"accessRole\"),\n backgroundColor: stringField(record, \"backgroundColor\"),\n foregroundColor: stringField(record, \"foregroundColor\"),\n colorId: stringField(record, \"colorId\"),\n timeZone: stringField(record, \"timeZone\"),\n };\n};\n\nconst toColorMap = (value: unknown): Record<string, CalendarColorEntry> => {\n const entries = Object.entries(asRecord(value)).map(([colorId, entry]): [string, CalendarColorEntry] => {\n const record = asRecord(entry);\n return [colorId, { background: stringField(record, \"background\"), foreground: stringField(record, \"foreground\") }];\n });\n return Object.fromEntries(entries);\n};\n\n/** Kept as a named export for the existing unit tests / callers; the shared\n * helper now carries the wording. */\nexport const calendarApiError = (status: number, body: string): Error => googleApiError(CALENDAR_API_LABEL, status, body);\n\n/** Normalise either spelling of a span into the pair the API body carries. */\nexport const resolveEventSpan = (span: CalendarEventSpan): { start: CalendarEventTime; end: CalendarEventTime } =>\n \"start\" in span ? { start: span.start, end: span.end } : { start: { dateTime: span.startDateTime }, end: { dateTime: span.endDateTime } };\n\nexport async function createCalendarEvent(accessToken: string, input: CalendarEventInput): Promise<CalendarEventSummary> {\n const body = {\n summary: input.summary,\n description: input.description,\n location: input.location,\n ...resolveEventSpan(input),\n ...(input.colorId ? { colorId: input.colorId } : {}),\n ...(input.eventId ? { id: input.eventId } : {}),\n };\n const created = await googleRequest(CALENDAR_API_LABEL, accessToken, eventsUrl(input.calendarId), { method: \"POST\", body: JSON.stringify(body) });\n return toEventSummary(created);\n}\n\nconst eventUrl = (calendarId: string | undefined, eventId: string): string => `${eventsUrl(calendarId)}/${encodeURIComponent(eventId)}`;\n\n/** PATCH body for an event edit — only the fields the caller actually supplied.\n *\n * `undefined` means \"leave as is\" and `\"\"` means \"clear it\", so the two cannot\n * be collapsed: dropping `description: \"\"` would silently ignore a request to\n * empty the body text. Pure so the distinction is testable without network. */\n/** One end of the patch: the structured value if given, else the flat one\n * wrapped, else absent (= leave it alone). */\nconst eventEnd = (key: \"start\" | \"end\", structured: CalendarEventTime | undefined, flat: string | undefined): Record<string, CalendarEventTime> => {\n if (structured !== undefined) return { [key]: structured };\n return flat !== undefined ? { [key]: { dateTime: flat } } : {};\n};\n\nexport const buildEventPatch = (input: UpdateCalendarEventInput): Record<string, unknown> => ({\n ...(input.summary !== undefined ? { summary: input.summary } : {}),\n ...(input.description !== undefined ? { description: input.description } : {}),\n ...(input.location !== undefined ? { location: input.location } : {}),\n ...eventEnd(\"start\", input.start, input.startDateTime),\n ...eventEnd(\"end\", input.end, input.endDateTime),\n ...(input.colorId ? { colorId: input.colorId } : {}),\n});\n\n/** Edit an existing event in place. PATCH, not PUT — a PUT would need the whole\n * event and would drop every field the caller never read (attendees,\n * reminders, recurrence). */\nexport async function updateCalendarEvent(accessToken: string, input: UpdateCalendarEventInput): Promise<CalendarEventSummary> {\n const url = eventUrl(input.calendarId, input.eventId);\n const updated = await googleRequest(CALENDAR_API_LABEL, accessToken, url, {\n method: \"PATCH\",\n body: JSON.stringify(buildEventPatch(input)),\n ...(input.ifMatch ? { extraHeaders: { \"If-Match\": input.ifMatch } } : {}),\n });\n return toEventSummary(updated);\n}\n\n/** An event plus the version token a conditional write needs. */\nexport interface FetchedCalendarEvent {\n event: CalendarEventSummary;\n /** Google's `etag` for this version, `\"\"` when absent. Sent back as\n * `If-Match` so a PATCH cannot overwrite a version we never read. */\n etag: string;\n}\n\n/** Read ONE event, or null when it is gone (404 / 410).\n *\n * A push needs Google's current value per changed record to tell a local-only\n * edit from a both-sides conflict. Deliberately not `syncCalendarEvents`: that\n * consumes the calendar's sync token, and the token's window is served once —\n * spending it here would make the next pull miss everything in it. */\nexport async function getCalendarEvent(accessToken: string, input: DeleteCalendarEventInput): Promise<FetchedCalendarEvent | null> {\n try {\n const fetched = await googleRequest(CALENDAR_API_LABEL, accessToken, eventUrl(input.calendarId, input.eventId));\n return { event: toEventSummary(fetched), etag: stringField(asRecord(fetched), \"etag\") };\n } catch (error) {\n if (isGoogleApiError(error) && EVENT_ABSENT_STATUSES.includes(error.status)) return null;\n throw error;\n }\n}\n\n/** Remove an event. Google answers 204 with no body, so there is nothing to\n * return; a second delete of the same id answers 410, which surfaces as a\n * GoogleApiError rather than being swallowed. */\nexport async function deleteCalendarEvent(accessToken: string, input: DeleteCalendarEventInput): Promise<void> {\n await googleRequest(CALENDAR_API_LABEL, accessToken, eventUrl(input.calendarId, input.eventId), { method: \"DELETE\" });\n}\n\nexport async function listCalendarEvents(accessToken: string, input: ListEventsInput = {}): Promise<CalendarEventSummary[]> {\n const params = new URLSearchParams({\n timeMin: input.timeMin ?? new Date().toISOString(),\n maxResults: String(input.maxResults ?? DEFAULT_LIST_MAX_RESULTS),\n singleEvents: \"true\",\n orderBy: \"startTime\",\n });\n const listed = await googleRequest(CALENDAR_API_LABEL, accessToken, `${eventsUrl(input.calendarId)}?${params.toString()}`);\n return itemsOf(listed).map(toEventSummary);\n}\n\nexport interface SyncEventsInput {\n /** Calendar to sync; defaults to the user's primary. */\n calendarId?: string | undefined;\n /** Token from the previous sync. Omit for a full sync. */\n syncToken?: string | undefined;\n /** Page size for the underlying list calls. */\n maxResults?: number | undefined;\n}\n\nexport interface CalendarSyncResult {\n /** Changed events since `syncToken` (all of them on a full sync). Deletions\n * arrive here too, as `status: \"cancelled\"`. */\n events: CalendarEventSummary[];\n /** Token to pass to the NEXT sync. Absent only if Google omitted it. */\n nextSyncToken?: string | undefined;\n /** The stored token had expired (410) — the caller must drop it and re-sync\n * from scratch; no events are returned in that case. */\n fullResyncRequired: boolean;\n /** The page guard fired with pages still pending, so `events` is a PARTIAL\n * window and `nextSyncToken` is absent.\n *\n * Reported rather than left implicit because a truncated walk is otherwise\n * byte-identical to a completed one: the caller would apply the partial set,\n * report success, and — since only Google's last page carries the token —\n * repeat the same truncated walk on every later run, silently (#2850).\n * Google states a page \"may be less than this value, or none at all, even if\n * there are more events matching the query\", so this is reachable on a real\n * calendar, the more so with `singleEvents` expanding unbounded recurrences. */\n pagesExhausted: boolean;\n}\n\n// Sentinel for \"the syncToken expired\" so the page loop can bail without\n// throwing — 410 is an expected, recoverable state, not a failure.\nconst GONE = Symbol(\"calendar-sync-gone\");\n\nasync function fetchSyncPage(accessToken: string, calendarId: string | undefined, params: URLSearchParams): Promise<unknown> {\n try {\n return await googleRequest(CALENDAR_API_LABEL, accessToken, `${eventsUrl(calendarId)}?${params.toString()}`);\n } catch (err: unknown) {\n if (isGoogleApiError(err) && err.status === HTTP_GONE) return GONE;\n throw err;\n }\n}\n\n/** Incremental sync over the events of one calendar (#2095).\n *\n * Deliberately separate from `listCalendarEvents`: Google forbids combining\n * `syncToken` with `timeMin` / `timeMax` / `updatedMin` / `orderBy` / `q`,\n * and that function always sends `timeMin` + `orderBy`. So this one sends\n * neither — a sync covers the WHOLE calendar and the caller sorts / windows\n * client-side. `showDeleted` must stay true or deletions would be invisible.\n *\n * `nextSyncToken` is only present on the LAST page, so every page must be\n * walked before the token is worth storing. */\n/** The query for one page of the walk. `showDeleted` must stay true or\n * deletions would be invisible; `pageToken` is sent ALONGSIDE `syncToken`,\n * which is what Google requires to page an incremental sync. */\nexport function syncPageParams(input: SyncEventsInput, pageToken: string | undefined): URLSearchParams {\n const params = new URLSearchParams({\n singleEvents: \"true\",\n showDeleted: \"true\",\n maxResults: String(input.maxResults ?? EVENT_SYNC_PAGE_SIZE),\n });\n if (input.syncToken) params.set(\"syncToken\", input.syncToken);\n if (pageToken) params.set(\"pageToken\", pageToken);\n return params;\n}\n\nexport async function syncCalendarEvents(accessToken: string, input: SyncEventsInput = {}): Promise<CalendarSyncResult> {\n const events: CalendarEventSummary[] = [];\n let pageToken: string | undefined;\n let nextSyncToken: string | undefined;\n\n for (let page = 0; page < MAX_EVENT_SYNC_PAGES; page += 1) {\n const payload = await fetchSyncPage(accessToken, input.calendarId, syncPageParams(input, pageToken));\n if (payload === GONE) return { events: [], fullResyncRequired: true, pagesExhausted: false };\n\n const record = asRecord(payload);\n events.push(...itemsOf(payload).map(toEventSummary));\n nextSyncToken = stringField(record, \"nextSyncToken\") || undefined;\n pageToken = stringField(record, \"nextPageToken\") || undefined;\n if (!pageToken) break;\n // Google sends the sync token only on the LAST page, so anything read\n // before one is not a resume point for the walk this call is doing.\n // Dropped rather than trusted: kept, it would let a caller resume past the\n // pages this walk never read (Codex review #2853).\n nextSyncToken = undefined;\n }\n\n // A token still in hand means the loop ran out of pages, not out of calendar.\n return { events, nextSyncToken, fullResyncRequired: false, pagesExhausted: pageToken !== undefined };\n}\n\nexport interface CalendarListPage {\n items: unknown[];\n nextPageToken?: string | undefined;\n}\n\n/** Pagination loop for CalendarList.list, extracted so it can be tested without\n * network. Stops at the last page (no token) or the runaway page cap. */\nexport async function collectCalendarPages(\n fetchPage: (pageToken?: string) => Promise<CalendarListPage>,\n maxPages = MAX_CALENDAR_LIST_PAGES,\n): Promise<CalendarSummary[]> {\n const calendars: CalendarSummary[] = [];\n let pageToken: string | undefined;\n for (let page = 0; page < maxPages; page += 1) {\n const { items, nextPageToken } = await fetchPage(pageToken);\n calendars.push(...items.map(toCalendarSummary));\n if (!nextPageToken) break;\n pageToken = nextPageToken;\n }\n return calendars;\n}\n\n/** What a calendar absent from the user's list can still tell about itself. */\nexport interface CalendarMeta {\n /** IANA zone (`Asia/Tokyo`), `\"\"` when Google omits it. */\n timeZone: string;\n /** `owner` / `writer` / `reader` / `freeBusyReader`, `\"\"` when unreported. */\n accessRole: string;\n}\n\nexport const toCalendarMeta = (value: unknown): CalendarMeta => {\n const record = asRecord(value);\n return { timeZone: stringField(record, \"timeZone\"), accessRole: stringField(record, \"accessRole\") };\n};\n\n/** One calendar's zone and the caller's role on it, read by id rather than\n * looked up in the user's list.\n *\n * `calendarList` only holds calendars the user has ADDED; a calendar shared\n * with them can be readable and writable by id without appearing there, and a\n * push still needs its zone to send an offset-less `dateTime`.\n *\n * Deliberately `events.list` and NOT `calendars.get`: the latter takes only\n * full-calendar scopes (`calendar`, `calendar.readonly`, `calendar.calendars*`)\n * and this app asks for none of them, so it answers 403 for every account it\n * links (#2735). The events list carries the same `timeZone` — plus the\n * `accessRole` the calendar resource does not have — under `calendar.events`,\n * which the push already holds because it writes with it. Only the envelope is\n * read; the one event asked for is the smallest page Google will serve. */\nexport async function getCalendarMeta(accessToken: string, calendarId: string | undefined): Promise<CalendarMeta> {\n const params = new URLSearchParams({ maxResults: String(CALENDAR_META_PAGE_SIZE) });\n return toCalendarMeta(await googleRequest(CALENDAR_API_LABEL, accessToken, `${eventsUrl(calendarId)}?${params.toString()}`));\n}\n\n/** The calendars the user has added/subscribed to (primary + secondary +\n * shared), each with its id, name and colour, following pagination. Needs the\n * calendar-list read scope (GOOGLE_CALENDARLIST_SCOPE). */\nexport async function listCalendars(accessToken: string): Promise<CalendarSummary[]> {\n return collectCalendarPages(async (pageToken) => {\n const params = new URLSearchParams({ maxResults: String(CALENDAR_LIST_PAGE_SIZE) });\n if (pageToken) params.set(\"pageToken\", pageToken);\n const payload = await googleRequest(CALENDAR_API_LABEL, accessToken, `${CALENDAR_BASE_URL}/users/me/calendarList?${params.toString()}`);\n const record = asRecord(payload);\n return { items: itemsOf(payload), nextPageToken: typeof record.nextPageToken === \"string\" ? record.nextPageToken : undefined };\n });\n}\n\n/** Resolve a `colorId` (on an event or calendar) to its hex background/foreground. */\nexport async function getCalendarColors(accessToken: string): Promise<CalendarColors> {\n const payload = await googleRequest(CALENDAR_API_LABEL, accessToken, `${CALENDAR_BASE_URL}/colors`);\n const record = asRecord(payload);\n return { event: toColorMap(record.event), calendar: toColorMap(record.calendar) };\n}\n","// Cross-process exclusion for the calendar state files (#2679).\n//\n// `.sync-state.json` and `.push-state.json` are both read-modify-write over a\n// whole file. Inside one process a write queue orders that; between processes\n// nothing did, so two hosts sharing a workspace could each read the same\n// snapshot and the later write would drop the earlier one's entry.\n//\n// Scoped to the file mutation, NOT to the sync run. The mutation is held for\n// milliseconds, which is what makes this cheap: no \"another host is busy\"\n// result to thread through the routes, and a stale lock can be reclaimed on a\n// short timer instead of needing a heartbeat sized against a multi-minute full\n// walk.\n//\n// What it does NOT do: stop two hosts walking the same calendar at once. That\n// wastes API calls, but the records are upserts and the baseline is now safe,\n// so nothing is lost by it.\nimport { hasStringProp } from \"@mulmoclaude/common\";\nimport { open, readFile, stat, unlink } from \"node:fs/promises\";\nimport { randomUUID } from \"node:crypto\";\nimport { log } from \"./host.js\";\n\nconst LOCK_MODE = 0o600;\n/** How long a lock may sit untouched before another process may reclaim it.\n *\n * Sized against the HOLD time, which is one read plus one atomic write — a few\n * milliseconds. Generous by three orders of magnitude so a slow disk never\n * looks like a crash, while a host that died mid-write blocks the others for\n * at most this long. */\nconst LOCK_STALE_MS = 10_000;\n/** How long a caller waits before giving up and proceeding unlocked. */\nconst LOCK_WAIT_MS = 5_000;\nconst LOCK_RETRY_MS = 20;\n\n/** Deliberately just the holder. There is no timestamp in here: an age written\n * by one host and read by another is two different wall clocks compared as if\n * they were one, which breaks staleness in BOTH directions — a live lock\n * reclaimed early, or a dead one that never expires (Codex review #2690). The\n * filesystem's own mtime is the single authority instead. */\ninterface LockFile {\n /** Not a secret — it only has to be unique per acquisition, so that a release\n * removes its OWN lock and never the one that replaced it. */\n holder: string;\n}\n\n/** The clock and the sleep, injected so the timing rules can be exercised\n * without real waits. */\nexport interface LockClock {\n now: () => number;\n sleep: (delay_ms: number) => Promise<void>;\n}\n\nexport const liveLockClock: LockClock = {\n now: () => Date.now(),\n sleep: (delay_ms) => new Promise((resolve) => setTimeout(resolve, delay_ms)),\n};\n\nconst isEexist = (error: unknown): boolean => typeof error === \"object\" && error !== null && \"code\" in error && error.code === \"EEXIST\";\n\n/** The file's bytes, or null when it is not there. */\nasync function readLockRaw(lockPath: string): Promise<string | null> {\n try {\n return await readFile(lockPath, \"utf-8\");\n } catch {\n return null;\n }\n}\n\n/** Absent means unheld; UNREADABLE does not — see `stealIfStale`. */\nfunction parseLock(raw: string): LockFile | null {\n try {\n const parsed: unknown = JSON.parse(raw);\n return hasStringProp(parsed, \"holder\") ? { holder: parsed.holder } : null;\n } catch {\n return null;\n }\n}\n\nasync function readLock(lockPath: string): Promise<LockFile | null> {\n const raw = await readLockRaw(lockPath);\n return raw === null ? null : parseLock(raw);\n}\n\n/** Take the lock, or answer false because someone else holds a live one. */\nasync function tryAcquire(lockPath: string, holder: string): Promise<boolean> {\n try {\n const handle = await open(lockPath, \"wx\", LOCK_MODE);\n try {\n await handle.writeFile(JSON.stringify({ holder }));\n } finally {\n await handle.close();\n }\n return true;\n } catch (error) {\n if (!isEexist(error)) throw error;\n return false;\n }\n}\n\n/** How long this lock has existed, per the filesystem that holds it.\n *\n * From mtime rather than anything the holder wrote down, and for every lock\n * rather than only the unreadable ones. A timestamp written by one host and\n * compared against another host's clock is two wall clocks pretending to be\n * one: skew either way reclaims a live lock or immortalises a dead one (Codex\n * review #2690). Every host reading this workspace sees the SAME mtime.\n *\n * This still assumes the reader's clock and the filesystem's are within a TTL\n * of each other — true by construction on a local disk, and a sub-second NTP\n * question on a network mount, against a window of ten seconds.\n *\n * Reading mtime also covers the moment `open(\"wx\")` has created the file but\n * its payload has not landed yet: \"unreadable\" is a state every healthy lock\n * passes through, and one being born reads as newborn rather than dead.\n *\n * Null when the file is gone, which is not an age but an absence. */\nasync function lockAgeMs(lockPath: string, clock: LockClock): Promise<number | null> {\n const stamped = await stat(lockPath).catch(() => null);\n return stamped === null ? null : clock.now() - stamped.mtimeMs;\n}\n\n/** Drop a lock that has sat untouched past the TTL, whatever it says.\n *\n * Readable or not, nothing is reclaimed until it is demonstrably stale. For a\n * readable one the holder is re-read and compared before the unlink, so a\n * holder that renewed in between is not evicted. That narrows the window\n * rather than closing it — two processes can still both decide to clear the\n * same corpse. The cost of losing that race is one lost update, which is what\n * this file reduces rather than a new failure, and reaching it takes a host\n * dying mid-write plus a full TTL of waiting. */\nasync function stealIfStale(lockPath: string, clock: LockClock): Promise<void> {\n const raw = await readLockRaw(lockPath);\n if (raw === null) return;\n const held = parseLock(raw);\n const age_ms = await lockAgeMs(lockPath, clock);\n if (age_ms === null || age_ms < LOCK_STALE_MS) return;\n // A lock nobody can read is a lock nobody can release — `release` only removes\n // a file whose holder matches — so once it IS stale it has to be cleared here\n // or it would cost every later mutation the full wait before giving up.\n if (held === null) {\n log.warn(\"google\", \"reclaiming an unreadable calendar state lock\", { lockPath, age_ms });\n await unlink(lockPath).catch(() => undefined);\n return;\n }\n const stillHeld = await readLock(lockPath);\n if (stillHeld?.holder !== held.holder) return;\n log.warn(\"google\", \"reclaiming a calendar state lock whose holder went quiet\", { lockPath, age_ms });\n await unlink(lockPath).catch(() => undefined);\n}\n\nasync function release(lockPath: string, holder: string): Promise<void> {\n const held = await readLock(lockPath);\n // Only ever remove OUR lock: after a steal the file may belong to whoever\n // took it next, and unlinking that would hand the same lock to two holders.\n if (held?.holder !== holder) return;\n await unlink(lockPath).catch(() => undefined);\n}\n\n/** Run `mutate` with no other process mutating the same file.\n *\n * Fails OPEN. A workspace that cannot create the lock file — a read-only\n * mount, a missing directory, an exhausted disk — still syncs, the way it did\n * before #2679; the lock removes a race, it is not a precondition for\n * correctness. The same applies to waiting the timeout out: proceeding\n * unlocked risks the lost update this exists to prevent, whereas refusing\n * would stop the sync outright.\n *\n * Note what this cannot promise. `O_EXCL` is atomic on a local filesystem and\n * on NFSv3+, but a workspace living in a consumer sync folder (Dropbox,\n * iCloud, Drive) has no such guarantee — there the file is replicated after\n * the fact and both hosts believe they hold it. */\nexport async function withCalendarStateLock<T>(lockPath: string, mutate: () => Promise<T>, clock: LockClock = liveLockClock): Promise<T> {\n const holder = randomUUID();\n const deadline = clock.now() + LOCK_WAIT_MS;\n let holding = false;\n try {\n holding = await acquireBefore(lockPath, holder, deadline, clock);\n if (!holding) log.warn(\"google\", \"proceeding without the calendar state lock — another host is holding it\", { lockPath });\n return await mutate();\n } finally {\n if (holding) await release(lockPath, holder);\n }\n}\n\n/** Poll until the lock is ours or the deadline passes. */\nasync function acquireBefore(lockPath: string, holder: string, deadline: number, clock: LockClock): Promise<boolean> {\n for (;;) {\n try {\n if (await tryAcquire(lockPath, holder)) return true;\n await stealIfStale(lockPath, clock);\n } catch (error) {\n log.warn(\"google\", \"could not take the calendar state lock — mutating without it\", { lockPath, error: String(error) });\n return false;\n }\n if (clock.now() >= deadline) return false;\n await clock.sleep(LOCK_RETRY_MS);\n }\n}\n\n/** `<state file>.lock`, so each state file is serialised on its own. */\nexport const stateLockPath = (statePath: string): string => `${statePath}.lock`;\n","// Persistence for the Google Calendar incremental-sync tokens (#2095) and the\n// cross-host sync markers (#2678).\n//\n// Unlike the OAuth material in `paths.ts`, this state lives INSIDE the\n// workspace. A syncToken is a claim about which records the workspace already\n// holds: if it survived a workspace reset, the next incremental sync would\n// report \"nothing changed\" against an empty calendar and stay silently empty.\n// Keeping it next to the data it describes makes the two reset together.\nimport path from \"node:path\";\nimport { getWorkspaceRoot } from \"../collection/server/host.js\";\nimport { readJsonOrNull, writeJsonAtomicWithMode } from \"./fsJson.js\";\nimport { canonicalCalendarId } from \"./calendar.js\";\nimport { stateLockPath, withCalendarStateLock } from \"./calendarStateLock.js\";\n\nconst SYNC_STATE_MODE = 0o600;\n\n/** `<workspace>/data/calendar/.sync-state.json` */\nexport function calendarSyncStatePath(workspaceRoot?: string): string {\n return path.join(workspaceRoot ?? getWorkspaceRoot(), \"data\", \"calendar\", \".sync-state.json\");\n}\n\ninterface CalendarSyncState {\n /** calendarId → the `nextSyncToken` returned by that calendar's last sync. */\n tokens: Record<string, string>;\n /** calendarId → when a sync of it last STARTED (ISO).\n *\n * A separate concern from the token, deliberately: the token says how far\n * the workspace has read, never when anyone last ran. Workspace state rather\n * than host state, because that is exactly what lets a second host see that\n * this calendar is already being synced (#2678, `calendarSyncDue.ts`). */\n lastSyncedAt: Record<string, string>;\n}\n\n// Shared with the REST layer so a stored token is keyed by exactly the\n// calendar the request addressed — an omitted id and an explicit \"primary\"\n// must never end up as two different keys.\nconst calendarKey = canonicalCalendarId;\n\n/** The key for a consumer that reads a calendar without KEEPING what it reads.\n *\n * A sync token is a claim about which events the consumer already holds, but\n * the key above names only the calendar — so every consumer of one calendar\n * shares one cursor. That is right for the collections bound to it (one fetch\n * fans out to all of them in the same pass) and wrong for the standalone\n * `google` tool's `calendarSync`, which walks the calendar, reports a summary\n * to the LLM and discards the events.\n *\n * Sharing it lost data both ways (#2850): the tool consumed windows the\n * collections then never received, and a collection created afterwards resumed\n * from the tool's cursor — so its \"first sync\" was a delta of a window it had\n * never held, and the calendar's history never arrived. Same file, separate\n * keyspace. Idempotent through `calendarKey`, since the result is non-empty. */\nexport const toolCalendarSyncKey = (calendarId: string | undefined): string => `tool:${canonicalCalendarId(calendarId)}`;\n\n/** Read one map out of whatever is on disk. Tolerant on purpose: a file written\n * before `lastSyncedAt` existed simply lacks the key, and a hand-edited one may\n * hold anything. A dropped entry costs a full re-walk or a duplicate run, never\n * data — throwing here would stop the sync entirely. */\nfunction stringRecord(value: unknown): Record<string, string> {\n if (typeof value !== \"object\" || value === null) return {};\n return Object.fromEntries(Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"));\n}\n\nasync function readState(workspaceRoot?: string): Promise<CalendarSyncState> {\n const stored = await readJsonOrNull<Partial<CalendarSyncState>>(calendarSyncStatePath(workspaceRoot));\n return { tokens: stringRecord(stored?.tokens), lastSyncedAt: stringRecord(stored?.lastSyncedAt) };\n}\n\n// One file holds every calendar's token, so an unguarded read-modify-write\n// loses updates: two calendars syncing at once both read the same snapshot and\n// the later write drops the earlier one's token — silently forcing that\n// calendar into a full re-walk next run. Serialising the whole cycle keeps\n// them ordered. Scope is this process, which is where the concurrency comes\n// from (parallel tool calls today, the scheduler fanning out over calendars\n// next). Held in a const wrapper so the tail can advance without a `let`.\nconst writeQueue: { tail: Promise<unknown> } = { tail: Promise.resolve() };\n\n/** Decide from the stored state and write the result, both inside the queue, so\n * nothing in this process can read between the two. A `null` state skips the\n * write; `result` is what the caller learns about the decision. */\nasync function updateStateWith<T>(decide: (state: CalendarSyncState) => { state: CalendarSyncState | null; result: T }, workspaceRoot?: string): Promise<T> {\n const statePath = calendarSyncStatePath(workspaceRoot);\n // The queue orders this process; the file lock orders the others. Both are\n // needed — the lock is per read-modify-write, so two of THIS process's own\n // mutations would still interleave without the queue (#2679).\n const run = writeQueue.tail.then(\n async () =>\n await withCalendarStateLock(stateLockPath(statePath), async () => {\n const { state, result } = decide(await readState(workspaceRoot));\n if (state) await writeJsonAtomicWithMode(statePath, state, SYNC_STATE_MODE);\n return result;\n }),\n );\n // Swallow on the queue only — the caller still sees the original rejection.\n writeQueue.tail = run.catch(() => undefined);\n return await run;\n}\n\nasync function updateState(mutate: (state: CalendarSyncState) => CalendarSyncState, workspaceRoot?: string): Promise<void> {\n await updateStateWith((state) => ({ state: mutate(state), result: undefined }), workspaceRoot);\n}\n\nconst withoutKey = (record: Record<string, string>, dropped: string): Record<string, string> =>\n Object.fromEntries(Object.entries(record).filter(([key]) => key !== dropped));\n\nexport async function loadCalendarSyncToken(calendarId?: string, workspaceRoot?: string): Promise<string | null> {\n const state = await readState(workspaceRoot);\n return state.tokens[calendarKey(calendarId)] ?? null;\n}\n\nexport async function saveCalendarSyncToken(calendarId: string | undefined, syncToken: string, workspaceRoot?: string): Promise<void> {\n const stored = calendarKey(calendarId);\n await updateState((state) => ({ ...state, tokens: { ...state.tokens, [stored]: syncToken } }), workspaceRoot);\n}\n\n/** Drop one calendar's token — used when Google answers 410, so the next run\n * starts a clean full sync. Leaves the sync marker alone: \"restart the walk\"\n * says nothing about \"nobody is running\". */\nexport async function clearCalendarSyncToken(calendarId?: string, workspaceRoot?: string): Promise<void> {\n const dropped = calendarKey(calendarId);\n await updateState((state) => ({ ...state, tokens: withoutKey(state.tokens, dropped) }), workspaceRoot);\n}\n\nexport async function loadCalendarLastSyncedAt(calendarId?: string, workspaceRoot?: string): Promise<string | null> {\n const state = await readState(workspaceRoot);\n return state.lastSyncedAt[calendarKey(calendarId)] ?? null;\n}\n\n/** Stamp when a sync of this calendar started, but only if `mayClaim` accepts\n * what the marker currently says. Answers whether THIS call is the one that\n * claimed it.\n *\n * The decision and the stamp share one pass through the queue on purpose.\n * Deciding from a snapshot read earlier — as a caller filtering a whole map of\n * calendars up front must — leaves both hosts believing the calendar is free,\n * which is the race this exists to close (Codex review #2680). What remains is\n * the read→write gap BETWEEN processes; closing that needs a real file lock\n * (#2679), and this stays a soft dedup until then.\n *\n * The time is passed in rather than read here so the rule stays testable\n * without freezing the clock. */\nexport async function claimCalendarSyncIfDue(\n calendarId: string | undefined,\n startedAt: string,\n mayClaim: (lastSyncedAt: string | null) => boolean,\n workspaceRoot?: string,\n): Promise<boolean> {\n const stored = calendarKey(calendarId);\n return await updateStateWith((state) => {\n const claimed = mayClaim(state.lastSyncedAt[stored] ?? null);\n return { state: claimed ? { ...state, lastSyncedAt: { ...state.lastSyncedAt, [stored]: startedAt } } : null, result: claimed };\n }, workspaceRoot);\n}\n\n/** Drop one calendar's marker, so the next tick — on either host — may sync it\n * immediately instead of waiting the window out. */\nexport async function clearCalendarLastSyncedAt(calendarId?: string, workspaceRoot?: string): Promise<void> {\n const dropped = calendarKey(calendarId);\n await updateState((state) => ({ ...state, lastSyncedAt: withoutKey(state.lastSyncedAt, dropped) }), workspaceRoot);\n}\n","// Whether a calendar may be synced now, given when this workspace last STARTED\n// a sync of it (#2678).\n//\n// `googleCalendarSyncTaskDef` is a factory several hosts register — MulmoClaude\n// and a standalone MulmoTerminal/MulmoBooks — and nothing kept them from syncing\n// one calendar at once: the Google link is machine-wide (`~/.config/mulmo`, no\n// app name), the calendar lock is module state, and interval schedules fire on\n// wall-clock boundaries, so every host ticks in the SAME minute. Since `autoPush`\n// (#2620) such a run also writes to Google and rewrites the push baseline.\n//\n// Same soft-dedup feeds already run on `lastFetchedAt`, and with the same limit:\n// the marker is workspace state, so it only speaks for hosts sharing a workspace.\n\n/** Slack between a tick and the marker it writes. Without it a lone host reads\n * its OWN marker as \"not due yet\" — elapsed lands a hair under the interval\n * every time — and syncs every other hour. */\nconst TICK_JITTER_TOLERANCE_MS = 5 * 60 * 1000;\n\n/** How long a fresh marker suppresses a sync: one interval, less the slack.\n * The slack is halved rather than subtracted flat for a very short configured\n * interval, where a fixed five minutes would swallow the window whole. */\nexport function calendarSyncDueWindowMs(intervalMs: number): number {\n return intervalMs - Math.min(TICK_JITTER_TOLERANCE_MS, intervalMs / 2);\n}\n\n/** True iff no recent enough run stands in for this one.\n *\n * A marker that cannot be read as a time is treated as due: a stuck calendar is\n * worse than a duplicate run, which the sync is built to tolerate anyway\n * (writes are upserts by event id). */\nexport function isCalendarSyncDue(lastSyncedAt: string | null, windowMs: number, now: number = Date.now()): boolean {\n if (!lastSyncedAt) return true;\n const elapsed = now - Date.parse(lastSyncedAt);\n if (!Number.isFinite(elapsed)) return true;\n // A marker from a host whose clock runs ahead would otherwise hold this\n // calendar back for as long as the skew lasts. Beyond a whole window it\n // cannot be a run worth waiting for.\n if (elapsed < -windowMs) return true;\n return elapsed >= windowMs;\n}\n","// Whether a collection has ever received the whole calendar (#2850).\n//\n// The sync token cannot answer this. It is keyed by `calendarId` and nothing\n// else, so it is a claim about the CALENDAR, shared by every consumer of it —\n// the standalone `google` tool's `calendarSync`, and every collection bound to\n// that calendar. Whoever walks first stores a cursor, and the next collection\n// resumes from a window it never received: it gets the delta, reports success\n// with no error, and never holds the history at all.\n//\n// So the claim this file stores is the per-collection one the token is not:\n// \"these records already cover the whole of calendar X\".\n//\n// Kept in the collection's OWN data dir rather than in the shared calendar\n// state, and that placement is the design. The marker describes the records, so\n// it must reset exactly when they do — deleting a collection's data (through\n// `deleteCollection` or by hand, which is what the #2850 reporter did six\n// times) leaves no marker behind claiming a backfill that is gone. The\n// alternative, a slug-keyed entry in `.sync-state.json`, needs a cleanup that\n// stays in step with every delete path — the shape #2428 already was.\n//\n// Dot-prefixed so `listItems` skips it: record files are `<id>.json`, and the\n// reader already ignores names starting with `.`.\nimport path from \"node:path\";\nimport { readJsonOrNull, writeJsonAtomicWithMode } from \"./fsJson.js\";\nimport { canonicalCalendarId } from \"./calendar.js\";\nimport { log } from \"./host.js\";\n\nconst BACKFILL_STATE_MODE = 0o600;\nconst BACKFILL_FILE = \".calendar-sync.json\";\n\ninterface BackfillState {\n /** The canonical calendar these records were walked in full against. */\n calendarId: string;\n /** When that walk landed (ISO). Diagnostics — but its PRESENCE is checked,\n * see `needsCalendarBackfill`. */\n walkedAt: string;\n}\n\n/** `<dataDir>/.calendar-sync.json` */\nexport const calendarBackfillPath = (dataDir: string): string => path.join(dataDir, BACKFILL_FILE);\n\n/** Whether these records still need the whole calendar walked into them.\n *\n * Compared against the calendar the marker NAMES, not merely its presence: a\n * schema repointed at another calendar holds a backfill of the wrong one, and\n * resuming that calendar's cursor would leave the same silent gap this exists\n * to close.\n *\n * Unreadable, absent or INCOMPLETE all answer \"yes\" — every field must be\n * there, not just the one being compared. A redundant full walk costs API\n * calls and rewrites records that already match; skipping a needed one loses\n * the history silently, which is the failure being fixed, so the bias only\n * ever goes one way (CodeRabbit review #2853). */\nexport async function needsCalendarBackfill(dataDir: string, calendarId: string | undefined): Promise<boolean> {\n const stored = await readJsonOrNull<Partial<BackfillState>>(calendarBackfillPath(dataDir));\n if (typeof stored?.walkedAt !== \"string\" || stored.walkedAt === \"\") return true;\n return stored.calendarId !== canonicalCalendarId(calendarId);\n}\n\n/** Record that these records now cover the whole calendar. Never throws: the\n * marker is an optimisation over re-walking, so a workspace that cannot write\n * it syncs correctly and simply walks in full again next time. */\nexport async function markCalendarBackfilled(dataDir: string, calendarId: string | undefined, walkedAt: string): Promise<void> {\n const state: BackfillState = { calendarId: canonicalCalendarId(calendarId), walkedAt };\n try {\n await writeJsonAtomicWithMode(calendarBackfillPath(dataDir), state, BACKFILL_STATE_MODE);\n } catch (error) {\n log.warn(\"google\", \"could not record the calendar backfill marker — the next sync will walk the calendar again\", { dataDir, error: String(error) });\n }\n}\n","// What Google last reported for each synced event — the baseline a push diffs\n// against (#2598).\n//\n// Without it, a local edit and an untouched record are indistinguishable: the\n// pull writes Google's value into the record and keeps no copy, so \"the record\n// says 09:30\" answers nothing about who put it there.\n//\n// It holds the RAW Google values, not a hash of them, because the push has to\n// REBUILD a Google time from a stored one and `toCollectionDateTime` is lossy —\n// it drops the zone offset and flattens all-day into `…T00:00`. The baseline is\n// where both come back from (see `pushDateTime.ts`).\n//\n// Lives inside the workspace next to the sync token, and for the same reason\n// (`calendarSyncStore.ts`): it is a claim about which records the workspace\n// holds, so it must reset when the workspace does.\nimport path from \"node:path\";\nimport { getWorkspaceRoot } from \"../collection/server/host.js\";\nimport { readJsonOrNull, writeJsonAtomicWithMode } from \"./fsJson.js\";\nimport { canonicalCalendarId, type CalendarEventSummary } from \"./calendar.js\";\nimport { stateLockPath, withCalendarStateLock } from \"./calendarStateLock.js\";\n\nconst PUSH_STATE_MODE = 0o600;\n\n/** The pushable subset of an event. `htmlLink` and `status` are read-only on\n * Google's side, so a baseline for them could never be acted on. */\nexport type ShadowEvent = Pick<CalendarEventSummary, \"summary\" | \"start\" | \"end\" | \"colorId\" | \"description\" | \"location\">;\n\n/** calendarId → eventId → what Google last said. */\ninterface PushState {\n events: Record<string, Record<string, ShadowEvent>>;\n}\n\n/** `<workspace>/data/calendar/.push-state.json` */\nexport function calendarPushStatePath(workspaceRoot?: string): string {\n return path.join(workspaceRoot ?? getWorkspaceRoot(), \"data\", \"calendar\", \".push-state.json\");\n}\n\n// A state file written before `description` / `location` joined the pushable set\n// simply lacks those keys. That needs no migration: `fieldText(undefined)` and\n// the `\"\"` Google reports for an unset field both compare as empty, so an\n// upgraded host does not read every record as locally edited.\nexport const toShadowEvent = (event: CalendarEventSummary): ShadowEvent => ({\n summary: event.summary,\n start: event.start,\n end: event.end,\n colorId: event.colorId,\n description: event.description,\n location: event.location,\n});\n\nasync function readState(workspaceRoot?: string): Promise<PushState> {\n const stored = await readJsonOrNull<PushState>(calendarPushStatePath(workspaceRoot));\n return stored?.events ? stored : { events: {} };\n}\n\n// One file holds every calendar's baseline, so an unguarded read-modify-write\n// loses updates — the same trap `calendarSyncStore.ts` documents. Serialising\n// the whole cycle keeps concurrent calendars ordered.\nconst writeQueue: { tail: Promise<unknown> } = { tail: Promise.resolve() };\n\nasync function updateState(mutate: (events: PushState[\"events\"]) => PushState[\"events\"], workspaceRoot?: string): Promise<void> {\n const statePath = calendarPushStatePath(workspaceRoot);\n // Same pairing as `calendarSyncStore.ts`: the queue orders this process, the\n // file lock orders the others (#2679).\n const run = writeQueue.tail.then(\n async () =>\n await withCalendarStateLock(stateLockPath(statePath), async () => {\n const state = await readState(workspaceRoot);\n await writeJsonAtomicWithMode(statePath, { events: mutate(state.events) }, PUSH_STATE_MODE);\n }),\n );\n // Swallow on the queue only — the caller still sees the original rejection.\n writeQueue.tail = run.catch(() => undefined);\n return await run;\n}\n\nexport async function loadCalendarShadow(calendarId?: string, workspaceRoot?: string): Promise<Record<string, ShadowEvent>> {\n const state = await readState(workspaceRoot);\n return state.events[canonicalCalendarId(calendarId)] ?? {};\n}\n\n/** Apply a batch to one calendar's baseline. `null` removes an entry (the event\n * was cancelled), so a cancel-then-recreate cannot resume from a stale one.\n * Merged rather than replaced: a sync window describes only what changed. */\nexport function mergeShadow(current: Record<string, ShadowEvent>, updates: Record<string, ShadowEvent | null>): Record<string, ShadowEvent> {\n const removed = new Set(\n Object.entries(updates)\n .filter(([, entry]) => entry === null)\n .map(([eventId]) => eventId),\n );\n const kept = Object.entries(current).filter(([eventId]) => !removed.has(eventId));\n const written = Object.entries(updates).filter((entry): entry is [string, ShadowEvent] => entry[1] !== null);\n return Object.fromEntries([...kept, ...written]);\n}\n\nexport async function saveCalendarShadow(calendarId: string | undefined, updates: Record<string, ShadowEvent | null>, workspaceRoot?: string): Promise<void> {\n if (Object.keys(updates).length === 0) return;\n const key = canonicalCalendarId(calendarId);\n await updateState((events) => ({ ...events, [key]: mergeShadow(events[key] ?? {}, updates) }), workspaceRoot);\n}\n\n/** Drop one calendar's whole baseline. Called wherever the sync token is\n * cleared: a baseline outliving its token would let a push diff against events\n * the workspace no longer holds. */\nexport async function clearCalendarShadow(calendarId?: string, workspaceRoot?: string): Promise<void> {\n const dropped = canonicalCalendarId(calendarId);\n await updateState((events) => Object.fromEntries(Object.entries(events).filter(([key]) => key !== dropped)), workspaceRoot);\n}\n","// Serialising everything that touches ONE calendar's sync state.\n//\n// A leaf on purpose: both directions need it (the pull in `collectionSync.ts`,\n// the push in `collectionPush.ts`), and the pull now drives the push, so leaving\n// the lock inside either one would make the two import each other.\nimport { canonicalCalendarId } from \"./calendar.js\";\n\n/** Serialise `run` against whatever is already running for `key`.\n *\n * `locks` is passed in so the queuing rule is testable without module state;\n * the key is dropped once nothing is queued behind it, so the map cannot grow\n * an entry per calendar forever. A failed predecessor still releases the\n * queue — `then(run, run)`. */\nexport async function withKeyedLock<T>(locks: Map<string, Promise<unknown>>, key: string, run: () => Promise<T>): Promise<T> {\n const previous = locks.get(key) ?? Promise.resolve();\n const result = previous.then(run, run);\n const tail = result.then(\n () => undefined,\n () => undefined,\n );\n locks.set(key, tail);\n try {\n return await result;\n } finally {\n if (locks.get(key) === tail) locks.delete(key);\n }\n}\n\n/** In-flight work per canonical calendar id. Module state on purpose: the\n * scheduler, the create trigger and the Refresh button are three doors into\n * the same calendar (CodeRabbit review #2566). */\nconst calendarLocks = new Map<string, Promise<unknown>>();\n\n/** Serialise anything that touches ONE calendar's sync state.\n *\n * Shared with the push path (#2598), not only the pull doors: a push that\n * overtakes an in-flight pull would record a baseline for events the pull is\n * still writing, so the next push would diff against a future it never saw.\n *\n * NOT reentrant — a holder must call the lock-free form of whatever it drives,\n * or it waits on itself forever. */\nexport async function withCalendarLock<T>(calendarId: string | undefined, run: () => Promise<T>): Promise<T> {\n return await withKeyedLock(calendarLocks, canonicalCalendarId(calendarId), run);\n}\n","// Collection `datetime` value → the Google Calendar event time to push (#2598).\n//\n// The inverse of `toCollectionDateTime`, which is deliberately LOSSY: it drops\n// the RFC3339 zone designator (so the stored clock stays the one the user reads\n// off Google, independent of the machine that synced) and flattens an all-day\n// `YYYY-MM-DD` into `…T00:00`. Neither the zone nor the all-day-ness survives in\n// the stored value, so they cannot be derived from it.\n//\n// What Google last reported for the event carries both, so a push that has that\n// value reuses it. Only a locally-created record has no such history, and it\n// falls back to the calendar's own timezone — never the host's, which is the\n// machine-dependence `collectionDateTime.ts` exists to avoid.\n//\n// Pure: no I/O, no clock, no locale.\nimport { parseIsoDate, parseIsoDateTime } from \"../collection/core/calendarGrid.js\";\nimport type { CalendarEventTime } from \"./calendar.js\";\n\n/** RFC3339 zone designator: `Z`, `+09:00`, `-0500`. */\nconst ZONE_SUFFIX_RE = /(?:Z|[+-]\\d{2}:?\\d{2})$/;\nconst FRACTIONAL_SECONDS_RE = /\\.\\d+$/;\nconst TWO_DIGITS_RE = /^\\d{2}$/;\nconst CLOCK_SEPARATOR = \":\";\nconst DATE_TIME_SEPARATOR = \"T\";\nconst MIN_CLOCK_SEGMENTS = 2;\nconst MAX_CLOCK_SEGMENTS = 3;\nconst WHOLE_MINUTE_SECONDS = \"00\";\nconst MAX_SECONDS = 59;\n\n/** Whether `value` is a real calendar day (`2026-02-30` is not).\n *\n * Delegated to the parser the record lint, the calendar grid and the day view\n * already share, so the push agrees with the rest of the app on what a valid\n * stored date IS — and rejects an impossible one here instead of turning it\n * into an opaque Google 400. */\nconst isCalendarDate = (value: string): boolean => parseIsoDate(value) !== null;\n\n/** A value Google already accepts verbatim, as the event time to send, or null.\n *\n * Reached when the schema maps `start` onto a `string` field: `projectValue`\n * normalises only `datetime` fields, so that column holds Google's own text,\n * zone and all. The civil part is still validated — matching a trailing `Z` is\n * not evidence that the rest is a date-time. */\nfunction rawGoogleTime(value: string): CalendarEventTime | null {\n if (isCalendarDate(value)) return { date: value };\n const zone = value.match(ZONE_SUFFIX_RE)?.[0];\n if (zone === undefined) return null;\n // Google may answer fractional seconds, which the shared parser rejects.\n // Validate without them, then send the original — Calendar accepts them.\n const civil = value.slice(0, value.length - zone.length).replace(FRACTIONAL_SECONDS_RE, \"\");\n return parseIsoDateTime(civil) === null ? null : { dateTime: value };\n}\n\n/** Google's own all-day `end` is EXCLUSIVE — a single day on the 12th ends on\n * the 13th. The pull stores that as `2026-05-13T00:00` and this takes the date\n * part straight back, so the exclusive end round-trips untouched. Looks\n * off-by-one; adjusting it would shorten every all-day event by a day. */\nfunction allDayFrom(datePart: string): CalendarEventTime {\n return { date: datePart };\n}\n\n/** Split `HH:MM[:SS]` into RFC3339's always-three segments, or null.\n *\n * Split rather than one regex: expressing the optional seconds as `(?::(\\d{2}))?`\n * nests a quantifier inside a quantified group, which the ReDoS lint rejects. */\nfunction clockSegments(clockPart: string): string | null {\n const segments = clockPart.split(CLOCK_SEPARATOR);\n if (segments.length < MIN_CLOCK_SEGMENTS || segments.length > MAX_CLOCK_SEGMENTS) return null;\n if (!segments.every((segment) => TWO_DIGITS_RE.test(segment))) return null;\n const [hours, minutes, seconds] = segments;\n // `parseIsoDateTime` validated the hours and minutes but ignores seconds.\n if (seconds !== undefined && Number(seconds) > MAX_SECONDS) return null;\n return [hours, minutes, seconds ?? WHOLE_MINUTE_SECONDS].join(CLOCK_SEPARATOR);\n}\n\n/** `YYYY-MM-DDTHH:MM[:SS]` → its date and its RFC3339 clock, or null.\n *\n * Validity comes from the shared strict parser, so an out-of-range clock or an\n * impossible day is refused here rather than sent to Google. */\nfunction parseStoredDateTime(value: string): { datePart: string; clock: string } | null {\n if (parseIsoDateTime(value) === null) return null;\n const [datePart, clockPart, ...extra] = value.split(DATE_TIME_SEPARATOR);\n if (datePart === undefined || clockPart === undefined || extra.length > 0) return null;\n const clock = clockSegments(clockPart);\n return clock === null ? null : { datePart, clock };\n}\n\n/** The zone designator to re-attach, or null when `previous` carries none. */\nexport function zoneSuffixOf(previous: string | undefined): string | null {\n if (previous === undefined) return null;\n return previous.match(ZONE_SUFFIX_RE)?.[0] ?? null;\n}\n\n/** Build the event time to send for one field.\n *\n * `previous` is the value Google last reported for this event, absent for a\n * locally-created record. Returns null when `local` is neither a stored\n * datetime nor a raw Google value — the caller reports that record rather than\n * letting this invent a time Google would silently accept. */\nexport function toGoogleEventTime(local: unknown, previous: string | undefined, calendarTimeZone: string): CalendarEventTime | null {\n if (typeof local !== \"string\") return null;\n const trimmed = local.trim();\n if (trimmed.length === 0) return null;\n const raw = rawGoogleTime(trimmed);\n if (raw !== null) return raw;\n const parts = parseStoredDateTime(trimmed);\n if (parts === null) return null;\n // An all-day event stays all-day even when its date moves: the user edited a\n // date in a collection, not the event's kind.\n if (previous !== undefined && isCalendarDate(previous)) return allDayFrom(parts.datePart);\n const zone = zoneSuffixOf(previous);\n const dateTime = `${parts.datePart}${DATE_TIME_SEPARATOR}${parts.clock}`;\n return zone === null ? { dateTime, timeZone: calendarTimeZone } : { dateTime: `${dateTime}${zone}` };\n}\n","// Google Calendar date/dateTime → the shape a collection `datetime` field is\n// validated against (#2310).\n//\n// Google answers with RFC3339 including a zone designator for timed events\n// (`2026-05-12T08:45:00+09:00`, sometimes `…Z`) and a bare `YYYY-MM-DD` for\n// all-day ones. `parseIsoDateTime` — which the record lint, the calendar grid\n// and the day view all share — accepts only `YYYY-MM-DDTHH:MM[:SS]`, so both\n// shapes were stored and then reported as data problems on every synced record.\n//\n// The offset is DROPPED, not applied: the stored clock is then the one the user\n// reads off Google Calendar, and it stays that clock wherever the workspace is\n// opened. Converting to the host's local time would make a record's value\n// depend on the machine that happened to run the sync, and silently shift every\n// record when that machine moves timezone.\n//\n// Pure: no I/O, no clock, no locale.\n\n/** All-day events carry no clock; midnight is where the day view places them. */\nconst ALL_DAY_CLOCK = \"00:00\";\nconst DATE_ONLY_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n/** RFC3339 zone designator: `Z`, `+09:00`, `-0500`. */\nconst ZONE_SUFFIX_RE = /(?:Z|[+-]\\d{2}:?\\d{2})$/;\n/** Fractional seconds have no place in the collection datetime shape. */\nconst FRACTIONAL_SECONDS_RE = /\\.\\d+$/;\n\n/** Normalise one Google Calendar time value for storage in a `datetime` field.\n * Anything that isn't a recognisable Google shape (empty string, non-string,\n * free text) is returned untouched — the record lint should report it rather\n * than have this function invent a value. */\nexport function toCollectionDateTime(value: unknown): unknown {\n if (typeof value !== \"string\") return value;\n const trimmed = value.trim();\n if (DATE_ONLY_RE.test(trimmed)) return `${trimmed}T${ALL_DAY_CLOCK}`;\n if (!trimmed.includes(\"T\")) return value;\n return trimmed.replace(ZONE_SUFFIX_RE, \"\").replace(FRACTIONAL_SECONDS_RE, \"\");\n}\n","// Google event → collection record. Pure: no I/O, no clock, no workspace.\n//\n// A leaf on purpose. The pull writes records with it and the push rebuilds its\n// baseline with it (`pushPlan.ts`), which is what makes \"unchanged\" mean exactly\n// \"the pull would produce this\". Leaving it inside the pull would make the push\n// import the pull, and the pull now drives the push.\nimport type { CollectionFieldSpec, CollectionItem } from \"../collection/core/schema.js\";\nimport type { GOOGLE_CALENDAR_SOURCE_FIELDS } from \"../collection/core/schemaZ.js\";\nimport type { CalendarEventSummary } from \"./calendar.js\";\nimport { toCollectionDateTime } from \"./collectionDateTime.js\";\n\n/** The event fields a schema may map from — narrowed to real keys of\n * `CalendarEventSummary` so the projection below needs no cast. */\nexport type GoogleCalendarSourceField = (typeof GOOGLE_CALENDAR_SOURCE_FIELDS)[number];\n\nconst DATETIME_FIELD_TYPE = \"datetime\";\n\n/** Own-property lookup, mirroring what the record lint reads: a spec reachable\n * only through the prototype chain is not a DECLARED field, so it must not\n * decide how a value is stored. */\nfunction declaredSpec(fields: Record<string, CollectionFieldSpec>, field: string): CollectionFieldSpec | undefined {\n return Object.hasOwn(fields, field) ? fields[field] : undefined;\n}\n\n/** Google's raw value is normalised only into a field the schema declares as\n * `datetime` — that is the type whose stored shape the record lint, the\n * calendar grid and the day view all parse (#2310). A user who maps `start`\n * onto a `string` field asked for Google's value verbatim and keeps it. */\nfunction projectValue(fields: Record<string, CollectionFieldSpec>, field: string, value: string): unknown {\n return declaredSpec(fields, field)?.type === DATETIME_FIELD_TYPE ? toCollectionDateTime(value) : value;\n}\n\n/** Project one Google event onto the collection's own field names. The\n * primary field always takes the event id — upsert-by-id is what keeps the\n * sync idempotent, so it is deliberately not remappable. */\nexport function toCollectionRecord(\n event: CalendarEventSummary,\n map: Record<string, GoogleCalendarSourceField>,\n primaryKey: string,\n fields: Record<string, CollectionFieldSpec>,\n): CollectionItem {\n const mapped = Object.entries(map).map(([field, source]): [string, unknown] => [field, projectValue(fields, field, event[source])]);\n return { ...Object.fromEntries(mapped), [primaryKey]: event.id };\n}\n","// Deciding what a Collection → Google push should do to each record (#2598).\n//\n// Pure: the whole diff rule is expressed against three plain values — the local\n// record, the baseline Google last reported (`calendarPushState.ts`), and\n// Google's value right now — so it is testable without a workspace or a grant.\nimport type { CollectionFieldSpec, CollectionItem } from \"../collection/core/schema.js\";\nimport type { CalendarEventSummary } from \"./calendar.js\";\nimport type { ShadowEvent } from \"./calendarPushState.js\";\nimport { toCollectionRecord, type GoogleCalendarSourceField } from \"./collectionProjection.js\";\n\n/** The event fields Google lets a caller write — a SUBSET of what the pull can\n * read (`GOOGLE_CALENDAR_SOURCE_FIELDS`). A column mapped to one of the\n * pull-only fields is ignored here rather than rejected: the mapping was\n * authored for the pull, and a push has no business invalidating it.\n *\n * `satisfies` is what keeps the two lists honest. A field pushable but not\n * pullable could never have its baseline rebuilt from a pull, so \"unchanged\"\n * would stop meaning anything — this fails the build instead of shipping. */\nexport const PUSHABLE_SOURCE_FIELDS = [\"summary\", \"start\", \"end\", \"colorId\", \"description\", \"location\"] as const satisfies readonly GoogleCalendarSourceField[];\n\nexport type PushableSourceField = (typeof PUSHABLE_SOURCE_FIELDS)[number];\n\nconst isPushableSource = (source: string): source is PushableSourceField => PUSHABLE_SOURCE_FIELDS.some((field) => field === source);\n\n/** The subset of a schema's `map` a push can act on: collection field → event field. */\nexport function pushableMap(map: Record<string, string>): Record<string, PushableSourceField> {\n return Object.fromEntries(Object.entries(map).filter((entry): entry is [string, PushableSourceField] => isPushableSource(entry[1])));\n}\n\n/** Event field → the record column feeding it. The schema authors the map the\n * other way round (for the pull); a push looks up by event field. Two columns\n * mapped to one event field is already ambiguous for the pull — last wins. */\nexport function bySourceField(map: Record<string, PushableSourceField>): Partial<Record<PushableSourceField, string>> {\n return Object.fromEntries(Object.entries(map).map(([field, source]) => [source, field]));\n}\n\n/** Google accepts a caller-chosen event id of 5-1024 base32hex characters. A\n * record id outside that (a semantic id like `team-standup`) cannot be used,\n * and v1 reports it instead of re-keying the record — see the plan. */\nconst CLIENT_EVENT_ID_RE = /^[0-9a-v]{5,1024}$/;\n\nexport const isClientSettableEventId = (eventId: string): boolean => CLIENT_EVENT_ID_RE.test(eventId);\n\n/** A field's value as comparable/sendable text.\n *\n * Absent, null and `\"\"` all collapse to `\"\"`: Google reports an inherited colour\n * as `\"\"` while the record file may omit the key entirely, and treating those as\n * different would report a local edit on every single push.\n *\n * A non-primitive is JSON-encoded rather than left to `String`, whose\n * `[object Object]` would make two DIFFERENT objects compare equal — a\n * silently-skipped edit. */\nexport const fieldText = (value: unknown): string => {\n if (value === undefined || value === null) return \"\";\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"boolean\" || typeof value === \"bigint\") return String(value);\n // `JSON.stringify` answers undefined for a function or symbol — neither is a\n // field value any schema produces, and \"\" keeps them comparing as absent.\n return JSON.stringify(value) ?? \"\";\n};\n\n/** A stored datetime with no seconds — what the collection's own date-time input\n * produces. Google's values always carry seconds, so the pull writes them and a\n * hand-typed value does not. */\nconst SECONDLESS_DATETIME_RE = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$/;\nconst WHOLE_MINUTE = \":00\";\n\n/** Field text with `…T09:30` and `…T09:30:00` collapsed to one spelling.\n *\n * Without this, a record the user typed a time into never stops looking edited:\n * the push writes it, Google answers with seconds, the baseline projection keeps\n * them, and the seconds-less record then differs from its own baseline on every\n * later push — re-PATCHing the same event on every click, forever.\n *\n * A value that carries a zone offset (a `start` mapped onto a `string` field) is\n * left alone: it is Google's own text on both sides already. */\nconst comparableText = (value: unknown): string => {\n const text = fieldText(value);\n return SECONDLESS_DATETIME_RE.test(text) ? `${text}${WHOLE_MINUTE}` : text;\n};\n\n/** The event fields a baseline does NOT carry, because none of them is pushable\n * — the baseline exists to answer \"did the local side change a field we could\n * send?\", and a pull-only field is by definition not one.\n *\n * Typed as the exact complement of `ShadowEvent` + `id`, so a field added to\n * `CalendarEventSummary` without being made pushable must be zeroed here or\n * the build fails. */\nconst UNPUSHABLE_EVENT_FIELDS: Omit<CalendarEventSummary, keyof ShadowEvent | \"id\"> = {\n htmlLink: \"\",\n status: \"\",\n recurringEventId: \"\",\n originalStartTime: \"\",\n updated: \"\",\n transparency: \"\",\n eventType: \"\",\n hangoutLink: \"\",\n};\n\n/** A baseline shaped as the event it came from, so the comparison can run\n * through the very projection that wrote the record. */\nconst asEventSummary = (eventId: string, shadow: ShadowEvent): CalendarEventSummary => ({ id: eventId, ...UNPUSHABLE_EVENT_FIELDS, ...shadow });\n\n/** What the record WOULD hold if nobody had edited it since the baseline.\n *\n * Deliberately `toCollectionRecord` — the same function the pull writes with —\n * so \"unchanged\" here means exactly \"the pull would produce this\". A private\n * re-implementation would drift from it and report phantom edits. */\nexport function baselineRecord(\n eventId: string,\n shadow: ShadowEvent,\n map: Record<string, PushableSourceField>,\n primaryKey: string,\n fields: Record<string, CollectionFieldSpec>,\n): CollectionItem {\n return toCollectionRecord(asEventSummary(eventId, shadow), map, primaryKey, fields);\n}\n\n/** The mapped fields whose local value no longer matches the baseline. */\nexport function locallyChangedFields(record: CollectionItem, baseline: CollectionItem, map: Record<string, PushableSourceField>): PushableSourceField[] {\n return Object.entries(map)\n .filter(([field]) => comparableText(record[field]) !== comparableText(baseline[field]))\n .map(([, source]) => source);\n}\n\n/** The fields Google changed since the baseline, restricted to the ones the\n * local edit touches.\n *\n * Field-level on purpose: Google moving an event the user retitled locally is\n * not a conflict, because the patch only carries the title. Comparing whole\n * events would refuse those pushes for no reason. */\nexport function conflictingFields(shadow: ShadowEvent, current: ShadowEvent, changed: readonly PushableSourceField[]): PushableSourceField[] {\n return changed.filter((source) => comparableText(shadow[source]) !== comparableText(current[source]));\n}\n\nexport type RecordPlan =\n | { kind: \"create\" }\n | { kind: \"unchanged\" }\n /** Locally edited; `fields` is what to PATCH, pending the conflict check. */\n | { kind: \"changed\"; fields: PushableSourceField[] };\n\n/** Classify one record against its baseline, before Google is consulted.\n *\n * No baseline means the record never came from a sync, so it is local-only and\n * has to be created. Google is queried ONLY for a `changed` record, which is\n * what keeps a push over a large collection cheap. */\nexport function planRecord(\n eventId: string,\n record: CollectionItem,\n shadow: ShadowEvent | undefined,\n map: Record<string, PushableSourceField>,\n primaryKey: string,\n fields: Record<string, CollectionFieldSpec>,\n): RecordPlan {\n if (shadow === undefined) return { kind: \"create\" };\n const changed = locallyChangedFields(record, baselineRecord(eventId, shadow, map, primaryKey, fields), map);\n return changed.length === 0 ? { kind: \"unchanged\" } : { kind: \"changed\", fields: changed };\n}\n\n/** Whether an event that already holds this record's id may be adopted as its\n * baseline.\n *\n * A 409 from `insert` usually means a previous push created the event but its\n * baseline never landed. It can ALSO mean an unrelated event happens to hold\n * this id, and adopting that would bind the record to a stranger's event — so\n * adoption needs evidence the two are the same logical event, not just the same\n * id (Codex review).\n *\n * The evidence is that the remote event ALREADY equals what this record would\n * write. Adoption then records a baseline and writes nothing, so it cannot\n * modify whatever that event is. Anything else is refused. */\nexport function mayAdoptExisting(\n eventId: string,\n record: CollectionItem,\n existing: ShadowEvent,\n map: Record<string, PushableSourceField>,\n primaryKey: string,\n fields: Record<string, CollectionFieldSpec>,\n): boolean {\n return locallyChangedFields(record, baselineRecord(eventId, existing, map, primaryKey, fields), map).length === 0;\n}\n\n/** Record ids the baseline knows but the collection no longer holds — deleted\n * locally. v1 reports the count and pushes nothing: `calendarDeleteEvent`\n * removes the event for every attendee and cannot be undone. */\nexport function locallyDeletedIds(shadow: Record<string, ShadowEvent>, presentIds: readonly string[]): string[] {\n const present = new Set(presentIds);\n return Object.keys(shadow).filter((eventId) => !present.has(eventId));\n}\n","// Collection → Google Calendar push, on demand (#2598).\n//\n// The mirror of `collectionSync.ts`, and deliberately NOT its automatic twin:\n// this runs only when the user clicks, because a push writes to a calendar other\n// people may be reading. v1 creates and updates; it never deletes (a Google\n// delete removes the event for every attendee and cannot be undone).\n//\n// Both directions share `withCalendarLock`, so a push can never interleave with\n// the pull whose baseline it depends on.\nimport { discoverCollections } from \"../collection/server/discovery.js\";\nimport type { LoadedCollection } from \"../collection/server/discoveredCollection.js\";\nimport { storeFor } from \"../collection/server/store.js\";\nimport type { CollectionFieldSpec, CollectionItem } from \"../collection/core/schema.js\";\nimport { getGoogleAccessToken } from \"./auth.js\";\nimport { isGoogleApiError, HTTP_FORBIDDEN } from \"./apiClient.js\";\nimport {\n canonicalCalendarId,\n createCalendarEvent,\n getCalendarEvent,\n getCalendarMeta,\n listCalendars,\n updateCalendarEvent,\n CANCELLED_EVENT_STATUS,\n HTTP_CONFLICT,\n HTTP_PRECONDITION_FAILED,\n type CalendarEventInput,\n type CalendarEventSummary,\n type CalendarEventTime,\n type UpdateCalendarEventInput,\n} from \"./calendar.js\";\nimport { loadCalendarShadow, saveCalendarShadow, toShadowEvent, type ShadowEvent } from \"./calendarPushState.js\";\nimport { withCalendarLock } from \"./calendarLock.js\";\nimport { toGoogleEventTime } from \"./pushDateTime.js\";\nimport {\n bySourceField,\n conflictingFields,\n fieldText,\n isClientSettableEventId,\n locallyDeletedIds,\n mayAdoptExisting,\n planRecord,\n pushableMap,\n type PushableSourceField,\n} from \"./pushPlan.js\";\nimport { loadGoogleTokens } from \"./tokenStore.js\";\nimport { log } from \"./host.js\";\n\n/** Access roles that may write events. `reader` / `freeBusyReader` cannot, and a\n * push under them fails per event with an opaque 403 — checked once up front so\n * the user is told the real reason. */\nconst WRITABLE_ACCESS_ROLES: readonly string[] = [\"owner\", \"writer\"];\n\n/** What the up-front writability gate knows about the target calendar. */\nexport interface CalendarWriteTarget {\n /** The caller's role on the calendar, or null when neither source reported one.\n * Null means unknown, never read-only: hard-denying an unknown role would\n * refuse a calendar the user can in fact write to. */\n accessRole: string | null;\n /** IANA zone for rebuilding a zone-less stored clock, `\"\"` when unreported. */\n timeZone: string;\n}\n\n/** Whether to refuse the whole push before touching a single event.\n *\n * Only on POSITIVE evidence of a non-writable role. A calendar whose role\n * neither source reported is unknown and must fall through: hard-denying it\n * would block the feature outright for a calendar the user can in fact write\n * to. Those attempts surface Google's own 403 per record if the write really is\n * not allowed. */\nexport const isDeniedAccessRole = (accessRole: string | null): boolean => accessRole !== null && !WRITABLE_ACCESS_ROLES.includes(accessRole);\n\n/** A role Google left blank is UNKNOWN, not a denial. `isDeniedAccessRole`\n * refuses every role it does not recognise — `\"\"` included — so passing an\n * unreported one straight through would refuse a calendar Google simply said\n * nothing about. */\nexport const reportedAccessRole = (accessRole: string): string | null => accessRole || null;\n\nexport interface CalendarCollectionPushResult {\n slug: string;\n created: number;\n updated: number;\n /** Edited on both sides; skipped so neither version is destroyed. */\n conflicts: number;\n /** Records deleted locally. Reported only — v1 never deletes in Google. */\n localDeletes: number;\n /** Records that cannot be pushed as they stand, each with the reason. */\n skipped: string[];\n errors: string[];\n /** Records whose local edit did NOT reach Google and would be destroyed by\n * the pull that follows an automatic push (#2620).\n *\n * Only the two states where overwriting loses work with nowhere to recover\n * it from: a both-sides conflict, and an unexpected failure. A `skipped`\n * record is deliberately absent — several of its reasons (an id already\n * taken in Google, an unmappable span) are recovered BY letting the pull\n * write, so protecting them would stall the collection instead. */\n unpushedIds: string[];\n}\n\nexport type CalendarPushOutcome =\n | { kind: \"pushed\"; result: CalendarCollectionPushResult }\n | { kind: \"not-a-calendar\" }\n | { kind: \"not-linked\" }\n | { kind: \"read-only\"; accessRole: string }\n /** The push never got as far as a record: a revoked grant, an unreachable\n * Calendar API, a workspace read failure. Typed rather than left to throw so\n * the route answers in the same shape as the other setup problems instead of\n * an opaque 500. */\n | { kind: \"failed\"; message: string };\n\n/** The I/O a push crosses, injected so every outcome can be exercised with fakes\n * instead of a workspace on disk and a live Google grant. */\nexport interface CalendarPushDeps {\n findCollection: (slug: string, workspaceRoot: string) => Promise<LoadedCollection | null>;\n isLinked: () => Promise<boolean>;\n accessToken: () => Promise<string>;\n calendarMeta: (accessToken: string, calendarId: string | undefined) => Promise<CalendarWriteTarget>;\n}\n\n/** Resolve the calendar a schema names, for its writability and its timezone.\n *\n * The list is consulted first because it answers for every added calendar in\n * one call the push already makes. `\"primary\"` is matched on the `primary`\n * flag, not the id — the primary calendar's own id is the account's email\n * address, which never equals the literal the schema declares.\n *\n * A calendar absent from the list is NOT denied: `calendarList` holds only the\n * calendars the user has added, so one shared with write access can be missing\n * from it (Codex review). It is asked about itself instead, which reports the\n * same zone AND the real role. A 404 there means the calendar is genuinely\n * unreachable, and propagates as a `failed` outcome. */\nasync function liveCalendarMeta(accessToken: string, calendarId: string | undefined): Promise<CalendarWriteTarget> {\n const key = canonicalCalendarId(calendarId);\n const calendars = await listCalendars(accessToken);\n const listed = key === \"primary\" ? calendars.find((calendar) => calendar.primary) : calendars.find((calendar) => calendar.id === key);\n if (listed) return { accessRole: listed.accessRole, timeZone: listed.timeZone };\n const direct = await getCalendarMeta(accessToken, calendarId);\n log.info(\"google\", \"calendar is not in the user's list — role read from the calendar itself\", { calendarId: key, accessRole: direct.accessRole });\n return { accessRole: reportedAccessRole(direct.accessRole), timeZone: direct.timeZone };\n}\n\nasync function findCalendarCollection(slug: string, workspaceRoot: string): Promise<LoadedCollection | null> {\n const all = await discoverCollections({ workspaceRoot });\n return all.find((collection) => collection.slug === slug && collection.schema.googleCalendar) ?? null;\n}\n\nconst liveDeps: CalendarPushDeps = {\n findCollection: findCalendarCollection,\n isLinked: async () => Boolean((await loadGoogleTokens())?.refresh_token),\n accessToken: getGoogleAccessToken,\n calendarMeta: liveCalendarMeta,\n};\n\n/** Everything one record's push needs, resolved once for the whole run. */\ninterface PushContext {\n accessToken: string;\n calendarId: string | undefined;\n /** The calendar's IANA zone, `\"\"` when Google did not report one. */\n timeZone: string;\n map: Record<string, PushableSourceField>;\n bySource: Partial<Record<PushableSourceField, string>>;\n primaryKey: string;\n fields: Record<string, CollectionFieldSpec>;\n shadow: Record<string, ShadowEvent>;\n}\n\ntype PushOutcome =\n | { kind: \"created\"; event: CalendarEventSummary }\n | { kind: \"updated\"; event: CalendarEventSummary }\n | { kind: \"conflict\" }\n /** `event` present only when this record's baseline had to be repaired (an\n * event that already existed in Google), so the write path can persist it. */\n | { kind: \"unchanged\"; event?: CalendarEventSummary }\n | { kind: \"skipped\"; message: string }\n | { kind: \"error\"; message: string };\n\nexport type PushOutcomeKind = PushOutcome[\"kind\"];\n\nconst localValue = (ctx: PushContext, record: CollectionItem, source: PushableSourceField): unknown => {\n const field = ctx.bySource[source];\n return field === undefined ? undefined : record[field];\n};\n\ntype TimeResult = { ok: true; time: CalendarEventTime } | { ok: false; reason: string };\n\n/** One end of the span, rebuilt from the stored clock plus whatever the baseline\n * remembers about the original (zone, all-day). */\nfunction eventTime(ctx: PushContext, record: CollectionItem, source: \"start\" | \"end\", previous: string | undefined): TimeResult {\n const value = localValue(ctx, record, source);\n if (value === undefined) return { ok: false, reason: `no \\`${source}\\` field is mapped` };\n const time = toGoogleEventTime(value, previous, ctx.timeZone);\n if (time === null) return { ok: false, reason: `\\`${source}\\` is not a date-time Google accepts (got ${JSON.stringify(value)})` };\n // A zone-less dateTime needs `timeZone`; without one Google answers an opaque 400.\n if (\"dateTime\" in time && time.timeZone === \"\") return { ok: false, reason: `the calendar reports no timezone, so \\`${source}\\` cannot be sent without one` };\n return { ok: true, time };\n}\n\n/** `description` / `location` for a create — omitted when empty, since there is\n * nothing to state about an event that carries neither. */\nconst optionalText = (ctx: PushContext, record: CollectionItem): Pick<CalendarEventInput, \"description\" | \"location\"> => {\n const description = fieldText(localValue(ctx, record, \"description\"));\n const location = fieldText(localValue(ctx, record, \"location\"));\n return { ...(description ? { description } : {}), ...(location ? { location } : {}) };\n};\n\nasync function createFromRecord(ctx: PushContext, eventId: string, record: CollectionItem): Promise<PushOutcome> {\n if (!isClientSettableEventId(eventId)) {\n return { kind: \"skipped\", message: `${eventId}: the record id cannot be used as a Google event id (needs 5-1024 characters from 0-9a-v)` };\n }\n const start = eventTime(ctx, record, \"start\", undefined);\n const end = eventTime(ctx, record, \"end\", undefined);\n if (!start.ok) return { kind: \"skipped\", message: `${eventId}: ${start.reason}` };\n if (!end.ok) return { kind: \"skipped\", message: `${eventId}: ${end.reason}` };\n const colorId = fieldText(localValue(ctx, record, \"colorId\"));\n const event = await createCalendarEvent(ctx.accessToken, {\n eventId,\n calendarId: ctx.calendarId,\n summary: fieldText(localValue(ctx, record, \"summary\")),\n start: start.time,\n end: end.time,\n ...optionalText(ctx, record),\n ...(colorId ? { colorId } : {}),\n });\n return { kind: \"created\", event };\n}\n\ntype EventPatch = Pick<UpdateCalendarEventInput, \"summary\" | \"colorId\" | \"start\" | \"end\" | \"description\" | \"location\">;\ntype PatchResult = { ok: true; patch: EventPatch } | { ok: false; reason: string };\n\n/** The free-text fields of a patch, each only when the local edit touched it.\n * Unlike `colorId`, `\"\"` is a real value here — Google clears the field. */\nconst textPatch = (ctx: PushContext, record: CollectionItem, changed: readonly PushableSourceField[]): EventPatch => {\n const touched = ([\"summary\", \"description\", \"location\"] as const).filter((source) => changed.includes(source));\n return Object.fromEntries(touched.map((source) => [source, fieldText(localValue(ctx, record, source))]));\n};\n\n/** Only the fields the local edit touched — PATCH semantics, so an untouched\n * attendee list / recurrence rule is left alone. */\nfunction buildPatch(ctx: PushContext, record: CollectionItem, changed: readonly PushableSourceField[], shadow: ShadowEvent): PatchResult {\n const resolved = ([\"start\", \"end\"] as const)\n .filter((source) => changed.includes(source))\n .map((source) => ({ source, result: eventTime(ctx, record, source, shadow[source]) }));\n const [failure] = resolved.flatMap((entry) => (entry.result.ok ? [] : [entry.result.reason]));\n if (failure !== undefined) return { ok: false, reason: failure };\n const colorId = fieldText(localValue(ctx, record, \"colorId\"));\n // Calendar rejects \"\" as a palette id, so a cleared colour cannot be sent.\n // Reported rather than dropped: a silently dropped field leaves the record\n // looking edited forever, re-pushing it on every click.\n if (changed.includes(\"colorId\") && colorId === \"\") return { ok: false, reason: \"Google cannot clear an event colour once set\" };\n return {\n ok: true,\n patch: {\n ...textPatch(ctx, record, changed),\n ...(changed.includes(\"colorId\") ? { colorId } : {}),\n ...Object.fromEntries(resolved.flatMap((entry) => (entry.result.ok ? [[entry.source, entry.result.time]] : []))),\n },\n };\n}\n\n/** Google has dropped the event this record mirrors.\n *\n * The advice used to be \"sync first, then re-create it\", which sent people\n * nowhere: Google never resends a cancellation, so a sync sees nothing to do —\n * and before #2688 the sync that DID see it deleted the record along with the\n * edit. Deliberately silent on whether Google will hand the id back; that is\n * unverified. */\nconst goneFromGoogle = (eventId: string): PushOutcome => ({\n kind: \"skipped\",\n message: `${eventId}: Google no longer has this event, and your local edit was kept. Delete this record, or copy it into a new one.`,\n});\n\nasync function updateFromRecord(\n ctx: PushContext,\n eventId: string,\n record: CollectionItem,\n changed: readonly PushableSourceField[],\n shadow: ShadowEvent,\n): Promise<PushOutcome> {\n const fetched = await getCalendarEvent(ctx.accessToken, { calendarId: ctx.calendarId, eventId });\n if (fetched === null || fetched.event.status === CANCELLED_EVENT_STATUS) return goneFromGoogle(eventId);\n if (conflictingFields(shadow, toShadowEvent(fetched.event), changed).length > 0) return { kind: \"conflict\" };\n const built = buildPatch(ctx, record, changed, shadow);\n if (!built.ok) return { kind: \"skipped\", message: `${eventId}: ${built.reason}` };\n try {\n // `If-Match` closes the window between the read above and this write: without\n // it a change landing in between is silently overwritten, which is exactly\n // what the conflict check exists to prevent.\n const event = await updateCalendarEvent(ctx.accessToken, { eventId, calendarId: ctx.calendarId, ifMatch: fetched.etag, ...built.patch });\n return { kind: \"updated\", event };\n } catch (error) {\n if (isGoogleApiError(error) && error.status === HTTP_PRECONDITION_FAILED) return { kind: \"conflict\" };\n throw error;\n }\n}\n\n/** Create, recovering from the one 409 that is provably safe to recover from.\n *\n * A 409 means the id is taken, which is USUALLY a previous push whose baseline\n * never landed. It can also be an unrelated event that happens to hold this id,\n * so the recovery never writes: it adopts the baseline only when the remote\n * event already equals what this record would write, and otherwise reports the\n * fix. Patching whatever answered the 409 would let a push modify a stranger's\n * event (Codex review).\n *\n * Refusal is not a dead end — a Sync writes both the record and its baseline,\n * after which the record pushes normally. */\nasync function createOrAdopt(ctx: PushContext, eventId: string, record: CollectionItem): Promise<PushOutcome> {\n try {\n return await createFromRecord(ctx, eventId, record);\n } catch (error) {\n if (!isGoogleApiError(error) || error.status !== HTTP_CONFLICT) throw error;\n const fetched = await getCalendarEvent(ctx.accessToken, { calendarId: ctx.calendarId, eventId });\n if (fetched === null) throw error;\n if (mayAdoptExisting(eventId, record, toShadowEvent(fetched.event), ctx.map, ctx.primaryKey, ctx.fields)) {\n return { kind: \"unchanged\", event: fetched.event };\n }\n return {\n kind: \"skipped\",\n message: `${eventId}: an event with this id already exists in Google and differs from this record — press Sync to adopt it, then push again`,\n };\n }\n}\n\n/** A 403 on a write is a permissions answer, but `googleApiError` appends the\n * \"is the API enabled for the Cloud project?\" hint to every 403 — true for a\n * disabled API, misleading here. Reached when the up-front gate let the push\n * through on an unknown role, which is exactly when the user needs the real\n * reason. */\nfunction writeFailure(eventId: string, error: unknown): PushOutcome {\n if (isGoogleApiError(error) && error.status === HTTP_FORBIDDEN) {\n return { kind: \"skipped\", message: `${eventId}: Google refused the write — you may not have permission to change events on this calendar` };\n }\n return { kind: \"error\", message: `${eventId}: ${String(error)}` };\n}\n\nasync function pushRecord(ctx: PushContext, eventId: string, record: CollectionItem): Promise<PushOutcome> {\n try {\n const shadow = ctx.shadow[eventId];\n const plan = planRecord(eventId, record, shadow, ctx.map, ctx.primaryKey, ctx.fields);\n if (plan.kind === \"unchanged\") return { kind: \"unchanged\" };\n if (plan.kind === \"create\" || shadow === undefined) return await createOrAdopt(ctx, eventId, record);\n return await updateFromRecord(ctx, eventId, record, plan.fields, shadow);\n } catch (error) {\n // One rejected event must not abandon the rest of the collection.\n return writeFailure(eventId, error);\n }\n}\n\n/** The baseline an outcome establishes, so the next push sees the record as\n * unchanged instead of pushing it again. Empty for anything that wrote nothing\n * and had no baseline to repair. */\nfunction pushedShadow(outcomes: readonly PushOutcome[]): Record<string, ShadowEvent> {\n const written = outcomes.flatMap((outcome) => (\"event\" in outcome && outcome.event !== undefined ? [outcome.event] : []));\n return Object.fromEntries(written.map((event) => [event.id, toShadowEvent(event)]));\n}\n\n/** One record's push, kept with the id so the pull can be told which records to\n * leave alone. */\ninterface PushAttempt {\n eventId: string;\n outcome: PushOutcome;\n}\n\n/** See `unpushedIds` on the result for why `skipped` is not in this set. */\nconst UNPUSHED_KINDS: readonly PushOutcome[\"kind\"][] = [\"conflict\", \"error\"];\n\n/** Whether this outcome leaves a local edit that only exists locally, so the\n * pull must not overwrite the record and the baseline must not advance. */\nexport const isUnpushed = (kind: PushOutcomeKind): boolean => UNPUSHED_KINDS.includes(kind);\n\nfunction tally(slug: string, attempts: readonly PushAttempt[], localDeletes: number): CalendarCollectionPushResult {\n const outcomes = attempts.map((attempt) => attempt.outcome);\n const count = (kind: PushOutcome[\"kind\"]): number => outcomes.filter((outcome) => outcome.kind === kind).length;\n return {\n slug,\n created: count(\"created\"),\n updated: count(\"updated\"),\n conflicts: count(\"conflict\"),\n localDeletes,\n skipped: outcomes.flatMap((outcome) => (outcome.kind === \"skipped\" ? [outcome.message] : [])),\n errors: outcomes.flatMap((outcome) => (outcome.kind === \"error\" ? [outcome.message] : [])),\n unpushedIds: attempts.filter((attempt) => isUnpushed(attempt.outcome.kind)).map((attempt) => attempt.eventId),\n };\n}\n\n/** Record ids whose local value no longer matches the baseline — edits that have\n * not reached Google.\n *\n * Pure, and deliberately built from `planRecord`, so it answers with exactly the\n * records a push WOULD have acted on. A private re-implementation would drift\n * from the push and protect the wrong set. */\nexport function locallyEditedIds(\n records: readonly CollectionItem[],\n shadow: Record<string, ShadowEvent>,\n map: Record<string, PushableSourceField>,\n primaryKey: string,\n fields: Record<string, CollectionFieldSpec>,\n): string[] {\n const ids = records.map((record) => ({ eventId: fieldText(record[primaryKey]), record }));\n return ids\n .filter(({ eventId, record }) => planRecord(eventId, record, shadow[eventId], map, primaryKey, fields).kind !== \"unchanged\")\n .map(({ eventId }) => eventId);\n}\n\n/** The same set, read from the workspace.\n *\n * Needed when a push cannot run AT ALL — a calendar whose role degraded to\n * reader, a revoked grant, an IO failure. The pull still runs, since reading\n * needs no write access, and would overwrite exactly these records while\n * advancing their baseline past the edit (CodeRabbit review #2666). Computed\n * from the stored baseline alone, so it works precisely when Google is the\n * thing that is unreachable. */\nexport async function unsentLocalEdits(collection: LoadedCollection, workspaceRoot: string): Promise<string[]> {\n const { schema } = collection;\n const shadow = await loadCalendarShadow(schema.googleCalendar?.calendarId, workspaceRoot);\n const records = await storeFor(collection, { workspaceRoot }).list();\n return locallyEditedIds(records, shadow, pushableMap(schema.googleCalendar?.map ?? {}), schema.primaryKey, schema.fields);\n}\n\n/** Push one collection WITHOUT taking the calendar lock.\n *\n * The caller must already hold it. The scheduled push→pull cycle\n * (`collectionSync.ts`) runs inside the lock it took for the pull, and the lock\n * is not reentrant — going through `pushCalendarForCollection` there would make\n * the sync wait on itself forever. */\nexport async function pushCollectionNow(collection: LoadedCollection, workspaceRoot: string, deps: CalendarPushDeps = liveDeps): Promise<CalendarPushOutcome> {\n const { schema, slug } = collection;\n const calendarId = schema.googleCalendar?.calendarId;\n const accessToken = await deps.accessToken();\n const meta = await deps.calendarMeta(accessToken, calendarId);\n if (isDeniedAccessRole(meta.accessRole)) return { kind: \"read-only\", accessRole: meta.accessRole ?? \"\" };\n\n const map = pushableMap(schema.googleCalendar?.map ?? {});\n const shadow = await loadCalendarShadow(calendarId, workspaceRoot);\n const records = await storeFor(collection, { workspaceRoot }).list();\n const ctx: PushContext = {\n accessToken,\n calendarId,\n timeZone: meta.timeZone,\n map,\n bySource: bySourceField(map),\n primaryKey: schema.primaryKey,\n fields: schema.fields,\n shadow,\n };\n\n const attempts: PushAttempt[] = [];\n for (const record of records) {\n const eventId = fieldText(record[schema.primaryKey]);\n const outcome = await pushRecord(ctx, eventId, record);\n attempts.push({ eventId, outcome });\n // Persisted per record, not once at the end: an interruption after a\n // successful write would otherwise leave that event with no baseline, and\n // the next push would retry it as a create and hit Google's duplicate-id\n // 409. Costs nothing for a record that wrote nothing (empty batch).\n await saveCalendarShadow(calendarId, pushedShadow([outcome]), workspaceRoot);\n }\n const deletes = locallyDeletedIds(\n shadow,\n records.map((record) => fieldText(record[schema.primaryKey])),\n );\n const result = tally(slug, attempts, deletes.length);\n if (result.errors.length > 0) log.warn(\"google\", \"calendar push finished with errors\", { slug, errors: result.errors.length });\n return { kind: \"pushed\", result };\n}\n\n/** Push ONE collection's records to the calendar it declares.\n *\n * \"Does this collection sync at all\" is answered before \"is Google linked\", for\n * the reason `syncCalendarForCollection` gives: telling someone to link their\n * account for a collection that never declared a calendar sends them fixing the\n * wrong thing. */\nexport async function pushCalendarForCollection(slug: string, workspaceRoot: string, deps: CalendarPushDeps = liveDeps): Promise<CalendarPushOutcome> {\n try {\n const collection = await deps.findCollection(slug, workspaceRoot);\n if (collection === null) return { kind: \"not-a-calendar\" };\n if (!(await deps.isLinked())) return { kind: \"not-linked\" };\n return await withCalendarLock(collection.schema.googleCalendar?.calendarId, () => pushCollectionNow(collection, workspaceRoot, deps));\n } catch (error) {\n // A throw here is a setup failure (revoked grant, Calendar API unreachable,\n // unreadable workspace), not a per-record one. Reported in the same typed\n // shape as the other setup problems so the route need not guess.\n log.warn(\"google\", \"calendar push could not start\", { slug, error: String(error) });\n return { kind: \"failed\", message: String(error) };\n }\n}\n","// LLM-free Google Calendar → collection sync (#2095).\n//\n// The whole point of #2095 is that syncing must not cost tokens: this path\n// runs on the scheduler, calls the Calendar REST API directly, and writes\n// records itself. No chat, no agent, no MCP round-trip.\n//\n// Destination is not hardcoded — a collection opts in by declaring\n// `googleCalendar` in its schema, exactly the way a feed opts in by declaring\n// `ingest`. There is no preset calendar collection (the standalone Calendar\n// view was removed in 0.7.0); the user asks for one and the agent authors it.\nimport { stat } from \"node:fs/promises\";\nimport { MISSED_RUN_POLICIES, SCHEDULE_TYPES } from \"@receptron/task-scheduler\";\nimport type { SystemTaskDef } from \"../scheduler/adapter.js\";\nimport type { CollectionItem } from \"../collection/core/schema.js\";\nimport { discoverCollections } from \"../collection/server/discovery.js\";\nimport { getWorkspaceRoot } from \"../collection/server/host.js\";\nimport type { LoadedCollection } from \"../collection/server/discoveredCollection.js\";\nimport type { DeleteItemResult, WriteItemResult } from \"../collection/server/io.js\";\nimport { storeFor } from \"../collection/server/store.js\";\nimport { getGoogleAccessToken } from \"./auth.js\";\nimport { canonicalCalendarId, syncCalendarEvents, CANCELLED_EVENT_STATUS, type CalendarEventSummary } from \"./calendar.js\";\nimport { withCalendarLock } from \"./calendarLock.js\";\nimport { mergeIntoExisting } from \"../collection/core/project.js\";\nimport { toCollectionRecord } from \"./collectionProjection.js\";\nimport { pushCollectionNow, unsentLocalEdits, type CalendarCollectionPushResult, type CalendarPushOutcome } from \"./collectionPush.js\";\nimport {\n claimCalendarSyncIfDue,\n clearCalendarLastSyncedAt,\n clearCalendarSyncToken,\n loadCalendarSyncToken,\n saveCalendarSyncToken,\n} from \"./calendarSyncStore.js\";\nimport { calendarSyncDueWindowMs, isCalendarSyncDue } from \"./calendarSyncDue.js\";\nimport { markCalendarBackfilled, needsCalendarBackfill } from \"./calendarBackfillState.js\";\nimport { clearCalendarShadow, loadCalendarShadow, saveCalendarShadow, toShadowEvent, type ShadowEvent } from \"./calendarPushState.js\";\nimport { baselineRecord, locallyChangedFields, pushableMap } from \"./pushPlan.js\";\nimport { loadGoogleTokens } from \"./tokenStore.js\";\nimport { log } from \"./host.js\";\n\nexport const GOOGLE_CALENDAR_SYNC_TASK_ID = \"system:google-calendar-sync\";\nconst DEFAULT_SYNC_INTERVAL_MS = 60 * 60 * 1000;\n\nexport interface CalendarCollectionSyncResult {\n slug: string;\n written: number;\n removed: number;\n /** Events that can NEVER be stored — e.g. an id the record-file sanitiser\n * rejects. Reported and skipped rather than retried; see `classifyWrite`. */\n unwritable: string[];\n /** Events left alone because the record they would overwrite holds an edit\n * Google has not seen. NOT an error — the token still advances past them; only\n * the baseline is held back, so the next push reports the conflict (#2684). */\n withheld: string[];\n /** Retryable failures. Any of these hold the sync token back. */\n errors: string[];\n}\n\n/** `skipped` is a benign no-op; `unwritable` can never succeed so it must NOT\n * hold the token; `error` is retryable and does hold it; `withheld` is a\n * deliberate refusal to overwrite a local edit. */\ntype ApplyOutcome =\n | { kind: \"written\" }\n | { kind: \"removed\" }\n | { kind: \"skipped\" }\n | { kind: \"withheld\" }\n | { kind: \"unwritable\"; message: string }\n | { kind: \"error\"; message: string };\n\n// `writeItem` / `deleteItem` report most failures by RETURNING a non-`ok` kind\n// rather than throwing. Ignoring that would let the token advance past events\n// that never landed, and Google never resends them (Codex review #2184).\n//\n// But not every failure is worth retrying: an id the record-file sanitiser\n// rejects can never become valid, so holding the token for it would re-fetch\n// the same window forever and permanently kill that calendar's sync — far\n// worse than dropping the one event. Google's own ids are base32hex and pass,\n// but an imported/client-set id may contain characters the sanitiser refuses.\n// (Observed during Claude review, not flagged by a bot.)\nexport function classifyWrite(eventId: string, kind: WriteItemResult[\"kind\"]): ApplyOutcome {\n if (kind === \"ok\") return { kind: \"written\" };\n if (kind === \"invalid-id\") return { kind: \"unwritable\", message: `write ${eventId}: invalid-id` };\n return { kind: \"error\", message: `write ${eventId}: ${kind}` };\n}\n\nexport function classifyDelete(eventId: string, kind: DeleteItemResult[\"kind\"]): ApplyOutcome {\n if (kind === \"ok\") return { kind: \"removed\" };\n // Cancelling an event we never stored is normal, not a failure.\n if (kind === \"not-found\") return { kind: \"skipped\" };\n if (kind === \"invalid-id\") return { kind: \"unwritable\", message: `delete ${eventId}: invalid-id` };\n return { kind: \"error\", message: `delete ${eventId}: ${kind}` };\n}\n\n/** Whether this record still says what the workspace last saw Google say.\n *\n * Compared against the baseline as it stood when this run STARTED READING, not\n * as it stands now: a full re-walk clears the baseline before writing a new one\n * (`restartFullSync`), so reading it live would answer \"no baseline\" for every\n * event and protect nothing exactly when the window is widest (#2684).\n *\n * A record with no baseline at all is NOT withheld — that is an event this\n * workspace has never held, so there is no local edit to lose. */\nexport function unsentEditGuard(\n schema: LoadedCollection[\"schema\"],\n baseline: Record<string, ShadowEvent>,\n): (existing: CollectionItem, eventId: string) => boolean {\n // Built once per collection rather than per event: a full walk runs this over\n // every event the calendar has (Sourcery review #2687).\n const map = pushableMap(schema.googleCalendar?.map ?? {});\n return (existing, eventId) => {\n const shadow = baseline[eventId];\n if (shadow === undefined) return false;\n return locallyChangedFields(existing, baselineRecord(eventId, shadow, map, schema.primaryKey, schema.fields), map).length > 0;\n };\n}\n\n/** What the pull may do to the record behind one event.\n *\n * The guard runs BEFORE the status is consulted, and that order is the whole\n * fix: \"is there something local to lose here?\" outranks \"what did Google do\n * to it?\". Asking about the status first is how a cancellation kept deleting\n * records that held an edit Google had never seen (#2688), long after the\n * same guard had been put in front of the overwrite (#2684). */\nexport function applyPlanFor(\n existing: CollectionItem | null,\n event: CalendarEventSummary,\n hasUnsentEdit: (existing: CollectionItem, eventId: string) => boolean,\n): \"withhold\" | \"delete\" | \"write\" {\n if (existing !== null && hasUnsentEdit(existing, event.id)) return \"withhold\";\n return event.status === CANCELLED_EVENT_STATUS ? \"delete\" : \"write\";\n}\n\n/** Lay Google's mapped values over whatever the record already holds. Split out\n * so `applyEvent` reads as read → decide → act rather than carrying the write\n * itself (CodeRabbit review #2689). */\nasync function writeProjected(\n write: NonNullable<ReturnType<typeof storeFor>[\"write\"]>,\n event: CalendarEventSummary,\n schema: LoadedCollection[\"schema\"],\n existing: CollectionItem | null,\n): Promise<ApplyOutcome> {\n const record = toCollectionRecord(event, schema.googleCalendar?.map ?? {}, schema.primaryKey, schema.fields);\n return classifyWrite(event.id, (await write(event.id, mergeIntoExisting(existing, record))).kind);\n}\n\nasync function applyEvent(\n collection: LoadedCollection,\n event: CalendarEventSummary,\n workspaceRoot: string,\n hasUnsentEdit: (existing: CollectionItem, eventId: string) => boolean,\n): Promise<ApplyOutcome> {\n const { schema } = collection;\n try {\n // Discovery rejects googleCalendar on a read-only (dataSource) schema,\n // so absent write/delete is defense in depth, not a live path. The store\n // threads the slug into the change publish, so an open view updates live.\n const store = storeFor(collection, { workspaceRoot });\n if (!store.write || !store.delete) return { kind: \"unwritable\", message: `collection '${collection.slug}' is read-only` };\n // Read and decide immediately before acting. The set computed back at push\n // time cannot cover an edit made while the window was in flight — minutes\n // of it, on a full walk (#2684).\n const existing = await store.read(event.id);\n const plan = applyPlanFor(existing, event, hasUnsentEdit);\n if (plan === \"withhold\") return { kind: \"withheld\" };\n if (plan === \"delete\") return classifyDelete(event.id, (await store.delete(event.id)).kind);\n return await writeProjected(store.write, event, schema, existing);\n } catch (error) {\n // A thrown IO error (EACCES, ENOSPC, …) must not abort the remaining events\n // or the other collections on this calendar — record it as retryable so the\n // token holds and the next run retries only what failed (CodeRabbit #2184).\n return { kind: \"error\", message: `apply ${event.id}: ${String(error)}` };\n }\n}\n\n/** The events of a window a pull may act on.\n *\n * A record the push just refused to send is edited on BOTH sides. Writing\n * Google's value over it destroys the local edit the push declined to resolve,\n * so the local one stands. Single-sourced because the record write and the\n * baseline write must agree exactly on which events they skip — disagreeing is\n * what would silently overwrite Google on the next push (#2620). */\nexport function pullableEvents(events: readonly CalendarEventSummary[], unpushed: ReadonlySet<string>): CalendarEventSummary[] {\n return events.filter((event) => !unpushed.has(event.id));\n}\n\n/** Report what an automatic push did, since nobody is watching a scheduled run.\n * A conflict or an error means that record now diverges from Google until\n * someone resolves it, which must not be silent. */\nfunction reportAutoPush(slug: string, result: CalendarCollectionPushResult): void {\n const { created, updated, conflicts, skipped, errors, unpushedIds } = result;\n if (created + updated > 0) log.info(\"google\", \"auto-pushed local calendar edits\", { slug, created, updated });\n if (unpushedIds.length > 0) {\n log.warn(\"google\", \"records the auto push could not send — the pull will leave them alone\", { slug, conflicts, unpushedIds });\n }\n if (skipped.length > 0) log.warn(\"google\", \"records the auto push skipped\", { slug, skipped });\n if (errors.length > 0) log.warn(\"google\", \"auto push errors\", { slug, errors });\n}\n\n/** What an automatic push protected, per collection slug. `null` means it could\n * not be worked out at all — see `PROTECTION_UNKNOWN`. */\nexport type UnpushedBySlug = ReadonlyMap<string, ReadonlySet<string> | null>;\n\nconst NOTHING_UNPUSHED: ReadonlySet<string> = new Set();\n\n/** A collection whose protection is unknown must not be pulled this run.\n *\n * Reported through the retryable `errors` channel rather than as a special case:\n * that already holds the sync token AND skips the baseline save, so the window\n * simply replays next run with nothing lost. Failing OPEN here — pulling with no\n * protection — would overwrite the very edits this exists to protect; the read\n * that failed is no evidence that the pull's own writes would fail too, so they\n * would land (CodeRabbit review #2666). */\nexport const PROTECTION_UNKNOWN = \"could not work out which records to protect from the pull\";\n\n/** What ONE collection's pull must leave alone: only what ITS OWN push did not\n * send.\n *\n * Scoped per collection because a calendar can back several of them, and one\n * collection's unsent edit says nothing about the others. Sharing one set across\n * the group starved a collection that never even declares `autoPush`: a\n * neighbour's conflict froze its records — and the sync token still advanced, so\n * Google never resent them (Codex review #2666). */\nexport const unpushedFor = (unpushed: UnpushedBySlug, slug: string): ReadonlySet<string> | null => {\n const protection = unpushed.get(slug);\n return protection === undefined ? NOTHING_UNPUSHED : protection;\n};\n\n/** What the calendar's BASELINE must leave alone: the union over every\n * collection.\n *\n * Deliberately not per collection, unlike the records above. `.push-state.json`\n * holds ONE baseline per calendar, shared by every collection on it, so there is\n * no per-collection baseline to hold back. Advancing it while any collection\n * still has an unresolved conflict is the failure that silently overwrites\n * Google on the next push, and holding it back only ever means \"keep reporting\n * the conflict\" — so the union is the safe side of an asymmetry the shared\n * storage forces.\n *\n * A `null` (unknown) entry contributes nothing, because that collection reports\n * a retryable error instead — which stops the baseline being saved at all. */\nexport const allUnpushed = (unpushed: UnpushedBySlug): ReadonlySet<string> => new Set([...unpushed.values()].flatMap((ids) => (ids === null ? [] : [...ids])));\n\n/** What a collection's pull must protect when its push did not run AT ALL.\n *\n * Registering nothing here was a silent data loss of exactly the kind this\n * feature exists to prevent: a calendar whose role degrades to reader refuses\n * the whole push, yet the pull still runs — reading needs no write access — and\n * overwrote every unsent local edit while advancing its baseline past it, so the\n * next push could not even report the conflict (CodeRabbit review #2666). A\n * collection that never declares `autoPush` is in that same state on every\n * single run, which is how the loss reappeared without a failure in sight\n * (#2683).\n *\n * Protects the edited records rather than all of them, so an unchanged record\n * keeps syncing normally. `null` when even that could not be worked out — the\n * caller then refuses to pull the collection at all, because failing open here\n * destroys exactly what this protects. */\nasync function protectUnsentEdits(collection: LoadedCollection, workspaceRoot: string, deps: PullProtectionDeps): Promise<ReadonlySet<string> | null> {\n try {\n const edited = await deps.unsentEdits(collection, workspaceRoot);\n if (edited.length > 0) log.warn(\"google\", \"protecting local edits that have not reached Google\", { slug: collection.slug, edited });\n return new Set(edited);\n } catch (error) {\n log.warn(\"google\", PROTECTION_UNKNOWN, { slug: collection.slug, error: String(error) });\n return null;\n }\n}\n\n/** The I/O the protection rule crosses, injected so every branch of it can be\n * exercised with fakes instead of a workspace on disk and a live Google grant.\n * The rule is what #2666 and #2683 both got wrong, so it is worth pinning. */\nexport interface PullProtectionDeps {\n pushNow: (collection: LoadedCollection, workspaceRoot: string) => Promise<CalendarPushOutcome>;\n unsentEdits: (collection: LoadedCollection, workspaceRoot: string) => Promise<string[]>;\n}\n\nconst livePullProtectionDeps: PullProtectionDeps = { pushNow: pushCollectionNow, unsentEdits: unsentLocalEdits };\n\n/** What ONE collection's pull must leave alone, pushing it first if it asked to\n * be pushed.\n *\n * A collection WITHOUT `autoPush` never pushes, so its local edits are unsent by\n * definition — the same state a failed push leaves behind, and it needs the same\n * protection. Pulling over them destroys the edit AND advances the baseline past\n * it, after which no conflict can be detected any more (#2683). \"The push did not\n * run\" is the condition that matters here; why it did not run is not.\n *\n * MUST run inside the calendar lock the caller already holds — hence\n * `pushCollectionNow` rather than `pushCalendarForCollection`, which would take\n * the same non-reentrant lock and wait on itself forever.\n *\n * A failed push must not stop the pull: the pull is what keeps the collection\n * fresh, and a revoked write grant is no reason to freeze reading. */\nexport async function pullProtectionFor(\n collection: LoadedCollection,\n workspaceRoot: string,\n deps: PullProtectionDeps = livePullProtectionDeps,\n): Promise<ReadonlySet<string> | null> {\n if (!collection.schema.googleCalendar?.autoPush) return await protectUnsentEdits(collection, workspaceRoot, deps);\n try {\n const outcome = await deps.pushNow(collection, workspaceRoot);\n if (outcome.kind === \"pushed\") {\n reportAutoPush(collection.slug, outcome.result);\n return new Set(outcome.result.unpushedIds);\n }\n log.warn(\"google\", \"auto push did not run\", { slug: collection.slug, reason: outcome.kind });\n } catch (error) {\n log.warn(\"google\", \"auto push failed — pulling anyway\", { slug: collection.slug, error: String(error) });\n }\n return await protectUnsentEdits(collection, workspaceRoot, deps);\n}\n\n/** Push the `autoPush` collections in this group, and answer with what every\n * collection's pull must leave alone, keyed by collection. */\nexport async function pushAndProtect(\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n deps: PullProtectionDeps = livePullProtectionDeps,\n): Promise<UnpushedBySlug> {\n const unpushed = new Map<string, ReadonlySet<string> | null>();\n for (const collection of collections) {\n unpushed.set(collection.slug, await pullProtectionFor(collection, workspaceRoot, deps));\n }\n return unpushed;\n}\n\nasync function restartFullSync(accessToken: string, calendarId: string | undefined, workspaceRoot: string) {\n await clearCalendarSyncToken(calendarId, workspaceRoot);\n // The push baseline describes the records the consumed token accounted for, so\n // it must not outlive that token — a full re-walk rewrites it from scratch.\n await clearCalendarShadow(calendarId, workspaceRoot);\n return await syncCalendarEvents(accessToken, { calendarId });\n}\n\n/** Whether a run may take the calendar, given what the shared marker says.\n *\n * The scheduled door defers to it; every user-facing door claims regardless,\n * because a Refresh click that silently returns nothing reads as an empty\n * calendar, not as \"another host has this\". */\nexport type ClaimGuard = (lastSyncedAt: string | null) => boolean;\n\nconst ALWAYS_CLAIM: ClaimGuard = () => true;\n\n/** Sync ONE calendar and fan its events out to every collection bound to it.\n *\n * The fan-out is not an optimisation, it is correctness: the sync token is\n * keyed by `calendarId`, so syncing collection-by-collection would let the\n * first collection advance the shared token and leave every later collection\n * on the same calendar reading an already-consumed window — silently missing\n * those events forever. Fetch once, apply to all, then advance the token.\n * (Codex + CodeRabbit review on #2184.)\n *\n * Queued per calendar for the same reason the fan-out exists: two passes over\n * one calendar (a Refresh click landing during the scheduled run) would each\n * load the SAME stored token and walk the same window. That is idempotent —\n * writes are upserts by event id — but it is a wasted full walk. Queued, the\n * second pass resumes from the token the first just stored and fetches only\n * what is genuinely new. That queue is module state, so it orders the doors\n * into THIS process only; `claimThenSync` is what other hosts can see. */\nexport async function syncCalendarGroup(\n calendarId: string | undefined,\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n mayClaim: ClaimGuard = ALWAYS_CLAIM,\n): Promise<CalendarCollectionSyncResult[]> {\n return await withCalendarLock(calendarId, () => claimThenSync(calendarId, collections, workspaceRoot, mayClaim));\n}\n\n/** Take the workspace-shared marker BEFORE the calendar is touched, sync only if\n * this run got it, and drop it again if the run could not happen at all.\n *\n * Claiming first is the whole point: hosts tick in the same minute (interval\n * schedules align to wall-clock boundaries), so a marker written on completion\n * would leave the entire run — minutes, for a first full walk — open for a\n * second host to start alongside this one and lose the push baseline it writes.\n *\n * Released only when the sync could not run at all. A run that finished with\n * retryable per-collection errors KEEPS the marker: it already held the token\n * and the baseline back, so it replays on the next tick either way, and\n * releasing would hand a permanently-failing calendar back to both hosts at\n * once — the very overlap this guards (observed during Claude review). */\nasync function claimThenSync(\n calendarId: string | undefined,\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n mayClaim: ClaimGuard,\n): Promise<CalendarCollectionSyncResult[]> {\n if (!(await claimCalendarSync(calendarId, workspaceRoot, mayClaim))) {\n log.info(\"google\", \"skipping a calendar this workspace synced recently — another host may be on it\", { calendarId });\n return [];\n }\n try {\n return await syncCalendarGroupNow(calendarId, collections, workspaceRoot);\n } catch (error) {\n await releaseCalendarSyncClaim(calendarId, workspaceRoot);\n throw error;\n }\n}\n\n/** Never throws, and fails OPEN: the marker guards against duplicate work, it is\n * not a precondition for syncing correctly. A workspace that cannot store it\n * syncs the way it did before #2678. */\nasync function claimCalendarSync(calendarId: string | undefined, workspaceRoot: string, mayClaim: ClaimGuard): Promise<boolean> {\n try {\n return await claimCalendarSyncIfDue(calendarId, new Date().toISOString(), mayClaim, workspaceRoot);\n } catch (error) {\n log.warn(\"google\", \"could not stamp the calendar sync marker — another host may sync this calendar in parallel\", { calendarId, error: String(error) });\n return true;\n }\n}\n\nasync function releaseCalendarSyncClaim(calendarId: string | undefined, workspaceRoot: string): Promise<void> {\n try {\n await clearCalendarLastSyncedAt(calendarId, workspaceRoot);\n } catch (error) {\n log.warn(\"google\", \"could not release the calendar sync marker after a failed run\", { calendarId, error: String(error) });\n }\n}\n\n/** Every event whose baseline must NOT advance: what the push could not send,\n * plus what the apply refused to overwrite.\n *\n * The two must agree exactly. The apply's refusals only became visible after it\n * ran, so they join the union here rather than at push time — leaving them out\n * would advance the baseline past a record the pull deliberately left holding a\n * local edit, which is the silent overwrite this whole path exists to stop. */\nexport const heldBack = (unpushed: UnpushedBySlug, results: readonly CalendarCollectionSyncResult[]): ReadonlySet<string> =>\n new Set([...allUnpushed(unpushed), ...results.flatMap((result) => result.withheld)]);\n\n/** The collections in this group that have never received the whole calendar,\n * by slug. Asked of each collection's OWN records (`calendarBackfillState.ts`),\n * because the sync token cannot answer it — it is keyed by calendar and shared\n * by every consumer of that calendar (#2850). */\nexport async function collectionsNeedingBackfill(collections: readonly LoadedCollection[]): Promise<string[]> {\n const checked = await Promise.all(\n collections.map(async (collection) => {\n const pending = await needsCalendarBackfill(collection.dataDir, collection.schema.googleCalendar?.calendarId);\n return pending ? collection.slug : null;\n }),\n );\n return checked.filter((slug): slug is string => slug !== null);\n}\n\n/** The token this run may resume from — none while any collection in the group\n * still needs the whole calendar.\n *\n * A stored token says how far THE CALENDAR has been read, by whoever read it:\n * the standalone `google` tool's `calendarSync`, or a sibling collection that\n * synced before this one existed. Resuming from it hands a brand-new collection\n * a delta of a window it never received — a handful of records, reported as a\n * success with no error, and never the history the docs promise (#2850).\n *\n * The full walk fans out to the whole group, which is free: writes are upserts,\n * so the collections that were already current simply rewrite what they hold. */\nexport async function resumableToken(\n calendarId: string | undefined,\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n): Promise<string | undefined> {\n const pending = await collectionsNeedingBackfill(collections);\n if (pending.length === 0) return (await loadCalendarSyncToken(calendarId, workspaceRoot)) ?? undefined;\n log.info(\"google\", \"walking the whole calendar — these collections have never received it\", { calendarId, collections: pending });\n return undefined;\n}\n\n/** Record the backfill for every collection the window landed in. Called only\n * after a FULL walk that fully landed: marking one after an incremental window\n * would claim a history the records do not hold. */\nasync function markGroupBackfilled(calendarId: string | undefined, collections: readonly LoadedCollection[]): Promise<void> {\n const walkedAt = new Date().toISOString();\n await Promise.all(collections.map((collection) => markCalendarBackfilled(collection.dataDir, collection.schema.googleCalendar?.calendarId, walkedAt)));\n}\n\n/** What a partial window reports. Names the cause the user can actually act on:\n * a page guard is reached when Google has to expand an enormous number of\n * recurring instances, and one series with no end date is enough. */\nexport const PARTIAL_CALENDAR_WINDOW =\n \"Google returned more pages of events than one sync pass walks, so only part of the calendar was copied. \" +\n \"Give any recurring event with no end date a finite end date, then sync again.\";\n\n/** A truncated walk must not read as a completed one (#2850).\n *\n * Added AFTER the token/baseline gate, not through it. A short fetch is not a\n * failed write: the events that did arrive are Google's own and landed\n * correctly, so their baseline is true and holding it back would freeze them —\n * a record with no baseline reads as an unsent local edit, which the next pull\n * then refuses to touch (#2683). What must not happen is the walk claiming\n * completeness, and that is covered without the gate: `nextSyncToken` only\n * appears on Google's last page, so a truncated walk has no token to advance,\n * and the backfill marker is withheld separately. */\nexport const withPartialWindowError = (results: readonly CalendarCollectionSyncResult[], pagesExhausted: boolean): CalendarCollectionSyncResult[] =>\n pagesExhausted ? results.map((result) => ({ ...result, errors: [...result.errors, PARTIAL_CALENDAR_WINDOW] })) : [...results];\n\n/** What a finished window is allowed to advance.\n *\n * One pure rule rather than three conditions spread through the write-back,\n * because the three answers genuinely differ and each wrong one is a SILENT\n * data bug rather than a visible failure: a baseline held back freezes the\n * records it describes (#2683), a baseline advanced past a failed write hides\n * a conflict (#2620), a backfill marker set on a partial walk re-creates the\n * #2850 gap, and a token advanced past either loses the window for good. */\nexport interface WindowAdvance {\n /** Record what Google now says, so the next push can tell a local edit from a pull. */\n baseline: boolean;\n /** Claim these records now hold the WHOLE calendar. */\n backfill: boolean;\n /** Move the shared cursor past this window. */\n token: boolean;\n}\n\n/** `landed` is the write-side verdict (`windowFullyLanded`); the rest describe\n * the window Google returned.\n *\n * A page-capped walk (`pagesExhausted`) keeps its baseline — the events that\n * arrived are Google's own and were written correctly — but must NOT claim the\n * backfill, or the collection would stop asking for the rest of its calendar.\n * Its token is refused too, though Google makes that moot by sending\n * `nextSyncToken` only on the last page. */\nexport function windowAdvance(window: { landed: boolean; walkedInFull: boolean; pagesExhausted: boolean; nextSyncToken?: string | undefined }): WindowAdvance {\n if (!window.landed) return { baseline: false, backfill: false, token: false };\n const complete = !window.pagesExhausted;\n return { baseline: true, backfill: window.walkedInFull && complete, token: complete && window.nextSyncToken !== undefined };\n}\n\n/** The window this run should read, and whether it was a full walk. A 410\n * restart is a full walk too, so it backfills just as well as a forced one. */\nasync function readWindow(\n calendarId: string | undefined,\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n): Promise<{ result: Awaited<ReturnType<typeof syncCalendarEvents>>; walkedInFull: boolean }> {\n const accessToken = await getGoogleAccessToken();\n const resumeFrom = await resumableToken(calendarId, collections, workspaceRoot);\n const first = await syncCalendarEvents(accessToken, { calendarId, syncToken: resumeFrom });\n const result = first.fullResyncRequired ? await restartFullSync(accessToken, calendarId, workspaceRoot) : first;\n if (result.pagesExhausted) {\n log.warn(\"google\", \"the calendar walk ran out of pages — only part of it was copied\", { calendarId, events: result.events.length });\n }\n return { result, walkedInFull: resumeFrom === undefined || first.fullResyncRequired };\n}\n\nasync function syncCalendarGroupNow(\n calendarId: string | undefined,\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n): Promise<CalendarCollectionSyncResult[]> {\n // Push BEFORE the window is fetched, so a local edit is already in Google when\n // the pull reads it: the record then comes back holding Google's own canonical\n // value, and the baseline agrees with both. Pulling first would overwrite the\n // very edit that was waiting to go up (#2620).\n const unpushed = await pushAndProtect(collections, workspaceRoot);\n\n // Snapshot AFTER the push and BEFORE the window is read. After, because the\n // push writes a baseline per record it sends, and an earlier snapshot would\n // read those records as locally edited. Before, because `restartFullSync`\n // clears the baseline — the snapshot is what survives that clear and lets the\n // apply still tell an edit from an untouched record (#2684).\n const baseline = await loadCalendarShadow(calendarId, workspaceRoot);\n\n const { result, walkedInFull } = await readWindow(calendarId, collections, workspaceRoot);\n const applied = await applyWindowToGroup(collections, result.events, workspaceRoot, unpushed, baseline);\n const advance = windowAdvance({\n landed: windowFullyLanded(calendarId, applied),\n walkedInFull,\n pagesExhausted: result.pagesExhausted,\n nextSyncToken: result.nextSyncToken,\n });\n\n if (advance.baseline) await saveCalendarShadow(calendarId, shadowUpdates(result.events, heldBack(unpushed, applied), baseline), workspaceRoot);\n if (advance.backfill) await markGroupBackfilled(calendarId, collections);\n if (advance.token && result.nextSyncToken) await advanceToken(calendarId, result.nextSyncToken, collections, workspaceRoot);\n return withPartialWindowError(applied, result.pagesExhausted);\n}\n\n/** Apply one window to every collection on the calendar, honouring what each\n * one's own push protected. A collection whose protection could not be worked\n * out is not pulled at all — it reports a retryable error instead, which holds\n * the token and the baseline back for the whole group. */\nasync function applyWindowToGroup(\n collections: readonly LoadedCollection[],\n events: readonly CalendarEventSummary[],\n workspaceRoot: string,\n unpushed: UnpushedBySlug,\n baseline: Record<string, ShadowEvent>,\n): Promise<CalendarCollectionSyncResult[]> {\n const results: CalendarCollectionSyncResult[] = [];\n for (const collection of collections) {\n const protection = unpushedFor(unpushed, collection.slug);\n results.push(\n protection === null\n ? { slug: collection.slug, written: 0, removed: 0, unwritable: [], withheld: [], errors: [PROTECTION_UNKNOWN] }\n : await applyEventsToCollection(collection, events, workspaceRoot, protection, baseline),\n );\n }\n return results;\n}\n\n/** Whether the token and baseline may advance past this window.\n *\n * Google never resends a window, so advancing past a failed write would lose\n * those events for good; holding the token back just replays them next run\n * (writes are idempotent). An `unwritable` event can never succeed, so it does\n * NOT hold the token — but it is logged loudly, since it will silently never\n * appear in the collection. */\nfunction windowFullyLanded(calendarId: string | undefined, results: readonly CalendarCollectionSyncResult[]): boolean {\n // Logged per collection, not flattened across the group: a calendar can back\n // several, and \"one of them is stuck\" is unactionable without the slug — the\n // more so now that `autoPush` runs this unattended (CodeRabbit review #2666).\n results\n .filter((entry) => entry.unwritable.length > 0)\n .forEach((entry) =>\n log.warn(\"google\", \"skipping calendar events that can never be stored\", { calendarId, slug: entry.slug, unwritable: entry.unwritable }),\n );\n // A withheld event is a decision, not a failure: the record kept a local edit\n // Google has not seen, so re-fetching the window would only refuse it again.\n // The token advances past it and the BASELINE is what holds back, which is\n // what keeps the next push able to report the conflict (#2684).\n results\n .filter((entry) => entry.withheld.length > 0)\n .forEach((entry) =>\n log.warn(\"google\", \"leaving calendar records alone — they hold edits Google has not seen\", { calendarId, slug: entry.slug, withheld: entry.withheld }),\n );\n const failed = results.filter((entry) => entry.errors.length > 0);\n failed.forEach((entry) => log.warn(\"google\", \"holding back calendar sync token after failed writes\", { calendarId, slug: entry.slug, errors: entry.errors }));\n return failed.length === 0;\n}\n\n/** The baseline this window establishes: what Google now says per event, and\n * `null` for a cancelled one so a recreate cannot resume from a dead baseline.\n *\n * An event whose record the push could not send is left OUT, and that omission\n * is load-bearing. Advancing its baseline to Google's new value while the record\n * keeps the local one would make the next push read a plain one-sided edit —\n * no conflict to detect any more — and quietly overwrite Google. Held back, the\n * baseline stays older than both sides, so the conflict keeps being reported\n * until someone resolves it (#2620).\n *\n * `held` carries what those events must KEEP. Omitting them is enough on an\n * incremental run, where the file is merged rather than replaced — but a full\n * re-walk CLEARS the baseline first (`restartFullSync`), and there omission\n * drops the entry for good. The next push would then read a conflicted record\n * as a brand-new create, hit Google's duplicate-id 409 and refuse it, instead\n * of reporting the conflict it actually is. Re-stating the pre-run value makes\n * a held-back event behave the same either way (observed during Claude review;\n * no bot flagged it). */\nexport function shadowUpdates(\n events: readonly CalendarEventSummary[],\n unpushed: ReadonlySet<string> = new Set(),\n held: Record<string, ShadowEvent> = {},\n): Record<string, ShadowEvent | null> {\n const advanced = pullableEvents(events, unpushed).map((event): [string, ShadowEvent | null] => [\n event.id,\n event.status === CANCELLED_EVENT_STATUS ? null : toShadowEvent(event),\n ]);\n const kept = [...unpushed].flatMap((eventId): [string, ShadowEvent][] => (held[eventId] === undefined ? [] : [[eventId, held[eventId]]]));\n return Object.fromEntries([...kept, ...advanced]);\n}\n\n/** Save the window's token unless every collection that consumed it was deleted\n * while the sync was in flight.\n *\n * The sync opens with a `discoverCollections()` snapshot, so a delete landing\n * mid-run has already cleared this calendar's token by the time we get here\n * (`releaseOrphanedCalendarToken`). Saving anyway would resurrect exactly the\n * orphan the delete removed, and the next collection on this calendar would\n * resume from a token describing records it never received (#2428).\n *\n * One survivor is enough: it still needs the incremental position. */\nasync function advanceToken(\n calendarId: string | undefined,\n nextSyncToken: string,\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n): Promise<void> {\n if (!(await anySyncedCollectionSurvives(collections))) {\n log.info(\"google\", \"not advancing the sync token — every collection on this calendar was deleted mid-sync\", { calendarId });\n return;\n }\n await saveCalendarSyncToken(calendarId, nextSyncToken, workspaceRoot);\n}\n\nasync function pathExists(absPath: string): Promise<boolean> {\n try {\n await stat(absPath);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Liveness of the collections a sync just wrote to, checked against the skill\n * dir `deleteCollection` removes. `exists` is injected so the rule is testable\n * without a filesystem. An empty group has no survivor by definition. */\nexport async function anySyncedCollectionSurvives(\n collections: readonly Pick<LoadedCollection, \"skillDir\">[],\n exists: (absPath: string) => Promise<boolean> = pathExists,\n): Promise<boolean> {\n const alive = await Promise.all(collections.map((collection) => exists(collection.skillDir)));\n return alive.some(Boolean);\n}\n\nasync function applyEventsToCollection(\n collection: LoadedCollection,\n events: readonly CalendarEventSummary[],\n workspaceRoot: string,\n unpushed: ReadonlySet<string>,\n baseline: Record<string, ShadowEvent>,\n): Promise<CalendarCollectionSyncResult> {\n const hasUnsentEdit = unsentEditGuard(collection.schema, baseline);\n const attempts: { eventId: string; outcome: ApplyOutcome }[] = [];\n for (const event of pullableEvents(events, unpushed)) {\n attempts.push({ eventId: event.id, outcome: await applyEvent(collection, event, workspaceRoot, hasUnsentEdit) });\n }\n const outcomes = attempts.map((attempt) => attempt.outcome);\n return {\n slug: collection.slug,\n written: outcomes.filter((outcome) => outcome.kind === \"written\").length,\n removed: outcomes.filter((outcome) => outcome.kind === \"removed\").length,\n withheld: attempts.flatMap((attempt) => (attempt.outcome.kind === \"withheld\" ? [attempt.eventId] : [])),\n unwritable: outcomes.flatMap((outcome) => (outcome.kind === \"unwritable\" ? [outcome.message] : [])),\n errors: outcomes.flatMap((outcome) => (outcome.kind === \"error\" ? [outcome.message] : [])),\n };\n}\n\n/** A refresh token is what lets the scheduler run unattended; without one\n * there is nothing to sync with. */\nasync function isGoogleLinked(): Promise<boolean> {\n return Boolean((await loadGoogleTokens())?.refresh_token);\n}\n\n/** Group the declaring collections by the calendar they read, so each calendar\n * is fetched exactly once.\n *\n * Keyed by the CANONICAL id, not the declared one: an omitted `calendarId` and\n * an explicit `\"primary\"` address the same calendar and therefore share one\n * sync token, so grouping them apart would let one group advance the token out\n * from under the other — the very loss this grouping exists to prevent\n * (Codex review #2184). */\nexport function groupByCalendar(collections: readonly LoadedCollection[]): Map<string, LoadedCollection[]> {\n const groups = new Map<string, LoadedCollection[]>();\n for (const collection of collections) {\n const key = canonicalCalendarId(collection.schema.googleCalendar?.calendarId);\n groups.set(key, [...(groups.get(key) ?? []), collection]);\n }\n return groups;\n}\n\n/** The minimum a value needs for the orphan check: just the calendar it reads.\n * Structural so the rule can be exercised without building a LoadedCollection. */\nexport interface CalendarDeclaring {\n googleCalendar?: { calendarId?: string | undefined } | undefined;\n}\n\n/** The canonical calendar whose sync token nothing needs any more, or null.\n *\n * Sync tokens are keyed by calendar, NOT by collection, so a deleted\n * collection's token outlives it. Recreating a collection on the same calendar\n * then resumes from that token and receives only the delta — the new\n * collection never gets the history (#2428).\n *\n * Returns null while ANY remaining collection still reads that calendar:\n * clearing a live calendar's token costs a full re-walk on the next sync. The\n * comparison is on the CANONICAL id for the same reason `groupByCalendar` is —\n * an omitted `calendarId` and an explicit `\"primary\"` are one calendar. */\nexport function orphanedCalendarId(deleted: CalendarDeclaring, remaining: readonly CalendarDeclaring[]): string | null {\n if (!deleted.googleCalendar) return null;\n const key = canonicalCalendarId(deleted.googleCalendar.calendarId);\n const stillRead = remaining.some((other) => other.googleCalendar && canonicalCalendarId(other.googleCalendar.calendarId) === key);\n return stillRead ? null : key;\n}\n\n/** Drop the sync token of a just-deleted collection's calendar, unless another\n * collection still reads it. Call AFTER the delete lands — the check reads the\n * collections that survive it.\n *\n * Returns the cleared calendar id, or null when nothing was cleared. Never\n * throws: a failed cleanup must not fail the delete it follows. */\nexport async function releaseOrphanedCalendarToken(deleted: CalendarDeclaring, workspaceRoot: string): Promise<string | null> {\n try {\n if (!deleted.googleCalendar) return null;\n const remaining = await discoverCollections({ workspaceRoot });\n const orphaned = orphanedCalendarId(\n deleted,\n remaining.map((collection) => collection.schema),\n );\n if (orphaned === null) return null;\n await clearCalendarSyncToken(orphaned, workspaceRoot);\n await clearCalendarShadow(orphaned, workspaceRoot);\n await clearCalendarLastSyncedAt(orphaned, workspaceRoot);\n log.info(\"google\", \"cleared the sync token of a calendar no collection reads any more\", { calendarId: orphaned });\n return orphaned;\n } catch (error) {\n log.warn(\"google\", \"could not release the deleted collection's calendar sync token\", { error: String(error) });\n return null;\n }\n}\n\n/** Every declaring collection, grouped by the calendar it reads. */\nasync function declaringGroups(workspaceRoot: string): Promise<Map<string, LoadedCollection[]>> {\n const all = await discoverCollections({ workspaceRoot });\n return groupByCalendar(all.filter((collection) => collection.schema.googleCalendar));\n}\n\n/** Whether a background sync of these groups may run at all.\n *\n * Authoring the collection before linking the account is an expected state,\n * not a failure. Checking once here keeps it a quiet skip instead of an\n * access-token throw per calendar, every hour, until the user links (#2188).\n * A user-triggered sync answers differently — it says so out loud.\n *\n * Deliberately not phrased as a per-host state: the link lives in\n * `~/.config/mulmo`, which carries no app name, so every host on the machine\n * shares one answer. Reading it as \"linked on this host only\" is what made the\n * calendar look implicitly exclusive when it never was (#2678). */\nasync function backgroundSyncAllowed(groups: Map<string, LoadedCollection[]>): Promise<boolean> {\n if (groups.size === 0) return false;\n if (await isGoogleLinked()) return true;\n log.info(\"google\", \"skipping calendar sync — no Google account linked\", { calendars: groups.size });\n return false;\n}\n\n/** Run each group, isolating failures per calendar — one unreachable calendar\n * (or a revoked grant) must not stop the others. `mayClaim` is evaluated per\n * calendar as it comes up, never once for the whole map: an earlier calendar's\n * full walk takes minutes, and a decision made before it started says nothing\n * about who holds this one now. */\nasync function runCalendarGroups(\n groups: Map<string, LoadedCollection[]>,\n workspaceRoot: string,\n mayClaim: ClaimGuard = ALWAYS_CLAIM,\n): Promise<CalendarCollectionSyncResult[]> {\n const results: CalendarCollectionSyncResult[] = [];\n for (const [calendarId, collections] of groups) {\n try {\n results.push(...(await syncCalendarGroup(calendarId, collections, workspaceRoot, mayClaim)));\n } catch (error) {\n log.warn(\"google\", \"calendar sync failed\", { calendarId, error: String(error) });\n results.push(\n ...collections.map((collection) => ({ slug: collection.slug, written: 0, removed: 0, unwritable: [], withheld: [], errors: [String(error)] })),\n );\n }\n }\n return results;\n}\n\n/** Sync every collection whose calendar is due — no host in this workspace has\n * started one within `intervalMs` (#2678). Without that gate this walked every\n * declaring group on every tick, so a second host registering the same task\n * simply doubled the runs, concurrently.\n *\n * Dueness is not decided here, only described: the guard is handed down and\n * evaluated where the marker is written, so the answer cannot go stale between\n * deciding and claiming (Codex review #2680). */\nexport async function syncDueCalendarCollections(\n workspaceRoot: string,\n intervalMs: number = DEFAULT_SYNC_INTERVAL_MS,\n): Promise<CalendarCollectionSyncResult[]> {\n const groups = await declaringGroups(workspaceRoot);\n if (!(await backgroundSyncAllowed(groups))) return [];\n const windowMs = calendarSyncDueWindowMs(intervalMs);\n return await runCalendarGroups(groups, workspaceRoot, (lastSyncedAt) => isCalendarSyncDue(lastSyncedAt, windowMs));\n}\n\n/** The groups holding a collection that still needs the whole calendar.\n *\n * This used to ask whether the CALENDAR had a stored token, which is not the\n * same question and is why the trigger silently did nothing for the #2850\n * reporter: their calendar already had a token — from the standalone `google`\n * tool, and on later attempts from the collection they had just deleted by\n * hand — so the brand-new collection matched nothing and its first sync never\n * ran at all. `pending` is injected so the rule is testable without a\n * workspace on disk.\n *\n * Still self-silencing: a landed full walk marks its collections, so a group\n * stops matching once every collection in it holds the history. */\nexport async function groupsNeedingBackfill<T>(groups: Map<string, T>, pending: (value: T) => Promise<boolean>): Promise<Map<string, T>> {\n const checked = await Promise.all([...groups].map(async (entry) => ((await pending(entry[1])) ? entry : null)));\n return new Map(checked.filter((entry): entry is [string, T] => entry !== null));\n}\n\n/** Sync only the calendars a collection has never received in full — the first\n * sync for a just-created collection, which otherwise stays empty until the\n * hourly scheduler run (#2427). Cheap and safe to call on every config write. */\nexport async function syncNewCalendarCollections(workspaceRoot: string): Promise<CalendarCollectionSyncResult[]> {\n const groups = await declaringGroups(workspaceRoot);\n const pending = await groupsNeedingBackfill(groups, async (collections) => (await collectionsNeedingBackfill(collections)).length > 0);\n if (!(await backgroundSyncAllowed(pending))) return [];\n log.info(\"google\", \"running the first sync for calendars a collection has never received in full\", { calendars: [...pending.keys()] });\n return await runCalendarGroups(pending, workspaceRoot);\n}\n\n/** A user-triggered sync's outcome. `not-a-calendar` and `not-linked` are\n * states the caller must report rather than swallow: a Refresh click that\n * quietly returns \"0 written\" reads as an empty calendar, not as a setup gap. */\nexport type ManualCalendarSyncOutcome = { kind: \"synced\"; results: CalendarCollectionSyncResult[] } | { kind: \"not-a-calendar\" } | { kind: \"not-linked\" };\n\n/** The I/O a manual sync crosses, injectable so the three outcomes can be\n * exercised with fakes instead of a workspace on disk and a live Google grant\n * (CodeRabbit review #2566). */\nexport interface ManualCalendarSyncDeps {\n loadGroups: (workspaceRoot: string) => Promise<Map<string, LoadedCollection[]>>;\n isLinked: () => Promise<boolean>;\n runGroups: (groups: Map<string, LoadedCollection[]>, workspaceRoot: string) => Promise<CalendarCollectionSyncResult[]>;\n}\n\nconst liveManualSyncDeps: ManualCalendarSyncDeps = { loadGroups: declaringGroups, isLinked: isGoogleLinked, runGroups: runCalendarGroups };\n\n/** Sync the calendar ONE collection reads, on demand (the Refresh button).\n *\n * Deliberately syncs the whole group, not just `slug`: the sync token is keyed\n * by calendar, so consuming a window for one collection would leave the others\n * on that calendar reading an already-consumed one. Returns every result of the\n * group so the caller can report the requested slug's own counts.\n *\n * \"Does this collection sync at all\" is answered BEFORE \"is Google linked\":\n * telling someone to link their account for a collection that never declared a\n * calendar sends them fixing the wrong thing. */\nexport async function syncCalendarForCollection(\n slug: string,\n workspaceRoot: string,\n deps: ManualCalendarSyncDeps = liveManualSyncDeps,\n): Promise<ManualCalendarSyncOutcome> {\n const groups = await deps.loadGroups(workspaceRoot);\n const owning = [...groups].filter(([, collections]) => collections.some((collection) => collection.slug === slug));\n if (owning.length === 0) return { kind: \"not-a-calendar\" };\n if (!(await deps.isLinked())) return { kind: \"not-linked\" };\n return { kind: \"synced\", results: await deps.runGroups(new Map(owning), workspaceRoot) };\n}\n\n/** The interval this task is scheduled on right now. A non-interval schedule\n * cannot be reached — the factory below builds an interval one — but the type\n * allows it, so it falls back rather than asserting. */\nfunction scheduledIntervalMs(task: SystemTaskDef): number {\n return task.schedule.type === SCHEDULE_TYPES.interval ? task.schedule.intervalMs : DEFAULT_SYNC_INTERVAL_MS;\n}\n\n/** Scheduler registration, shaped like `feedRefreshTaskDef` so hosts wire it\n * with a single line.\n *\n * `run` reads the interval back off the definition instead of closing over the\n * option: a host rewrites `schedule` from its own overrides file AFTER this\n * returns, and the due window has to follow it. Frozen at the default, a\n * shortened interval would tick often and skip nearly every tick. */\nexport function googleCalendarSyncTaskDef(opts?: { workspaceRoot?: string; intervalMs?: number }): SystemTaskDef {\n const def: SystemTaskDef = {\n id: GOOGLE_CALENDAR_SYNC_TASK_ID,\n name: \"Google Calendar sync\",\n description: \"Pulls changed Google Calendar events into any collection declaring `googleCalendar`, without invoking the LLM.\",\n schedule: { type: SCHEDULE_TYPES.interval, intervalMs: opts?.intervalMs ?? DEFAULT_SYNC_INTERVAL_MS },\n missedRunPolicy: MISSED_RUN_POLICIES.runOnce,\n run: () => syncDueCalendarCollections(opts?.workspaceRoot ?? getWorkspaceRoot(), scheduledIntervalMs(def)).then(() => {}),\n };\n return def;\n}\n","// Google Tasks v1 REST calls. `@default` is Google's alias for the user's\n// default task list, so callers can stay list-agnostic.\nimport { asRecord, googleRequest, itemsOf, stringField, DEFAULT_LIST_MAX_RESULTS } from \"./apiClient.js\";\n\nconst TASKS_BASE_URL = \"https://tasks.googleapis.com/tasks/v1\";\nconst TASKS_API_LABEL = \"Google Tasks API\";\nconst DEFAULT_TASK_LIST_ID = \"@default\";\nconst TASK_STATUS_COMPLETED = \"completed\";\nconst TASK_STATUS_NEEDS_ACTION = \"needsAction\";\nconst MAX_TASK_LISTS = 50;\n\nexport interface TaskListSummary {\n id: string;\n title: string;\n}\n\nexport interface TaskSummary {\n id: string;\n title: string;\n status: string;\n due: string;\n notes: string;\n}\n\nexport interface ListTasksInput {\n taskListId?: string | undefined;\n maxResults?: number | undefined;\n showCompleted?: boolean | undefined;\n}\n\nexport interface CreateTaskInput {\n title: string;\n notes?: string | undefined;\n /** RFC3339. Google stores a DATE only — the time part is recorded but\n * ignored by the UI, so callers should not promise time-of-day fidelity. */\n due?: string | undefined;\n taskListId?: string | undefined;\n}\n\nexport interface CompleteTaskInput {\n taskId: string;\n taskListId?: string | undefined;\n}\n\nexport interface UpdateTaskInput {\n taskId: string;\n title?: string | undefined;\n /** `\"\"` clears the notes; omit to leave them untouched. */\n notes?: string | undefined;\n /** RFC3339. Same DATE-only caveat as `CreateTaskInput.due`. Google rejects\n * `\"\"`, so a due date can be changed but not removed through this call. */\n due?: string | undefined;\n taskListId?: string | undefined;\n}\n\nexport const toTaskListSummary = (value: unknown): TaskListSummary => {\n const record = asRecord(value);\n return { id: stringField(record, \"id\"), title: stringField(record, \"title\") };\n};\n\nexport const toTaskSummary = (value: unknown): TaskSummary => {\n const record = asRecord(value);\n return {\n id: stringField(record, \"id\"),\n title: stringField(record, \"title\"),\n status: stringField(record, \"status\"),\n due: stringField(record, \"due\"),\n notes: stringField(record, \"notes\"),\n };\n};\n\n/** Resolve a declared taskListId to the one the API addresses. `||` (not `??`)\n * so a blank string also falls back instead of building a malformed\n * `/lists//tasks` URL — the same rule, and the same reason, as\n * `canonicalCalendarId`. */\nexport const canonicalTaskListId = (taskListId: string | undefined): string => taskListId?.trim() || DEFAULT_TASK_LIST_ID;\n\nconst tasksUrl = (taskListId: string | undefined, suffix = \"\"): string =>\n `${TASKS_BASE_URL}/lists/${encodeURIComponent(canonicalTaskListId(taskListId))}/tasks${suffix}`;\n\nexport async function listTaskLists(accessToken: string): Promise<TaskListSummary[]> {\n const listed = await googleRequest(TASKS_API_LABEL, accessToken, `${TASKS_BASE_URL}/users/@me/lists?maxResults=${MAX_TASK_LISTS}`);\n return itemsOf(listed).map(toTaskListSummary);\n}\n\nexport async function listTasks(accessToken: string, input: ListTasksInput = {}): Promise<TaskSummary[]> {\n const params = new URLSearchParams({\n maxResults: String(input.maxResults ?? DEFAULT_LIST_MAX_RESULTS),\n showCompleted: String(input.showCompleted ?? false),\n });\n const listed = await googleRequest(TASKS_API_LABEL, accessToken, `${tasksUrl(input.taskListId)}?${params.toString()}`);\n return itemsOf(listed).map(toTaskSummary);\n}\n\nexport async function createTask(accessToken: string, input: CreateTaskInput): Promise<TaskSummary> {\n const body = { title: input.title, notes: input.notes, due: input.due };\n const created = await googleRequest(TASKS_API_LABEL, accessToken, tasksUrl(input.taskListId), { method: \"POST\", body: JSON.stringify(body) });\n return toTaskSummary(created);\n}\n\n/** PATCH body for a task edit — only the fields the caller supplied. As with\n * events, `undefined` means \"leave as is\" and `\"\"` means \"clear it\", so the\n * two must stay distinct. `status` is deliberately absent: `completeTask` /\n * `uncompleteTask` own that transition, and two ways to set it would drift\n * apart. */\nexport const buildTaskPatch = (input: UpdateTaskInput): Record<string, unknown> => ({\n ...(input.title !== undefined ? { title: input.title } : {}),\n ...(input.notes !== undefined ? { notes: input.notes } : {}),\n ...(input.due !== undefined ? { due: input.due } : {}),\n});\n\nexport async function updateTask(accessToken: string, input: UpdateTaskInput): Promise<TaskSummary> {\n const url = tasksUrl(input.taskListId, `/${encodeURIComponent(input.taskId)}`);\n const updated = await googleRequest(TASKS_API_LABEL, accessToken, url, { method: \"PATCH\", body: JSON.stringify(buildTaskPatch(input)) });\n return toTaskSummary(updated);\n}\n\n/** The only two states this module transitions a task between. Spelled as a\n * union rather than `string` so the compiler, not just the doc comment,\n * enforces that there is no third one. */\ntype TaskStatus = typeof TASK_STATUS_COMPLETED | typeof TASK_STATUS_NEEDS_ACTION;\n\n/** Shared body of the two status transitions. PATCH keeps the rest of the task\n * intact — a PUT would need the full body and would silently drop fields the\n * caller never read. Private on purpose: the exported `completeTask` /\n * `uncompleteTask` names are the API, so no caller can invent a third target\n * state by passing an arbitrary status through. */\nasync function patchTaskStatus(accessToken: string, input: CompleteTaskInput, status: TaskStatus): Promise<TaskSummary> {\n const url = tasksUrl(input.taskListId, `/${encodeURIComponent(input.taskId)}`);\n const updated = await googleRequest(TASKS_API_LABEL, accessToken, url, { method: \"PATCH\", body: JSON.stringify({ status }) });\n return toTaskSummary(updated);\n}\n\nexport async function completeTask(accessToken: string, input: CompleteTaskInput): Promise<TaskSummary> {\n return patchTaskStatus(accessToken, input, TASK_STATUS_COMPLETED);\n}\n\n/** Send a completed task back to the to-do list.\n *\n * Its own function rather than a flag on `completeTask`, and deliberately not\n * a `status` field on `updateTask`: one kind per target state keeps the name\n * honest and leaves exactly one code path setting each value.\n *\n * The patch carries `status` alone — mirroring `completeTask`, which also\n * sets only `status` and lets Google fill in the `completed` timestamp. Note\n * that whether Google *clears* that timestamp on the way back is its\n * behaviour, not ours, and is unverified here: `TaskSummary` doesn't carry\n * `completed`, so nothing in this codebase would show a stale one. If a\n * reopened task ever displays a completion date in Google's own UI, this is\n * the place to add `completed: null` to the patch. */\nexport async function uncompleteTask(accessToken: string, input: CompleteTaskInput): Promise<TaskSummary> {\n return patchTaskStatus(accessToken, input, TASK_STATUS_NEEDS_ACTION);\n}\n\nexport async function deleteTask(accessToken: string, input: CompleteTaskInput): Promise<void> {\n const url = tasksUrl(input.taskListId, `/${encodeURIComponent(input.taskId)}`);\n await googleRequest(TASKS_API_LABEL, accessToken, url, { method: \"DELETE\" });\n}\n","// Google Drive v3 REST calls under the `drive.file` scope — the app only\n// ever sees files IT created, never the user's wider Drive. That narrow\n// scope is why this is non-sensitive and needs no Google verification\n// review; keep every call inside it.\nimport { randomBytes } from \"node:crypto\";\nimport { asRecord, googleRequest, stringField, DEFAULT_LIST_MAX_RESULTS } from \"./apiClient.js\";\n\nconst DRIVE_FILES_URL = \"https://www.googleapis.com/drive/v3/files\";\nconst DRIVE_UPLOAD_URL = \"https://www.googleapis.com/upload/drive/v3/files\";\nconst DRIVE_API_LABEL = \"Google Drive API\";\nconst DEFAULT_MIME_TYPE = \"text/plain\";\nconst FILE_FIELDS = \"id,name,mimeType,webViewLink,modifiedTime\";\n// Reading a huge blob into a tool result would blow the context window; the\n// tool surface is for app-created text documents, not media.\nconst MAX_READ_CHARS = 100_000;\nconst TEXT_MIME_PREFIXES = [\"text/\", \"application/json\", \"application/xml\", \"application/javascript\"];\n\nexport interface DriveFileSummary {\n id: string;\n name: string;\n mimeType: string;\n webViewLink: string;\n modifiedTime: string;\n}\n\nexport interface ListDriveFilesInput {\n maxResults?: number | undefined;\n}\n\nexport interface CreateDriveFileInput {\n name: string;\n content: string;\n mimeType?: string | undefined;\n}\n\nexport interface ReadDriveFileInput {\n fileId: string;\n}\n\nexport const toDriveFileSummary = (value: unknown): DriveFileSummary => {\n const record = asRecord(value);\n return {\n id: stringField(record, \"id\"),\n name: stringField(record, \"name\"),\n mimeType: stringField(record, \"mimeType\"),\n webViewLink: stringField(record, \"webViewLink\"),\n modifiedTime: stringField(record, \"modifiedTime\"),\n };\n};\n\nexport const isTextMimeType = (mimeType: string): boolean => TEXT_MIME_PREFIXES.some((prefix) => mimeType.startsWith(prefix));\n\nexport async function listDriveFiles(accessToken: string, input: ListDriveFilesInput = {}): Promise<DriveFileSummary[]> {\n const params = new URLSearchParams({\n pageSize: String(input.maxResults ?? DEFAULT_LIST_MAX_RESULTS),\n fields: `files(${FILE_FIELDS})`,\n orderBy: \"modifiedTime desc\",\n });\n const listed = await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}?${params.toString()}`);\n const record = asRecord(listed);\n const files = Array.isArray(record.files) ? record.files : [];\n return files.map(toDriveFileSummary);\n}\n\n// Multipart upload: metadata part + media part in one request. Built by hand\n// because the `googleapis` SDK is the only alternative and this is the sole\n// multipart call in the engine.\nconst BOUNDARY_BYTES = 16;\n\n/** Fresh per request: a fixed boundary appearing inside file content would\n * split the payload at the wrong place, so Drive would store a truncated or\n * mangled file. Random 128 bits makes an accidental — or crafted — collision\n * infeasible; `newMultipartBoundary` is re-derived until it is absent from\n * the body it must delimit. */\nconst newMultipartBoundary = (): string => `mulmo-drive-${randomBytes(BOUNDARY_BYTES).toString(\"hex\")}`;\n\n// RFC 2045 token + parameters: no CR/LF (header injection into the part\n// headers) and no quotes/semicolons that would let a crafted value forge\n// extra headers or parameters.\nconst MIME_TYPE_RE = /^[A-Za-z0-9!#$&^_.+-]+\\/[A-Za-z0-9!#$&^_.+-]+$/;\n\nexport const assertSafeMimeType = (mimeType: string): string => {\n if (!MIME_TYPE_RE.test(mimeType)) throw new Error(`Google Drive API: invalid mimeType '${mimeType}' — expected a plain type/subtype such as text/plain`);\n return mimeType;\n};\n\nexport const buildMultipartBody = (metadata: Record<string, string>, content: string, mimeType: string, boundary: string): string =>\n [\n `--${boundary}`,\n \"Content-Type: application/json; charset=UTF-8\",\n \"\",\n JSON.stringify(metadata),\n `--${boundary}`,\n `Content-Type: ${mimeType}`,\n \"\",\n content,\n `--${boundary}--`,\n \"\",\n ].join(\"\\r\\n\");\n\n/** A boundary that appears nowhere in the parts it delimits. */\nexport const pickBoundary = (parts: string[], generate: () => string = newMultipartBoundary): string => {\n const collides = (candidate: string): boolean => parts.some((part) => part.includes(candidate));\n let boundary = generate();\n while (collides(boundary)) boundary = generate();\n return boundary;\n};\n\nexport async function createDriveFile(accessToken: string, input: CreateDriveFileInput): Promise<DriveFileSummary> {\n const mimeType = assertSafeMimeType(input.mimeType ?? DEFAULT_MIME_TYPE);\n const metadata = { name: input.name, mimeType };\n const boundary = pickBoundary([input.content, JSON.stringify(metadata)]);\n const params = new URLSearchParams({ uploadType: \"multipart\", fields: FILE_FIELDS });\n const created = await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_UPLOAD_URL}?${params.toString()}`, {\n method: \"POST\",\n contentType: `multipart/related; boundary=${boundary}`,\n body: buildMultipartBody(metadata, input.content, mimeType, boundary),\n });\n return toDriveFileSummary(created);\n}\n\nexport async function readDriveFile(accessToken: string, input: ReadDriveFileInput): Promise<{ file: DriveFileSummary; content: string }> {\n const fileId = encodeURIComponent(input.fileId);\n const metadata = await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}/${fileId}?fields=${FILE_FIELDS}`);\n const file = toDriveFileSummary(metadata);\n if (!isTextMimeType(file.mimeType)) {\n throw new Error(`Google Drive API: '${file.name}' is ${file.mimeType || \"a binary file\"} — only text files can be read as content`);\n }\n const raw = await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}/${fileId}?alt=media`, { expectText: true });\n const content = typeof raw === \"string\" ? raw.slice(0, MAX_READ_CHARS) : \"\";\n return { file, content };\n}\n\nexport async function deleteDriveFile(accessToken: string, input: ReadDriveFileInput): Promise<void> {\n await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}/${encodeURIComponent(input.fileId)}`, { method: \"DELETE\" });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../src/google/host.ts","../../src/google/datetime.ts","../../src/google/deletePlan.ts","../../src/google/util.ts","../../src/google/eventDerived.ts","../../src/google/eventSpanInput.ts","../../src/google/paths.ts","../../src/google/clientSecret.ts","../../src/google/fsJson.ts","../../src/google/tokenStore.ts","../../src/google/broker.ts","../../src/google/auth.ts","../../src/google/authFlow.ts","../../src/google/apiClient.ts","../../src/google/calendar.ts","../../src/google/calendarStateLock.ts","../../src/google/calendarSyncStore.ts","../../src/google/calendarSyncDue.ts","../../src/google/calendarBackfillState.ts","../../src/google/calendarPushState.ts","../../src/google/calendarLock.ts","../../src/google/pushDateTime.ts","../../src/google/collectionDateTime.ts","../../src/google/collectionProjection.ts","../../src/google/pushPlan.ts","../../src/google/collectionPush.ts","../../src/google/collectionSync.ts","../../src/google/tasks.ts","../../src/google/driveFile.ts"],"sourcesContent":["// Host binding for the Google engine. The engine logs through the host's\n// logger, but a package-level import of the host logger would be an uphill\n// dependency — so the host injects it once at startup (same pattern as\n// `collection/server/host.ts`). The default is silent so the engine works\n// unconfigured in unit tests.\n\nimport { createForwardingLogger, createHostSlot, type StructuredLogger } from \"../host/hostSlot.js\";\n\n/** Public alias of the shared `StructuredLogger` — keeps the domain surface name-stable. */\nexport type GoogleLogger = StructuredLogger;\n\nconst hostSlot = createHostSlot<GoogleLogger>(\"@mulmoclaude/core/google: configureGoogleHost()\");\n\nexport function configureGoogleHost(binding: { log: GoogleLogger }): void {\n hostSlot.set(binding.log);\n}\n\nexport const log: GoogleLogger = createForwardingLogger(() => hostSlot.peek());\n","// Strict RFC3339 date-time validation shared by every surface that feeds\n// Calendar `dateTime`/`timeMin` values (agent tool args, remote-host command\n// params). Calendar rejects date-only / offset-less values with an opaque\n// 400, so callers validate here and return an actionable message instead.\n\n// Fractional seconds are normalized away first — an optional `(\\.\\d+)?`\n// group inside the main pattern trips security/detect-unsafe-regex.\nconst ISO_DATE_TIME_WITH_OFFSET_RE = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:Z|[+-](\\d{2}):(\\d{2}))$/;\nconst FRACTIONAL_SECONDS_RE = /\\.\\d+(?=Z|[+-])/;\n\nconst MAX_HOUR = 23;\nconst MAX_MINUTE = 59;\nconst MAX_SECOND = 59;\n\n// JS Date normalizes overflowed components (2026-02-31 parses as\n// 2026-03-03), so `new Date(value)` alone cannot reject impossible dates —\n// a UTC round-trip of the raw components can.\nconst isRealCalendarDate = (year: number, month: number, day: number): boolean => {\n const roundTrip = new Date(Date.UTC(year, month - 1, day));\n return roundTrip.getUTCFullYear() === year && roundTrip.getUTCMonth() === month - 1 && roundTrip.getUTCDate() === day;\n};\n\nexport const isIsoDateTimeWithOffset = (value: string): boolean => {\n const match = ISO_DATE_TIME_WITH_OFFSET_RE.exec(value.replace(FRACTIONAL_SECONDS_RE, \"\"));\n if (!match) return false;\n const [year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0] = match.slice(1, 7).map(Number);\n const timeInRange = hour <= MAX_HOUR && minute <= MAX_MINUTE && second <= MAX_SECOND;\n // RFC3339 bounds the offset to 00-23:59; groups 7/8 are undefined for \"Z\".\n const offsetInRange = match[7] === undefined || (Number(match[7]) <= MAX_HOUR && Number(match[8]) <= MAX_MINUTE);\n return isRealCalendarDate(year, month, day) && timeInRange && offsetInRange;\n};\n","// Whether a record deleted in the collection may be deleted in Google (#3234).\n//\n// The push has always REPORTED a local deletion and left Google alone, because\n// `events.delete` removes the event for everyone it was sent to and cannot be\n// undone from here. A calendar whose collection is the primary copy needs the\n// deletion to carry, though — otherwise the next pull brings the record back\n// and the user deletes it again, forever.\n//\n// So propagation is opt-in per collection, and even then it stops at an event\n// that carries attendees: deleting an invited event withdraws it from the\n// guests' calendars, which is a different act from tidying your own. The rule\n// is blunter than that reason — see `planDelete` for why an event only the\n// user was ever on is refused too. That guard is here, pure, because it is the\n// whole safety of the feature — and it answers from what Google currently\n// holds, never from the record, which by then is gone.\n//\n// Pure: no I/O, no clock, no locale.\n\n/** Why an event was left in Google although its record is gone. */\nexport type DeleteRefusal = { kind: \"has-attendees\" } | { kind: \"already-gone\" };\n\n/** An approval carries the version it was made against, so the delete can be\n * conditional on it. Keeping the two together is what stops a caller deleting\n * against a version the guard never saw. */\nexport type DeleteDecision = { ok: true; etag: string } | ({ ok: false } & DeleteRefusal);\n\nconst ATTENDEES_REASON = \"it carries attendees — deleting an invited event withdraws it from their calendars too\";\nconst ALREADY_GONE_REASON = \"it is no longer in Google\";\n\n/** The message the push reports for a refusal, with the event named. */\nexport const deleteRefusalMessage = (eventId: string, refusal: DeleteRefusal): string =>\n `${eventId}: left in Google because ${refusal.kind === \"has-attendees\" ? ATTENDEES_REASON : ALREADY_GONE_REASON}`;\n\n/** Decide one deletion against what Google currently holds.\n *\n * `null` means the event is already absent — a 404/410 on the read. Nothing to\n * delete, and reported apart from a refusal so \"we chose not to\" never reads\n * as \"we could not\".\n *\n * ANY attendee refuses, including the entry Google adds for the organiser. A\n * rule that tried to exclude \"only me\" would have to work out which entry is\n * the user from a payload that may not say, and being wrong there withdraws a\n * real invitation. Solo events — the ones this feature exists for — carry no\n * attendees at all, so the blunt rule costs them nothing. */\nexport function planDelete(fetched: { attendeeCount: number; etag: string } | null): DeleteDecision {\n if (fetched === null) return { ok: false, kind: \"already-gone\" };\n return fetched.attendeeCount > 0 ? { ok: false, kind: \"has-attendees\" } : { ok: true, etag: fetched.etag };\n}\n","// Time + type helpers kept local so the Google engine carries no host\n// dependency. `errorMessage` / `truncate` now come from the canonical core\n// helpers (#2217): the local copies had drifted — `errorMessage` collapsed\n// gRPC-shaped errors to \"unknown error\" instead of surfacing their `details`,\n// and `truncate` dropped the guard that keeps output within `max`.\nexport { errorMessage } from \"../utils/errors.js\";\nexport { truncate } from \"../utils/text.js\";\n\nexport const ONE_SECOND_MS = 1_000;\nexport const ONE_MINUTE_MS = 60_000;\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","// Google event fields that are ARRAYS, folded to the one scalar a collection\n// column can hold (#3233).\n//\n// A collection field is a scalar (`schemaZ.ts`), and `map` names a key of\n// `CalendarEventSummary` — so `attendees` (an array) and `conferenceData` (an\n// array inside an object) cannot be mapped at all until something decides which\n// single value they become. These are those decisions, kept pure and apart from\n// the projection so each is testable against the real Google shapes.\n//\n// Both fold toward the ONE question the collection asks of them: \"is this on my\n// schedule?\" and \"where do I join?\". Everything else in those structures is\n// dropped, deliberately — a column holding the whole attendee list would satisfy\n// neither question, because `where` resolves only top-level record fields.\n//\n// Pure: no I/O, no clock, no locale.\nimport { isRecord } from \"./util.js\";\n\nconst stringAt = (record: Record<string, unknown>, key: string): string => (typeof record[key] === \"string\" ? record[key] : \"\");\n\nconst recordsIn = (value: unknown): Record<string, unknown>[] => (Array.isArray(value) ? value.filter(isRecord) : []);\n\n/** The signed-in user's own `responseStatus` on this event, `\"\"` when Google\n * reported none.\n *\n * `\"\"` is the common case, not an edge one: an event with no attendees has no\n * entry to mark `self`, which is most of a personal calendar. So it means\n * \"Google said nothing\", never \"not going\" — filter with `!= \"declined\"`, not\n * with `== \"accepted\"`, or every solo event disappears too.\n *\n * Google's values: `needsAction`, `declined`, `tentative`, `accepted`. */\nexport function selfResponseStatus(attendees: unknown): string {\n const self = recordsIn(attendees).find((attendee) => attendee.self === true);\n return self === undefined ? \"\" : stringAt(self, \"responseStatus\");\n}\n\nconst VIDEO_ENTRY_POINT = \"video\";\n\n/** The URL that joins this event's meeting, `\"\"` when it has none.\n *\n * Only the `video` entry point. The others are a phone number and a dial-in\n * PIN page, and a column named for joining that sometimes holds `tel:` would\n * be worse than one that is empty — the caller can see empty and fall back.\n *\n * `hangoutLink` (#3229) already carries this for Google Meet; this is what\n * reaches the events whose conference is Zoom or Teams. */\nexport function conferenceVideoUri(conferenceData: unknown): string {\n if (!isRecord(conferenceData)) return \"\";\n const video = recordsIn(conferenceData.entryPoints).find((entry) => entry.entryPointType === VIDEO_ENTRY_POINT);\n return video === undefined ? \"\" : stringAt(video, \"uri\");\n}\n","// A caller-supplied event span → the pair of times the Calendar body carries.\n//\n// Every write surface (the `google` tool, the remote-host command channel)\n// takes a span as two strings. Calendar accepts two shapes and rejects the mix\n// with an opaque 400, so the rule lives here once rather than in each surface:\n// a timed event is RFC3339 WITH an offset on both ends, an all-day event is a\n// bare `YYYY-MM-DD` on both ends.\n//\n// Google's all-day `end` is EXCLUSIVE — a single day on the 17th ends on the\n// 18th. It is passed through untouched: `toEventSummary` reports Google's raw\n// value and `pushDateTime.allDayFrom` round-trips it, so converting an\n// inclusive end on the way in would shorten the event by a day on every\n// read-modify-write. An end that is not after the start is reported here, where\n// the caller can be told the rule, instead of at Google.\n//\n// Pure: no I/O, no clock, no locale.\nimport { parseIsoDate } from \"../collection/core/calendarGrid.js\";\nimport type { CalendarEventTime } from \"./calendar.js\";\nimport { isIsoDateTimeWithOffset } from \"./datetime.js\";\n\nconst DATE_ONLY_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** Whether `value` is a bare calendar day — the all-day spelling of an event\n * time, and the one shape `isIsoDateTimeWithOffset` is built to refuse.\n *\n * Realness comes from the parser the record lint, the calendar grid and the\n * push already share, so every surface agrees that `2026-02-30` is not a day.\n * The regex runs first because that parser trims, and a value with whitespace\n * around it would validate here and then reach Google verbatim. */\nexport const isCalendarDateOnly = (value: string): boolean => DATE_ONLY_RE.test(value) && parseIsoDate(value) !== null;\n\n/** One end of a span, or null when it is neither shape Calendar accepts. */\nexport const toEventTimeInput = (value: string): CalendarEventTime | null => {\n if (isCalendarDateOnly(value)) return { date: value };\n return isIsoDateTimeWithOffset(value) ? { dateTime: value } : null;\n};\n\n/** The rule every surface states for a value that is neither shape. Shared so\n * the tool, the remote host and their tests cannot drift apart. */\nexport const EVENT_TIME_HINT =\n \"must be an ISO 8601 date-time with a timezone offset (e.g. 2026-07-17T09:00:00+09:00), or a date for an all-day event (e.g. 2026-07-17)\";\n\n/** The same rule with the offending key named, for a surface whose error text\n * carries no path of its own. */\nexport const eventTimeHint = (key: string): string => `${key} ${EVENT_TIME_HINT}`;\n\n/** All-day `end` is exclusive, so a one-day event ends on the NEXT day. Spelled\n * out because the natural reading of \"all day on the 17th\" is `end` = the\n * 17th, which Calendar refuses. */\nexport const ALL_DAY_END_HINT = \"an all-day `end` is EXCLUSIVE — it is the day AFTER the last day, so a single day on 2026-07-17 needs end 2026-07-18\";\n\nexport const MIXED_SPAN_HINT = \"start and end must both be date-times with an offset, or both be dates for an all-day event — not one of each\";\n\n/** A lone all-day end cannot be patched: the stored event's kind is unknown\n * here, and giving one end of a timed event a `date` makes an event Calendar\n * rejects — with a message that names neither end. Both together are\n * unambiguous, so that is what the caller is asked for. */\nexport const LONE_ALL_DAY_HINT = `pass BOTH start and end to move an all-day event — ${ALL_DAY_END_HINT}`;\n\nconst isAllDay = (time: CalendarEventTime): time is { date: string } => \"date\" in time;\n\nexport interface SpanTimes {\n start: CalendarEventTime;\n end: CalendarEventTime;\n}\nexport type SpanResult = { ok: true; span: SpanTimes } | { ok: false; reason: string };\n\n/** Both ends of a create, validated together.\n *\n * Only the all-day ordering is checked: a timed span's ordering depends on the\n * two offsets, which Calendar resolves and reports on clearly, while an all-day\n * `end <= start` comes back as an opaque 400. Lexicographic comparison is exact\n * for `YYYY-MM-DD` — fixed-width, most significant part first. */\nexport function resolveSpanInput(start: string, end: string): SpanResult {\n const startTime = toEventTimeInput(start);\n if (startTime === null) return { ok: false, reason: eventTimeHint(\"start\") };\n const endTime = toEventTimeInput(end);\n if (endTime === null) return { ok: false, reason: eventTimeHint(\"end\") };\n if (isAllDay(startTime) !== isAllDay(endTime)) return { ok: false, reason: MIXED_SPAN_HINT };\n if (isAllDay(startTime) && isAllDay(endTime) && endTime.date <= startTime.date) return { ok: false, reason: ALL_DAY_END_HINT };\n return { ok: true, span: { start: startTime, end: endTime } };\n}\n\nexport interface PartialSpanTimes {\n start?: CalendarEventTime;\n end?: CalendarEventTime;\n}\nexport type PartialSpanResult = { ok: true; times: PartialSpanTimes } | { ok: false; reason: string };\n\n/** One end of an EDIT, where the other end is absent. */\nfunction resolveLoneEnd(key: \"start\" | \"end\", value: string): PartialSpanResult {\n const time = toEventTimeInput(value);\n if (time === null) return { ok: false, reason: eventTimeHint(key) };\n if (isAllDay(time)) return { ok: false, reason: LONE_ALL_DAY_HINT };\n return { ok: true, times: { [key]: time } };\n}\n\n/** The span of an EDIT, where either end may be absent. */\nexport function resolvePartialSpanInput(start: string | undefined, end: string | undefined): PartialSpanResult {\n if (start !== undefined && end !== undefined) {\n const resolved = resolveSpanInput(start, end);\n return resolved.ok ? { ok: true, times: resolved.span } : resolved;\n }\n if (start !== undefined) return resolveLoneEnd(\"start\", start);\n if (end !== undefined) return resolveLoneEnd(\"end\", end);\n return { ok: true, times: {} };\n}\n","// Google OAuth material lives OUTSIDE the workspace: the client secret is\n// machine-only (never synced) and the refresh token must survive workspace\n// resets — same reasoning as the gcloud / gh CLI model. The dir is the\n// host-NEUTRAL `~/.config/mulmo` because this engine is shared by both\n// MulmoClaude and MulmoTerminal, which deliberately share one grant per\n// machine. The `home` parameter exists so tests can thread a fake home.\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { mulmoConfigDir } from \"../global-config/paths.js\";\n\n/** Same directory as every other cross-app per-user file — see\n * `../global-config/paths.ts`, which owns the spelling. */\nexport function googleConfigDir(home?: string): string {\n return mulmoConfigDir(home);\n}\n\n/** Pre-0.20.1 token dir (mulmoclaude-branded); reads migrate away from it. */\nexport function legacyGoogleTokenPath(home?: string): string {\n return join(home ?? homedir(), \".config\", \"mulmoclaude\", \"google-token.json\");\n}\n\nexport function googleTokenPath(home?: string): string {\n return join(googleConfigDir(home), \"google-token.json\");\n}\n\nexport function googleSecretsDir(home?: string): string {\n return join(home ?? homedir(), \".secrets\");\n}\n","// Loads the Google OAuth **desktop-app** client credentials a user may have\n// downloaded from the Cloud Console into `~/.secrets/client_secret_*.json`.\n// Files are discovered by prefix so nobody has to rename Google's long default\n// filename.\n//\n// Only `{\"installed\": …}` (desktop) files count. A `{\"web\": …}` client cannot\n// drive the loopback consent this engine runs — and one legitimately sits in\n// the same directory for anyone who also deploys the broker — so web clients\n// are skipped rather than treated as a competing choice.\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { isRecord } from \"./util.js\";\nimport { googleSecretsDir } from \"./paths.js\";\n\nexport interface InstalledClientSecret {\n client_id: string;\n client_secret: string;\n}\n\n// Empty strings must not count: they would satisfy a `typeof` check, get\n// picked as the client, and fail at Google with an opaque invalid_client.\nconst isNonEmptyString = (value: unknown): value is string => typeof value === \"string\" && value !== \"\";\n\nconst isInstalledClientSecret = (value: unknown): value is { installed: InstalledClientSecret } => {\n if (!isRecord(value) || !isRecord(value.installed)) return false;\n return isNonEmptyString(value.installed.client_id) && isNonEmptyString(value.installed.client_secret);\n};\n\nconst isClientSecretFileName = (name: string): boolean => name.startsWith(\"client_secret_\") && name.endsWith(\".json\");\n\nconst readIfDesktopClient = async (filePath: string): Promise<string | null> => {\n try {\n const parsed: unknown = JSON.parse(await readFile(filePath, \"utf-8\"));\n return isInstalledClientSecret(parsed) ? filePath : null;\n } catch {\n // Unreadable or malformed: not a usable desktop client, and refusing the\n // whole directory over one stray file would be worse than ignoring it.\n return null;\n }\n};\n\n/** Absolute paths of every desktop-app client JSON in `~/.secrets/`. */\nasync function listDesktopClientSecretFiles(home?: string): Promise<string[]> {\n const dir = googleSecretsDir(home);\n const entries = await readdir(dir).catch((): string[] => []);\n const candidates = entries.filter(isClientSecretFileName).sort();\n const checked = await Promise.all(candidates.map((name) => readIfDesktopClient(join(dir, name))));\n return checked.filter((path): path is string => path !== null);\n}\n\n/** `missing` is the ordinary case — the broker supplies the client, so no user\n * action is needed. `ambiguous` (2+ desktop clients) still needs a human:\n * a stored refresh token pairs with exactly one client_id, so picking for\n * them could silently break the link. */\nexport type ClientSecretPresence = \"found\" | \"missing\" | \"ambiguous\";\n\nexport async function clientSecretPresence(home?: string): Promise<ClientSecretPresence> {\n const matches = await listDesktopClientSecretFiles(home);\n if (matches.length === 0) return \"missing\";\n return matches.length === 1 ? \"found\" : \"ambiguous\";\n}\n\nexport async function findClientSecretPath(home?: string): Promise<string> {\n const dir = googleSecretsDir(home);\n const matches = await listDesktopClientSecretFiles(home);\n const [first, ...rest] = matches;\n if (!first) {\n throw new Error(`no desktop-app client_secret_*.json found in ${dir} — this host links through the sign-in service instead`);\n }\n if (rest.length > 0) {\n const names = matches.map((path) => path.slice(dir.length + 1)).join(\", \");\n throw new Error(`multiple desktop-app client_secret_*.json files found in ${dir} (${names}) — keep exactly one`);\n }\n return first;\n}\n\nexport async function loadClientSecret(home?: string): Promise<InstalledClientSecret> {\n const filePath = await findClientSecretPath(home);\n const parsed: unknown = JSON.parse(await readFile(filePath, \"utf-8\"));\n if (!isInstalledClientSecret(parsed)) {\n throw new Error(`${filePath} is not a desktop-app OAuth client secret (missing \"installed\" with client_id / client_secret)`);\n }\n return { client_id: parsed.installed.client_id, client_secret: parsed.installed.client_secret };\n}\n","// Minimal JSON file I/O for the token store. The token file must be 0600, so\n// the write goes through the shared atomic writer with an explicit `mode`.\nimport { promises as fsp } from \"node:fs\";\nimport { writeFileAtomic } from \"../files/atomic.js\";\n\nconst JSON_INDENT = 2;\n\nexport async function readJsonOrNull<T>(filePath: string): Promise<T | null> {\n try {\n const raw = await fsp.readFile(filePath, \"utf-8\");\n const parsed: T = JSON.parse(raw);\n return parsed;\n } catch {\n return null;\n }\n}\n\n/** tmp-write + rename so readers never see a half-written file; `mode` applies\n * to the tmp file and survives the rename. A single-writer token file, so the\n * staging name is stable (`uniqueTmp: false`). */\nexport async function writeJsonAtomicWithMode(filePath: string, data: unknown, mode: number): Promise<void> {\n await writeFileAtomic(filePath, JSON.stringify(data, null, JSON_INDENT), { mode, uniqueTmp: false });\n}\n","// Persistence for the Google OAuth tokens (refresh + access) at\n// `~/.config/mulmo/google-token.json`, mode 600. Google omits\n// `refresh_token` from refresh responses, so merges must preserve the one we\n// already hold — losing it forces the user through the browser consent again.\nimport { constants as fsConstants, copyFile, mkdir, rm, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { Credentials } from \"google-auth-library\";\nimport { readJsonOrNull, writeJsonAtomicWithMode } from \"./fsJson.js\";\nimport { googleTokenPath, legacyGoogleTokenPath } from \"./paths.js\";\n\nconst TOKEN_FILE_MODE = 0o600;\n\n/** Which client minted the grant: `local` = the user's own client JSON in\n * `~/.secrets/`, `broker` = the mulmoserver broker. Renewal must use the same\n * path, since only that client's secret can refresh its tokens. Absent on\n * tokens written before the broker existed — those are all `local`. */\nexport type IssuedVia = \"local\" | \"broker\";\n\nexport interface StoredGoogleTokens extends Credentials {\n issuedVia?: IssuedVia;\n}\n\nexport function mergeGoogleTokens(existing: StoredGoogleTokens | null, incoming: StoredGoogleTokens): StoredGoogleTokens {\n const merged = { ...existing, ...incoming };\n if (!incoming.refresh_token && existing?.refresh_token) merged.refresh_token = existing.refresh_token;\n // A broker refresh response carries no marker; keep the one from the link.\n if (!incoming.issuedVia && existing?.issuedVia) merged.issuedVia = existing.issuedVia;\n return merged;\n}\n\nconst fileExists = async (filePath: string): Promise<boolean> =>\n await stat(filePath).then(\n () => true,\n () => false,\n );\n\n// Tokens written before 0.20.1 live under the mulmoclaude-branded dir; move\n// them once. COPYFILE_EXCL makes the create atomic-and-non-clobbering — an\n// exists+rename sequence could overwrite a token a concurrent process wrote\n// to the new path in between (TOCTOU). The legacy file is deleted only after\n// a successful copy; on EEXIST (new path won a race, or both files already\n// exist) it is left for any older install still reading it. copyFile\n// preserves the 600 mode.\nasync function migrateLegacyTokenFile(home?: string): Promise<void> {\n const current = googleTokenPath(home);\n const legacy = legacyGoogleTokenPath(home);\n if (!(await fileExists(legacy))) return;\n await mkdir(path.dirname(current), { recursive: true });\n try {\n await copyFile(legacy, current, fsConstants.COPYFILE_EXCL);\n } catch {\n return;\n }\n await rm(legacy, { force: true });\n}\n\nexport async function loadGoogleTokens(home?: string): Promise<StoredGoogleTokens | null> {\n await migrateLegacyTokenFile(home).catch(() => undefined);\n const current = await readJsonOrNull<StoredGoogleTokens>(googleTokenPath(home));\n if (current) return current;\n // Migration is best-effort — a valid legacy token must still count as\n // linked even when the move failed (permissions, read-only fs, …).\n return await readJsonOrNull<StoredGoogleTokens>(legacyGoogleTokenPath(home));\n}\n\nexport async function saveGoogleTokens(incoming: StoredGoogleTokens, home?: string): Promise<StoredGoogleTokens> {\n const merged = mergeGoogleTokens(await loadGoogleTokens(home), incoming);\n await writeJsonAtomicWithMode(googleTokenPath(home), merged, TOKEN_FILE_MODE);\n return merged;\n}\n\nexport async function deleteGoogleTokens(home?: string): Promise<void> {\n await rm(googleTokenPath(home), { force: true });\n}\n","// Client for the mulmoserver OAuth broker (receptron/mulmoserver#54).\n//\n// Why a broker: Google requires a client_secret at the token endpoint even for\n// PKCE flows, so a user without their own Cloud project cannot complete a link\n// on their own. The broker holds the secret and applies it — it never stores or\n// returns it, and tokens still live only on this machine.\n//\n// What proves this host started the flow is the PKCE code_verifier: anyone can\n// mint a `state` at /googleOAuthStart, so the verifier — not the state — is the\n// authorization. See the broker's pkce.ts for the same reasoning server-side.\nimport type { Credentials } from \"google-auth-library\";\nimport { fetchWithTimeout } from \"../utils/fetch.js\";\nimport { errorMessage, isRecord, ONE_SECOND_MS } from \"./util.js\";\n\nconst DEFAULT_BROKER_BASE_URL = \"https://asia-northeast1-mulmoserver.cloudfunctions.net\";\nconst BROKER_TIMEOUT_MS = 20 * ONE_SECOND_MS;\n\n// Trailing slashes are stripped by hand: `/\\/+$/` backtracks super-linearly on\n// a long run of slashes, which lint rejects.\nconst withoutTrailingSlashes = (url: string): string => {\n let end = url.length;\n while (end > 0 && url[end - 1] === \"/\") end -= 1;\n return url.slice(0, end);\n};\n\n/** `MULMO_GOOGLE_BROKER_URL` lets a fork / staging deploy point elsewhere\n * without a code change; unset means the shipped broker. */\nexport const brokerBaseUrl = (override: string | undefined = process.env.MULMO_GOOGLE_BROKER_URL): string =>\n withoutTrailingSlashes(override ?? DEFAULT_BROKER_BASE_URL);\n\nexport interface BrokerStartResponse {\n authUrl: string;\n state: string;\n}\n\nconst LOOPBACK_HOSTNAMES = new Set([\"127.0.0.1\", \"localhost\", \"::1\", \"[::1]\"]);\n\n/** Codes, PKCE verifiers and refresh tokens travel to the broker, so cleartext\n * is refused outright — an override pointed at `http://…` would put them on\n * the wire. Loopback stays allowed: that is where tests stub the broker, and\n * it never leaves the machine. */\nconst assertSecureBrokerUrl = (url: string): void => {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(`invalid Google sign-in service URL: ${url}`);\n }\n if (parsed.protocol === \"https:\") return;\n if (parsed.protocol === \"http:\" && LOOPBACK_HOSTNAMES.has(parsed.hostname)) return;\n throw new Error(`the Google sign-in service must be reached over HTTPS (got ${parsed.protocol}//${parsed.host})`);\n};\n\nconst brokerFetch = async (url: string, init: { method?: string; body?: string } = {}): Promise<unknown> => {\n assertSecureBrokerUrl(url);\n let response: Response;\n try {\n response = await fetchWithTimeout(url, {\n ...init,\n timeoutMs: BROKER_TIMEOUT_MS,\n headers: { \"Content-Type\": \"application/json\" },\n });\n } catch (err) {\n throw new Error(`Google sign-in service unreachable — check the network connection and retry (${errorMessage(err)})`);\n }\n if (!response.ok) {\n // The broker answers deliberately opaque errors (it must not help probe\n // codes / refresh tokens), so there is nothing more specific to surface.\n throw new Error(`Google sign-in service returned HTTP ${response.status}`);\n }\n try {\n return await response.json();\n } catch {\n // A proxy / captive portal answering 200 with HTML would otherwise surface\n // as a raw SyntaxError the agent can't act on.\n throw new Error(\"Google sign-in service returned a malformed response\");\n }\n};\n\nconst GOOGLE_CONSENT_HOST = \"accounts.google.com\";\n\n/** The returned URL is opened in the user's browser, so it must be Google's\n * own consent page and nothing else — a mis-pointed or hostile broker (the\n * base URL is overridable) could otherwise hand back a phishing page wearing\n * a sign-in flow. Cheap to assert, and Google's endpoint host is fixed. */\nconst assertGoogleConsentUrl = (authUrl: string): void => {\n let parsed: URL;\n try {\n parsed = new URL(authUrl);\n } catch {\n throw new Error(\"Google sign-in service returned an unusable authorization URL\");\n }\n if (parsed.protocol !== \"https:\" || parsed.hostname !== GOOGLE_CONSENT_HOST) {\n throw new Error(`Google sign-in service returned an authorization URL that is not Google's consent page (${parsed.protocol}//${parsed.host})`);\n }\n};\n\nexport async function brokerStart(port: number, codeChallenge: string, baseUrl = brokerBaseUrl()): Promise<BrokerStartResponse> {\n const params = new URLSearchParams({ port: String(port), code_challenge: codeChallenge });\n const payload = await brokerFetch(`${baseUrl}/googleOAuthStart?${params.toString()}`);\n const record = isRecord(payload) ? payload : {};\n if (typeof record.auth_url !== \"string\" || typeof record.state !== \"string\") {\n throw new Error(\"Google sign-in service returned an unexpected response (missing auth_url / state)\");\n }\n assertGoogleConsentUrl(record.auth_url);\n return { authUrl: record.auth_url, state: record.state };\n}\n\nconst toCredentials = (payload: unknown, existingRefreshToken?: string): Credentials => {\n const record = isRecord(payload) ? payload : {};\n if (typeof record.access_token !== \"string\") {\n throw new Error(\"Google sign-in service returned no access token\");\n }\n // The refresh endpoint does not echo the refresh_token back — keep the one we\n // already hold so a renewal can't silently unlink the account.\n const refreshToken = typeof record.refresh_token === \"string\" ? record.refresh_token : existingRefreshToken;\n return {\n access_token: record.access_token,\n ...(refreshToken ? { refresh_token: refreshToken } : {}),\n ...(typeof record.expiry_date === \"number\" ? { expiry_date: record.expiry_date } : {}),\n };\n};\n\nexport async function brokerExchange(input: { code: string; state: string; codeVerifier: string }, baseUrl = brokerBaseUrl()): Promise<Credentials> {\n const payload = await brokerFetch(`${baseUrl}/googleOAuthExchange`, {\n method: \"POST\",\n body: JSON.stringify({ code: input.code, state: input.state, code_verifier: input.codeVerifier }),\n });\n const credentials = toCredentials(payload);\n if (!credentials.refresh_token) {\n throw new Error(\"Google returned no refresh token — remove this app under Google Account → Security → Third-party access, then retry\");\n }\n return credentials;\n}\n\nexport async function brokerRefresh(refreshToken: string, baseUrl = brokerBaseUrl()): Promise<Credentials> {\n const payload = await brokerFetch(`${baseUrl}/googleOAuthRefresh`, {\n method: \"POST\",\n body: JSON.stringify({ refresh_token: refreshToken }),\n });\n return toCredentials(payload, refreshToken);\n}\n","// Google OAuth for the host machine, independent of Firebase Auth (which\n// discards refresh tokens). Two ways to reach a linked account:\n//\n// - LOCAL: the user dropped their own desktop-app client JSON in\n// `~/.secrets/`. Everything (consent, exchange, refresh) happens on this\n// machine — nothing but Google is contacted.\n// - BROKER (the default): no client JSON, so the mulmoserver broker applies\n// the client secret for the exchange / refresh it cannot be done without.\n// Tokens still only ever live here. See broker.ts.\n//\n// Either way the loopback listener, the PKCE verifier, and the token file are\n// this machine's. `issuedVia` on the stored token records which path minted it\n// so renewals take the matching one.\nimport { randomBytes } from \"node:crypto\";\nimport http from \"node:http\";\nimport { CodeChallengeMethod, OAuth2Client, type Credentials } from \"google-auth-library\";\nimport { brokerExchange, brokerRefresh, brokerStart } from \"./broker.js\";\nimport { log } from \"./host.js\";\nimport { errorMessage, ONE_MINUTE_MS, ONE_SECOND_MS } from \"./util.js\";\nimport { fetchWithTimeout } from \"../utils/fetch.js\";\nimport { clientSecretPresence, loadClientSecret, type InstalledClientSecret } from \"./clientSecret.js\";\nimport { deleteGoogleTokens, loadGoogleTokens, saveGoogleTokens, type IssuedVia } from \"./tokenStore.js\";\n\nexport const GOOGLE_CALENDAR_SCOPE = \"https://www.googleapis.com/auth/calendar.events\";\n/** Minimal read scope for the calendar list + per-calendar colours.\n * `calendar.events` already covers reading/writing events on any calendar, but\n * discovering WHICH calendars the user has (CalendarList.list) needs a\n * calendar-list read scope — this is the narrowest one that grants it. Kept in\n * step with the broker's consent scopes so local- and broker-linked accounts\n * grant the same set. */\nexport const GOOGLE_CALENDARLIST_SCOPE = \"https://www.googleapis.com/auth/calendar.calendarlist.readonly\";\nexport const GOOGLE_TASKS_SCOPE = \"https://www.googleapis.com/auth/tasks\";\nexport const GOOGLE_DRIVE_FILE_SCOPE = \"https://www.googleapis.com/auth/drive.file\";\n/** Requested at consent as one set — matches the scopes registered on the\n * OAuth consent screen, so a single re-link covers every supported API. */\nexport const GOOGLE_SCOPES = [GOOGLE_CALENDAR_SCOPE, GOOGLE_CALENDARLIST_SCOPE, GOOGLE_TASKS_SCOPE, GOOGLE_DRIVE_FILE_SCOPE];\nconst CALLBACK_PATH = \"/oauth2callback\";\nconst AUTH_TIMEOUT_MS = 5 * ONE_MINUTE_MS;\nconst STATE_BYTES = 16;\n/** Renew a minute early so a call can't start with a token that expires\n * mid-flight. */\nconst EXPIRY_MARGIN_MS = ONE_MINUTE_MS;\n\nexport interface AuthorizeGoogleOptions {\n home?: string;\n /** Called with the consent URL; open it in a browser (and/or print it). */\n onAuthUrl?: (url: string) => void;\n timeoutMs?: number;\n /** Abort a still-pending consent (a settings-UI restart, an explicit cancel).\n * Rejects the flow with {@link GOOGLE_AUTH_CANCELLED} and closes the loopback. */\n signal?: AbortSignal;\n}\n\nconst createClient = (secret: InstalledClientSecret, redirectUri?: string): OAuth2Client =>\n new OAuth2Client({ clientId: secret.client_id, clientSecret: secret.client_secret, ...(redirectUri !== undefined ? { redirectUri } : {}) });\n\nconst persistRotatedTokens = (client: OAuth2Client, home?: string): void => {\n client.on(\"tokens\", (tokens) => {\n saveGoogleTokens(tokens, home).catch((err: unknown) => {\n log.error(\"google\", \"failed to persist rotated tokens\", { error: String(err) });\n });\n });\n};\n\nconst REVOKED_GRANT_MESSAGE = \"could not obtain a Google access token — the grant may have been revoked; re-link the account\";\n\nconst localAccessToken = async (saved: Credentials, home?: string): Promise<string> => {\n const presence = await clientSecretPresence(home);\n // Two desktop clients: the token is still renewable — the engine just can't\n // tell which client minted it. The fix is to remove the duplicate, so raise\n // the loader's own wording rather than sending the user through a re-link.\n if (presence === \"ambiguous\") await loadClientSecret(home);\n // No client at all: the token is bound to the client_id that minted it, so\n // nothing — not even the broker — can renew it. Re-linking is the only way\n // out; the loader's \"no desktop client\" wording would read like a setup\n // problem instead.\n if (presence === \"missing\") {\n throw new Error(\n \"the saved Google link was created with an OAuth client that is no longer configured on this host — ask the user to link their Google account again in this app's settings\",\n );\n }\n const client = createClient(await loadClientSecret(home));\n client.setCredentials(saved);\n persistRotatedTokens(client, home);\n const { token } = await client.getAccessToken();\n if (!token) throw new Error(REVOKED_GRANT_MESSAGE);\n return token;\n};\n\n// The broker mints access tokens because only it holds the client secret. The\n// refreshed token is written back so the next call can reuse it until expiry\n// instead of hitting the broker every time.\nconst brokerAccessToken = async (saved: Credentials, home?: string): Promise<string> => {\n if (typeof saved.expiry_date === \"number\" && saved.access_token && saved.expiry_date - EXPIRY_MARGIN_MS > Date.now()) {\n return saved.access_token;\n }\n const refreshed = await brokerRefresh(saved.refresh_token ?? \"\");\n if (!refreshed.access_token) throw new Error(REVOKED_GRANT_MESSAGE);\n await saveGoogleTokens(refreshed, home);\n return refreshed.access_token;\n};\n\nexport async function getGoogleAccessToken(home?: string): Promise<string> {\n const saved = await loadGoogleTokens(home);\n if (!saved?.refresh_token) {\n // Host-neutral wording — this engine ships to multiple hosts whose link\n // flows differ (#2128); each host's own help carries the specific steps.\n throw new Error(\"Google account not linked on this host — ask the user to link their Google account in this app's settings, then retry\");\n }\n // Tokens written before the broker existed carry no marker; they were all\n // minted from a local client, so that stays the default.\n return saved.issuedVia === \"broker\" ? await brokerAccessToken(saved, home) : await localAccessToken(saved, home);\n}\n\nconst REVOKE_URL = \"https://oauth2.googleapis.com/revoke\";\n\n/** The revoke POST, injectable for tests. */\nexport type RevokeFetch = typeof fetchWithTimeout;\n\n/** Revoke the grant at Google (best-effort) and delete the local token file.\n * Revoke failures are logged but never block the local delete — Google may\n * already consider the token invalid, and keeping the file would leave the\n * user unable to unlink. */\nexport async function unlinkGoogle(home?: string, revokeFetch: RevokeFetch = fetchWithTimeout): Promise<void> {\n const saved = await loadGoogleTokens(home);\n const token = saved?.refresh_token ?? saved?.access_token;\n if (token) {\n try {\n const response = await revokeFetch(REVOKE_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({ token }).toString(),\n });\n if (!response.ok) log.warn(\"google\", \"token revoke returned non-ok\", { status: response.status });\n } catch (err) {\n log.warn(\"google\", \"token revoke failed, deleting local tokens anyway\", { error: errorMessage(err) });\n }\n }\n await deleteGoogleTokens(home);\n}\n\nconst startLoopbackServer = (): Promise<{ server: http.Server; port: number }> =>\n new Promise((resolve, reject) => {\n const server = http.createServer();\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => {\n const address = server.address();\n if (address === null || typeof address === \"string\") {\n reject(new Error(\"loopback server has no port\"));\n return;\n }\n resolve({ server, port: address.port });\n });\n });\n\n// State is validated before error/code — a callback that can't prove it\n// belongs to this flow must not influence it (its `error` text would\n// otherwise reach the terminal attacker-controlled).\nconst authCodeFromCallback = (url: URL, expectedState: string): string => {\n if (url.searchParams.get(\"state\") !== expectedState) throw new Error(\"OAuth state mismatch — possible CSRF, aborting\");\n const error = url.searchParams.get(\"error\");\n if (error) throw new Error(`Google authorization failed: ${error}`);\n const code = url.searchParams.get(\"code\");\n if (!code) throw new Error(\"authorization callback carried no code\");\n return code;\n};\n\nconst respondHtml = (res: http.ServerResponse, status: number, message: string): void => {\n res.writeHead(status, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(`<html><body><h3>${message}</h3></body></html>`);\n};\n\n/** Reject reason when a pending flow is torn down (a settings-UI restart or an\n * explicit cancel), kept distinct from a real authorization error so callers\n * can treat it as \"user changed their mind\", not \"linking failed\". */\nexport const GOOGLE_AUTH_CANCELLED = \"authorization cancelled\";\n\ninterface AuthCodeSettle {\n resolve: (code: string) => void;\n reject: (err: unknown) => void;\n}\n\n// Answers one loopback request. A terminal callback settles the flow; a\n// wrong-path or wrong-state request is answered but leaves the flow waiting —\n// a drive-by localhost probe or stale tab must not abort a pending consent.\nconst handleCallbackRequest = (req: http.IncomingMessage, res: http.ServerResponse, expectedState: string, settle: AuthCodeSettle): void => {\n const url = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n if (url.pathname !== CALLBACK_PATH) {\n res.writeHead(404);\n res.end();\n return;\n }\n if (url.searchParams.get(\"state\") !== expectedState) {\n respondHtml(res, 400, \"Invalid authorization callback. You can close this tab.\");\n return;\n }\n try {\n const code = authCodeFromCallback(url, expectedState);\n respondHtml(res, 200, \"Authorization complete — you can close this tab.\");\n settle.resolve(code);\n } catch (err) {\n // Static text only — the failure detail echoes query-string content, which\n // must not be reflected into HTML. The CLI prints the detail.\n respondHtml(res, 400, \"Authorization failed — see the terminal for details. You can close this tab.\");\n settle.reject(err);\n }\n};\n\n// Resolves with the OAuth code from the loopback callback, or rejects on the\n// timeout or on `signal` firing (a restart / cancel) — whichever comes first.\nexport const waitForAuthCode = (server: http.Server, expectedState: string, timeoutMs: number, signal?: AbortSignal): Promise<string> =>\n new Promise<string>((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error(`authorization timed out after ${timeoutMs / ONE_SECOND_MS}s`)), timeoutMs);\n const settle: AuthCodeSettle = {\n resolve: (code) => {\n clearTimeout(timer);\n resolve(code);\n },\n reject: (err) => {\n clearTimeout(timer);\n reject(err instanceof Error ? err : new Error(String(err)));\n },\n };\n if (signal) {\n if (signal.aborted) {\n settle.reject(new Error(GOOGLE_AUTH_CANCELLED));\n return;\n }\n signal.addEventListener(\"abort\", () => settle.reject(new Error(GOOGLE_AUTH_CANCELLED)), { once: true });\n }\n server.on(\"request\", (req, res) => handleCallbackRequest(req, res, expectedState, settle));\n });\n\n// `access_type: offline` + `prompt: consent` force Google to return a refresh\n// token on every run (repeat consents otherwise omit it).\nconst buildConsentUrl = (client: OAuth2Client, codeChallenge: string, state: string): string =>\n client.generateAuthUrl({\n access_type: \"offline\",\n prompt: \"consent\",\n scope: GOOGLE_SCOPES,\n code_challenge_method: CodeChallengeMethod.S256,\n code_challenge: codeChallenge,\n state,\n });\n\n// PKCE material is generated by an OAuth2Client with no credentials — the\n// helper is pure crypto, and in broker mode there is no client to construct.\nconst generatePkce = async (): Promise<{ codeVerifier: string; codeChallenge: string }> => {\n const { codeVerifier, codeChallenge } = await new OAuth2Client().generateCodeVerifierAsync();\n if (!codeChallenge) throw new Error(\"failed to derive a PKCE code challenge\");\n return { codeVerifier, codeChallenge };\n};\n\nconst authorizeWithLocalClient = async (\n secret: InstalledClientSecret,\n server: http.Server,\n port: number,\n opts: AuthorizeGoogleOptions,\n): Promise<Credentials> => {\n const client = createClient(secret, `http://127.0.0.1:${port}${CALLBACK_PATH}`);\n const { codeVerifier, codeChallenge } = await client.generateCodeVerifierAsync();\n if (!codeChallenge) throw new Error(\"failed to derive a PKCE code challenge\");\n const state = randomBytes(STATE_BYTES).toString(\"hex\");\n opts.onAuthUrl?.(buildConsentUrl(client, codeChallenge, state));\n const code = await waitForAuthCode(server, state, opts.timeoutMs ?? AUTH_TIMEOUT_MS, opts.signal);\n const { tokens } = await client.getToken({ code, codeVerifier });\n if (!tokens.refresh_token) {\n throw new Error(\"Google returned no refresh token — remove this app under Google Account → Security → Third-party access, then retry\");\n }\n return tokens;\n};\n\n// The broker signs `state` with a key it never releases, so it — not this\n// host — builds the authorization URL. `state` then round-trips through the\n// browser and comes back to our loopback, where waitForAuthCode matches it.\nconst authorizeWithBroker = async (server: http.Server, port: number, opts: AuthorizeGoogleOptions): Promise<Credentials> => {\n const { codeVerifier, codeChallenge } = await generatePkce();\n const { authUrl, state } = await brokerStart(port, codeChallenge);\n opts.onAuthUrl?.(authUrl);\n const code = await waitForAuthCode(server, state, opts.timeoutMs ?? AUTH_TIMEOUT_MS, opts.signal);\n return await brokerExchange({ code, state, codeVerifier });\n};\n\n// Persists the freshly linked tokens — unless a restart/cancel aborted this run\n// after the code came back. `waitForAuthCode` only guards the wait; the token\n// exchange and this save run past it, so an abandoned flow could otherwise\n// clobber the link the user actually kept. This re-checks the signal right\n// before the one persistent side effect.\nexport const commitLinkedTokens = async (tokens: Credentials, issuedVia: IssuedVia, opts: AuthorizeGoogleOptions): Promise<Credentials> => {\n if (opts.signal?.aborted) throw new Error(GOOGLE_AUTH_CANCELLED);\n return await saveGoogleTokens({ ...tokens, issuedVia }, opts.home);\n};\n\nexport async function authorizeGoogle(opts: AuthorizeGoogleOptions = {}): Promise<Credentials> {\n // A user-supplied client wins: it keeps the whole flow on this machine, and\n // silently preferring the broker would ignore a deliberate setup. Two of\n // them is unresolvable rather than a reason to fall back — the user meant to\n // use one of theirs, and a broker link would quietly not be it.\n const presence = await clientSecretPresence(opts.home);\n if (presence === \"ambiguous\") {\n // Same wording the loader raises, so the CLI and the settings UI (which\n // disables linking in this state) agree on the fix.\n await loadClientSecret(opts.home);\n }\n const useLocalClient = presence === \"found\";\n const { server, port } = await startLoopbackServer();\n try {\n const issuedVia: IssuedVia = useLocalClient ? \"local\" : \"broker\";\n const tokens = useLocalClient\n ? await authorizeWithLocalClient(await loadClientSecret(opts.home), server, port, opts)\n : await authorizeWithBroker(server, port, opts);\n return await commitLinkedTokens(tokens, issuedVia, opts);\n } finally {\n server.close();\n }\n}\n","// In-flight manager for the settings-UI OAuth flow. authorizeGoogle() resolves\n// only after the user finishes the browser consent, so the HTTP layer starts it\n// in the background, returns the consent URL immediately, and reports progress\n// via status polling. start() always reflects the latest intent: a new call\n// aborts any pending flow (which closes its loopback listener) before launching\n// a fresh one, so a user who abandoned the browser consent can just link again\n// instead of waiting out the server-side timeout.\nimport { log } from \"./host.js\";\nimport { errorMessage } from \"./util.js\";\nimport { authorizeGoogle } from \"./auth.js\";\n\nexport interface GoogleAuthFlowStatus {\n pending: boolean;\n lastError: string | null;\n}\n\nexport interface GoogleAuthFlow {\n start: () => Promise<{ authUrl: string }>;\n cancel: () => void;\n status: () => GoogleAuthFlowStatus;\n}\n\nexport const createGoogleAuthFlow = (authorize: typeof authorizeGoogle): GoogleAuthFlow => {\n let flowRunning = false;\n let lastError: string | null = null;\n let active: AbortController | null = null;\n\n const launch = (): Promise<{ authUrl: string }> => {\n const controller = new AbortController();\n active = controller;\n flowRunning = true;\n return new Promise((resolve, reject) => {\n authorize({ onAuthUrl: (url) => resolve({ authUrl: url }), signal: controller.signal })\n .then(() => log.info(\"google\", \"authorize flow completed\"))\n .catch((err: unknown) => {\n // An aborted flow is a user-initiated restart, not a failure to\n // surface — only real errors reach lastError.\n if (!controller.signal.aborted) {\n lastError = errorMessage(err);\n log.warn(\"google\", \"authorize flow failed\", { error: lastError });\n }\n reject(err instanceof Error ? err : new Error(String(err)));\n })\n .finally(() => {\n // A restart may have already installed a newer controller; only the\n // current flow clears the shared state so it can't strand the new one.\n if (active === controller) {\n flowRunning = false;\n active = null;\n }\n });\n });\n };\n\n const cancel = (): void => {\n active?.abort();\n };\n\n const start = (): Promise<{ authUrl: string }> => {\n cancel();\n lastError = null;\n return launch();\n };\n\n const status = (): GoogleAuthFlowStatus => ({ pending: flowRunning, lastError });\n\n return { start, cancel, status };\n};\n\nexport const googleAuthFlow = createGoogleAuthFlow(authorizeGoogle);\n","// Shared REST plumbing for the Google APIs this engine wraps (Calendar,\n// Tasks, Drive). Plain fetch instead of the `googleapis` SDK — a handful of\n// endpoints don't justify the dependency (see\n// plans/done/feat-google-oauth-calendar.md).\nimport { errorMessage, isRecord, ONE_SECOND_MS, truncate } from \"./util.js\";\nimport { fetchWithTimeout } from \"../utils/fetch.js\";\n\nexport const GOOGLE_API_TIMEOUT_MS = 30 * ONE_SECOND_MS;\nconst ERROR_BODY_MAX_CHARS = 300;\nexport const HTTP_FORBIDDEN = 403;\nexport const DEFAULT_LIST_MAX_RESULTS = 10;\nexport const MAX_LIST_RESULTS = 50;\n\n/** Carries the HTTP status so callers can branch on it (e.g. an expired\n * calendar syncToken answers 410) without parsing the message. */\nexport class GoogleApiError extends Error {\n readonly status: number;\n constructor(status: number, message: string) {\n super(message);\n this.name = \"GoogleApiError\";\n this.status = status;\n }\n}\n\nexport const isGoogleApiError = (value: unknown): value is GoogleApiError => value instanceof GoogleApiError;\n\n/** 403 usually means the API is not enabled for the user's Cloud project —\n * name the API so the agent's recovery guidance can be specific. */\nexport const googleApiError = (apiLabel: string, status: number, body: string): GoogleApiError => {\n const hint = status === HTTP_FORBIDDEN ? ` (is the ${apiLabel} enabled for the Cloud project?)` : \"\";\n const detail = body ? ` — ${truncate(body, ERROR_BODY_MAX_CHARS)}` : \"\";\n return new GoogleApiError(status, `${apiLabel}: HTTP ${status}${hint}${detail}`);\n};\n\nexport interface GoogleRequestInit {\n method?: string;\n body?: string;\n /** Overrides the default JSON content type (multipart upload, …). */\n contentType?: string;\n /** Response is not JSON (Drive media download) — return the raw text. */\n expectText?: boolean;\n /** Extra request headers, merged after the defaults. Used for conditional\n * writes (`If-Match: <etag>`), which is how a caller makes a PATCH fail with\n * 412 instead of silently overwriting a newer version. Cannot replace\n * `Authorization`. */\n extraHeaders?: Record<string, string>;\n}\n\nexport async function googleRequest(apiLabel: string, accessToken: string, url: string, init: GoogleRequestInit = {}): Promise<unknown> {\n const { contentType = \"application/json\", expectText = false, extraHeaders, ...rest } = init;\n const response = await fetchWithTimeout(url, {\n ...rest,\n timeoutMs: GOOGLE_API_TIMEOUT_MS,\n headers: { ...extraHeaders, Authorization: `Bearer ${accessToken}`, \"Content-Type\": contentType },\n });\n if (!response.ok) {\n const body = await response.text().catch((err: unknown) => errorMessage(err));\n throw googleApiError(apiLabel, response.status, body);\n }\n if (expectText) return await response.text();\n // 204 (Drive delete, …) has no body to parse.\n if (response.status === 204) return {};\n return await response.json();\n}\n\nexport const stringField = (record: Record<string, unknown>, key: string): string => (typeof record[key] === \"string\" ? record[key] : \"\");\n\nexport const asRecord = (value: unknown): Record<string, unknown> => (isRecord(value) ? value : {});\n\nexport const itemsOf = (value: unknown): unknown[] => {\n const record = asRecord(value);\n return Array.isArray(record.items) ? record.items : [];\n};\n","// Google Calendar v3 REST calls. Events read/write against any calendar the\n// user can access (default: their primary); the calendar list and colour\n// palette let callers show non-primary calendars and their colours.\nimport { asRecord, googleApiError, googleRequest, isGoogleApiError, itemsOf, stringField, DEFAULT_LIST_MAX_RESULTS } from \"./apiClient.js\";\nimport { conferenceVideoUri, selfResponseStatus } from \"./eventDerived.js\";\nimport { isRecord } from \"./util.js\";\n\nconst CALENDAR_BASE_URL = \"https://www.googleapis.com/calendar/v3\";\nconst CALENDAR_API_LABEL = \"Google Calendar API\";\nconst DEFAULT_CALENDAR_ID = \"primary\";\n// CalendarList.list is paginated; page through nextPageToken so an account\n// with many subscribed calendars isn't silently truncated. The page cap is a\n// runaway guard — 250 * 40 = 10k calendars, far beyond any real account.\nconst CALENDAR_LIST_PAGE_SIZE = 250;\nconst MAX_CALENDAR_LIST_PAGES = 40;\n// Events.list caps maxResults at 2500. The page cap is a runaway guard —\n// 2500 * 200 = 500k events, far beyond any real calendar's history.\nconst EVENT_SYNC_PAGE_SIZE = 2500;\nconst MAX_EVENT_SYNC_PAGES = 200;\n// `getCalendarMeta` reads the list envelope, never the events in it. Google's\n// accepted range for maxResults starts at 1, so this is the smallest page it\n// will serve.\nconst CALENDAR_META_PAGE_SIZE = 1;\n// An expired/invalidated calendar syncToken.\nconst HTTP_GONE = 410;\nconst HTTP_NOT_FOUND = 404;\n// A GET on an event that is not there: 404 when it never existed on this\n// calendar, 410 when it was deleted and the tombstone is no longer served.\n/** A GET or DELETE on an event that is not there. Exported because the push's\n * delete sweep has to read \"already gone\" as success, not as a failure to\n * retry — a retained baseline would announce the same phantom deletion on\n * every run. */\nexport const EVENT_ABSENT_STATUSES: readonly number[] = [HTTP_NOT_FOUND, HTTP_GONE];\n/** An `If-Match` write whose etag no longer matches — someone else changed the\n * event since it was read. */\nexport const HTTP_PRECONDITION_FAILED = 412;\n/** An `insert` whose caller-supplied event id is already taken. */\nexport const HTTP_CONFLICT = 409;\n/** Google's status for an event that was deleted; a sync window reports it so\n * consumers can remove their copy. Single-sourced — the pull and the push both\n * branch on it. */\nexport const CANCELLED_EVENT_STATUS = \"cancelled\";\n\n/** Resolve a declared calendarId to the one the API and the sync-token store\n * both address. `||` (not `??`) so an empty string also falls back instead of\n * building a malformed `/calendars//events` URL. Single-sourced because an\n * omitted id and an explicit \"primary\" MUST agree everywhere — grouping them\n * apart while they share a sync token silently loses events (#2184). */\nexport const canonicalCalendarId = (calendarId: string | undefined): string => calendarId || DEFAULT_CALENDAR_ID;\n\nconst eventsUrl = (calendarId: string | undefined): string => `${CALENDAR_BASE_URL}/calendars/${encodeURIComponent(canonicalCalendarId(calendarId))}/events`;\n\n/** One end of an event as the API expresses it: a timed instant, or a whole day.\n *\n * `timeZone` names the zone a `dateTime` WITHOUT an offset is read in — the\n * shape a collection push needs, since the stored clock carries no offset\n * (`pushDateTime.ts`). A `dateTime` that already ends in `Z`/`+09:00` needs no\n * `timeZone`. `date` is an all-day event, whose `end` Google treats as\n * EXCLUSIVE. */\nexport type CalendarEventTime = { dateTime: string; timeZone?: string } | { date: string };\n\n/** The two ways to state an event's span. The flat pair is what the `google`\n * tool passes (RFC3339 with an offset); the structured pair is what a push\n * needs to express an all-day event or an explicit zone. Expressed as a union\n * so \"neither given\" cannot typecheck on a create. */\nexport type CalendarEventSpan = { startDateTime: string; endDateTime: string } | { start: CalendarEventTime; end: CalendarEventTime };\n\nexport type CalendarEventInput = {\n summary: string;\n description?: string | undefined;\n location?: string | undefined;\n /** Calendar to create the event on; defaults to the user's primary. */\n calendarId?: string | undefined;\n /** Event colour (Google event palette id \"1\"..\"11\"); omit to inherit the calendar's colour. */\n colorId?: string | undefined;\n /** Caller-chosen event id. Google requires base32hex (`0-9a-v`), 5-1024 chars,\n * and answers 409 when it is already taken. A collection push sets this so a\n * locally-created record keeps its own record id as the event id, instead of\n * being re-keyed to Google's after the fact — a re-key that is missed leaves\n * a duplicate record on the next pull. */\n eventId?: string | undefined;\n} & CalendarEventSpan;\n\nexport interface UpdateCalendarEventInput {\n eventId: string;\n summary?: string | undefined;\n startDateTime?: string | undefined;\n endDateTime?: string | undefined;\n /** Structured span; wins over the flat pair when both are present. */\n start?: CalendarEventTime | undefined;\n end?: CalendarEventTime | undefined;\n /** `\"\"` clears the description; omit to leave it untouched. */\n description?: string | undefined;\n /** `\"\"` clears the location; omit to leave it untouched. */\n location?: string | undefined;\n calendarId?: string | undefined;\n colorId?: string | undefined;\n /** Etag of the version this edit was computed against. Sent as `If-Match`, so\n * Google answers 412 rather than letting the PATCH clobber a change that\n * landed after the caller read the event. Omit for an unconditional write. */\n ifMatch?: string | undefined;\n}\n\nexport interface DeleteCalendarEventInput {\n eventId: string;\n calendarId?: string | undefined;\n /** Etag of the version this decision was made against. Sent as `If-Match`, so\n * Google answers 412 rather than removing an event that changed after it was\n * read — the same guard `updateCalendarEvent` takes, and it matters more\n * here: a delete cannot be undone from this side. Omit for an unconditional\n * delete. */\n ifMatch?: string | undefined;\n}\n\nexport interface ListEventsInput {\n timeMin?: string | undefined;\n maxResults?: number | undefined;\n /** Calendar to read; defaults to the user's primary. */\n calendarId?: string | undefined;\n}\n\nexport interface CalendarEventSummary {\n id: string;\n summary: string;\n start: string;\n end: string;\n htmlLink: string;\n status: string;\n /** Google event palette id (\"1\"..\"11\"), or \"\" when the event inherits the calendar's colour. */\n colorId: string;\n /** The event body, RAW. Google stores limited HTML here, and normalising it on\n * the way in would silently strip the user's formatting on the way back out —\n * a mirrored calendar has to survive the round trip, so any prettifying\n * belongs to whatever displays it. */\n description: string;\n location: string;\n /** The series an expanded instance belongs to, \"\" for a one-off. The sync\n * expands recurrences, so a weekly meeting arrives once per occurrence; with\n * no key pointing back, one edit to the series is indistinguishable from a\n * batch of unrelated changes. */\n recurringEventId: string;\n /** Where an expanded instance sat BEFORE anyone moved it, so a dragged\n * occurrence reads as a move rather than a delete plus an insert. Flattened\n * to the instant like `start`/`end`, so an all-day series yields the date. */\n originalStartTime: string;\n /** Google's last-modified time (RFC3339). Google sets it on every write, so\n * it is readable and never pushable. */\n updated: string;\n /** \"transparent\" when the event does not consume the attendee's time. Google\n * omits the default, so \"\" reads as opaque. */\n transparency: string;\n /** Which KIND of entry this is, and how the ones Google generates get kept out\n * of a mirror. Google's whole set: \"default\", \"birthday\", \"focusTime\",\n * \"fromGmail\", \"outOfOffice\", \"workingLocation\". */\n eventType: string;\n /** Google Meet URL, \"\" when nothing is attached. */\n hangoutLink: string;\n /** The signed-in user's own `responseStatus`, folded out of `attendees`.\n * `\"\"` when Google reported none — which is every event that HAS no\n * attendees, so it reads as \"nothing said\", never as \"not going\". */\n selfResponseStatus: string;\n /** The URL that joins this event's meeting, folded out of\n * `conferenceData.entryPoints`. Covers the conferences `hangoutLink` does\n * not: Zoom, Teams, anything attached as conference data. */\n conferenceVideoUri: string;\n}\n\nexport interface CalendarSummary {\n id: string;\n summary: string;\n description: string;\n /** True only for the user's primary calendar. */\n primary: boolean;\n accessRole: string;\n backgroundColor: string;\n foregroundColor: string;\n /** Calendar palette id backing background/foregroundColor. */\n colorId: string;\n /** IANA zone (`Asia/Tokyo`). The zone a pushed `dateTime` without an offset is\n * read in: the collection stores the clock the user reads off THIS calendar,\n * so this is the only correct zone to re-attach. `\"\"` when Google omits it. */\n timeZone: string;\n}\n\nexport interface CalendarColorEntry {\n background: string;\n foreground: string;\n}\n\n/** Palettes from Google's `/colors` endpoint: `event` maps an event's `colorId`\n * and `calendar` maps a calendar's `colorId` to hex background/foreground. */\nexport interface CalendarColors {\n event: Record<string, CalendarColorEntry>;\n calendar: Record<string, CalendarColorEntry>;\n}\n\n/** People Google lists on the event, `0` when it carries none. Counts EVERY\n * entry, including the one Google adds for the organiser: see `deletePlan.ts`\n * for why the guard is not cleverer than that. */\nconst attendeeCount = (record: Record<string, unknown>): number => (Array.isArray(record.attendees) ? record.attendees.length : 0);\n\n// All-day events carry `date`, timed events carry `dateTime`.\nconst eventTime = (value: unknown): string => {\n if (!isRecord(value)) return \"\";\n if (typeof value.dateTime === \"string\") return value.dateTime;\n if (typeof value.date === \"string\") return value.date;\n return \"\";\n};\n\nexport const toEventSummary = (value: unknown): CalendarEventSummary => {\n const record = asRecord(value);\n return {\n id: stringField(record, \"id\"),\n summary: stringField(record, \"summary\"),\n start: eventTime(record.start),\n end: eventTime(record.end),\n htmlLink: stringField(record, \"htmlLink\"),\n status: stringField(record, \"status\"),\n colorId: stringField(record, \"colorId\"),\n description: stringField(record, \"description\"),\n location: stringField(record, \"location\"),\n recurringEventId: stringField(record, \"recurringEventId\"),\n originalStartTime: eventTime(record.originalStartTime),\n updated: stringField(record, \"updated\"),\n transparency: stringField(record, \"transparency\"),\n eventType: stringField(record, \"eventType\"),\n hangoutLink: stringField(record, \"hangoutLink\"),\n selfResponseStatus: selfResponseStatus(record.attendees),\n conferenceVideoUri: conferenceVideoUri(record.conferenceData),\n };\n};\n\nexport const toCalendarSummary = (value: unknown): CalendarSummary => {\n const record = asRecord(value);\n return {\n id: stringField(record, \"id\"),\n summary: stringField(record, \"summary\"),\n description: stringField(record, \"description\"),\n primary: record.primary === true,\n accessRole: stringField(record, \"accessRole\"),\n backgroundColor: stringField(record, \"backgroundColor\"),\n foregroundColor: stringField(record, \"foregroundColor\"),\n colorId: stringField(record, \"colorId\"),\n timeZone: stringField(record, \"timeZone\"),\n };\n};\n\nconst toColorMap = (value: unknown): Record<string, CalendarColorEntry> => {\n const entries = Object.entries(asRecord(value)).map(([colorId, entry]): [string, CalendarColorEntry] => {\n const record = asRecord(entry);\n return [colorId, { background: stringField(record, \"background\"), foreground: stringField(record, \"foreground\") }];\n });\n return Object.fromEntries(entries);\n};\n\n/** Kept as a named export for the existing unit tests / callers; the shared\n * helper now carries the wording. */\nexport const calendarApiError = (status: number, body: string): Error => googleApiError(CALENDAR_API_LABEL, status, body);\n\n/** Normalise either spelling of a span into the pair the API body carries. */\nexport const resolveEventSpan = (span: CalendarEventSpan): { start: CalendarEventTime; end: CalendarEventTime } =>\n \"start\" in span ? { start: span.start, end: span.end } : { start: { dateTime: span.startDateTime }, end: { dateTime: span.endDateTime } };\n\nexport async function createCalendarEvent(accessToken: string, input: CalendarEventInput): Promise<CalendarEventSummary> {\n const body = {\n summary: input.summary,\n description: input.description,\n location: input.location,\n ...resolveEventSpan(input),\n ...(input.colorId ? { colorId: input.colorId } : {}),\n ...(input.eventId ? { id: input.eventId } : {}),\n };\n const created = await googleRequest(CALENDAR_API_LABEL, accessToken, eventsUrl(input.calendarId), { method: \"POST\", body: JSON.stringify(body) });\n return toEventSummary(created);\n}\n\nconst eventUrl = (calendarId: string | undefined, eventId: string): string => `${eventsUrl(calendarId)}/${encodeURIComponent(eventId)}`;\n\n/** PATCH body for an event edit — only the fields the caller actually supplied.\n *\n * `undefined` means \"leave as is\" and `\"\"` means \"clear it\", so the two cannot\n * be collapsed: dropping `description: \"\"` would silently ignore a request to\n * empty the body text. Pure so the distinction is testable without network. */\n/** One end of the patch: the structured value if given, else the flat one\n * wrapped, else absent (= leave it alone). */\nconst eventEnd = (key: \"start\" | \"end\", structured: CalendarEventTime | undefined, flat: string | undefined): Record<string, CalendarEventTime> => {\n if (structured !== undefined) return { [key]: structured };\n return flat !== undefined ? { [key]: { dateTime: flat } } : {};\n};\n\nexport const buildEventPatch = (input: UpdateCalendarEventInput): Record<string, unknown> => ({\n ...(input.summary !== undefined ? { summary: input.summary } : {}),\n ...(input.description !== undefined ? { description: input.description } : {}),\n ...(input.location !== undefined ? { location: input.location } : {}),\n ...eventEnd(\"start\", input.start, input.startDateTime),\n ...eventEnd(\"end\", input.end, input.endDateTime),\n ...(input.colorId ? { colorId: input.colorId } : {}),\n});\n\n/** Edit an existing event in place. PATCH, not PUT — a PUT would need the whole\n * event and would drop every field the caller never read (attendees,\n * reminders, recurrence). */\nexport async function updateCalendarEvent(accessToken: string, input: UpdateCalendarEventInput): Promise<CalendarEventSummary> {\n const url = eventUrl(input.calendarId, input.eventId);\n const updated = await googleRequest(CALENDAR_API_LABEL, accessToken, url, {\n method: \"PATCH\",\n body: JSON.stringify(buildEventPatch(input)),\n ...(input.ifMatch ? { extraHeaders: { \"If-Match\": input.ifMatch } } : {}),\n });\n return toEventSummary(updated);\n}\n\n/** An event plus the version token a conditional write needs. */\nexport interface FetchedCalendarEvent {\n event: CalendarEventSummary;\n /** Google's `etag` for this version, `\"\"` when absent. Sent back as\n * `If-Match` so a PATCH cannot overwrite a version we never read. */\n etag: string;\n /** How many people the event lists, `0` for a solo one. Carried here rather\n * than on the summary because only the delete guard reads it, and it is the\n * one thing that guard cannot get from the record — by then the record is\n * gone (`deletePlan.ts`). */\n attendeeCount: number;\n}\n\n/** Read ONE event, or null when it is gone (404 / 410).\n *\n * A push needs Google's current value per changed record to tell a local-only\n * edit from a both-sides conflict. Deliberately not `syncCalendarEvents`: that\n * consumes the calendar's sync token, and the token's window is served once —\n * spending it here would make the next pull miss everything in it. */\nexport async function getCalendarEvent(accessToken: string, input: DeleteCalendarEventInput): Promise<FetchedCalendarEvent | null> {\n try {\n const fetched = await googleRequest(CALENDAR_API_LABEL, accessToken, eventUrl(input.calendarId, input.eventId));\n const record = asRecord(fetched);\n return { event: toEventSummary(fetched), etag: stringField(record, \"etag\"), attendeeCount: attendeeCount(record) };\n } catch (error) {\n if (isGoogleApiError(error) && EVENT_ABSENT_STATUSES.includes(error.status)) return null;\n throw error;\n }\n}\n\n/** Remove an event. Google answers 204 with no body, so there is nothing to\n * return; a second delete of the same id answers 410, which surfaces as a\n * GoogleApiError rather than being swallowed. */\nexport async function deleteCalendarEvent(accessToken: string, input: DeleteCalendarEventInput): Promise<void> {\n await googleRequest(CALENDAR_API_LABEL, accessToken, eventUrl(input.calendarId, input.eventId), {\n method: \"DELETE\",\n ...(input.ifMatch ? { extraHeaders: { \"If-Match\": input.ifMatch } } : {}),\n });\n}\n\nexport async function listCalendarEvents(accessToken: string, input: ListEventsInput = {}): Promise<CalendarEventSummary[]> {\n const params = new URLSearchParams({\n timeMin: input.timeMin ?? new Date().toISOString(),\n maxResults: String(input.maxResults ?? DEFAULT_LIST_MAX_RESULTS),\n singleEvents: \"true\",\n orderBy: \"startTime\",\n });\n const listed = await googleRequest(CALENDAR_API_LABEL, accessToken, `${eventsUrl(input.calendarId)}?${params.toString()}`);\n return itemsOf(listed).map(toEventSummary);\n}\n\nexport interface SyncEventsInput {\n /** Calendar to sync; defaults to the user's primary. */\n calendarId?: string | undefined;\n /** Token from the previous sync. Omit for a full sync. */\n syncToken?: string | undefined;\n /** Page size for the underlying list calls. */\n maxResults?: number | undefined;\n}\n\nexport interface CalendarSyncResult {\n /** Changed events since `syncToken` (all of them on a full sync). Deletions\n * arrive here too, as `status: \"cancelled\"`. */\n events: CalendarEventSummary[];\n /** Token to pass to the NEXT sync. Absent only if Google omitted it. */\n nextSyncToken?: string | undefined;\n /** The stored token had expired (410) — the caller must drop it and re-sync\n * from scratch; no events are returned in that case. */\n fullResyncRequired: boolean;\n /** The page guard fired with pages still pending, so `events` is a PARTIAL\n * window and `nextSyncToken` is absent.\n *\n * Reported rather than left implicit because a truncated walk is otherwise\n * byte-identical to a completed one: the caller would apply the partial set,\n * report success, and — since only Google's last page carries the token —\n * repeat the same truncated walk on every later run, silently (#2850).\n * Google states a page \"may be less than this value, or none at all, even if\n * there are more events matching the query\", so this is reachable on a real\n * calendar, the more so with `singleEvents` expanding unbounded recurrences. */\n pagesExhausted: boolean;\n}\n\n// Sentinel for \"the syncToken expired\" so the page loop can bail without\n// throwing — 410 is an expected, recoverable state, not a failure.\nconst GONE = Symbol(\"calendar-sync-gone\");\n\nasync function fetchSyncPage(accessToken: string, calendarId: string | undefined, params: URLSearchParams): Promise<unknown> {\n try {\n return await googleRequest(CALENDAR_API_LABEL, accessToken, `${eventsUrl(calendarId)}?${params.toString()}`);\n } catch (err: unknown) {\n if (isGoogleApiError(err) && err.status === HTTP_GONE) return GONE;\n throw err;\n }\n}\n\n/** Incremental sync over the events of one calendar (#2095).\n *\n * Deliberately separate from `listCalendarEvents`: Google forbids combining\n * `syncToken` with `timeMin` / `timeMax` / `updatedMin` / `orderBy` / `q`,\n * and that function always sends `timeMin` + `orderBy`. So this one sends\n * neither — a sync covers the WHOLE calendar and the caller sorts / windows\n * client-side. `showDeleted` must stay true or deletions would be invisible.\n *\n * `nextSyncToken` is only present on the LAST page, so every page must be\n * walked before the token is worth storing. */\n/** The query for one page of the walk. `showDeleted` must stay true or\n * deletions would be invisible; `pageToken` is sent ALONGSIDE `syncToken`,\n * which is what Google requires to page an incremental sync. */\nexport function syncPageParams(input: SyncEventsInput, pageToken: string | undefined): URLSearchParams {\n const params = new URLSearchParams({\n singleEvents: \"true\",\n showDeleted: \"true\",\n maxResults: String(input.maxResults ?? EVENT_SYNC_PAGE_SIZE),\n });\n if (input.syncToken) params.set(\"syncToken\", input.syncToken);\n if (pageToken) params.set(\"pageToken\", pageToken);\n return params;\n}\n\nexport async function syncCalendarEvents(accessToken: string, input: SyncEventsInput = {}): Promise<CalendarSyncResult> {\n const events: CalendarEventSummary[] = [];\n let pageToken: string | undefined;\n let nextSyncToken: string | undefined;\n\n for (let page = 0; page < MAX_EVENT_SYNC_PAGES; page += 1) {\n const payload = await fetchSyncPage(accessToken, input.calendarId, syncPageParams(input, pageToken));\n if (payload === GONE) return { events: [], fullResyncRequired: true, pagesExhausted: false };\n\n const record = asRecord(payload);\n events.push(...itemsOf(payload).map(toEventSummary));\n nextSyncToken = stringField(record, \"nextSyncToken\") || undefined;\n pageToken = stringField(record, \"nextPageToken\") || undefined;\n if (!pageToken) break;\n // Google sends the sync token only on the LAST page, so anything read\n // before one is not a resume point for the walk this call is doing.\n // Dropped rather than trusted: kept, it would let a caller resume past the\n // pages this walk never read (Codex review #2853).\n nextSyncToken = undefined;\n }\n\n // A token still in hand means the loop ran out of pages, not out of calendar.\n return { events, nextSyncToken, fullResyncRequired: false, pagesExhausted: pageToken !== undefined };\n}\n\nexport interface CalendarListPage {\n items: unknown[];\n nextPageToken?: string | undefined;\n}\n\n/** Pagination loop for CalendarList.list, extracted so it can be tested without\n * network. Stops at the last page (no token) or the runaway page cap. */\nexport async function collectCalendarPages(\n fetchPage: (pageToken?: string) => Promise<CalendarListPage>,\n maxPages = MAX_CALENDAR_LIST_PAGES,\n): Promise<CalendarSummary[]> {\n const calendars: CalendarSummary[] = [];\n let pageToken: string | undefined;\n for (let page = 0; page < maxPages; page += 1) {\n const { items, nextPageToken } = await fetchPage(pageToken);\n calendars.push(...items.map(toCalendarSummary));\n if (!nextPageToken) break;\n pageToken = nextPageToken;\n }\n return calendars;\n}\n\n/** What a calendar absent from the user's list can still tell about itself. */\nexport interface CalendarMeta {\n /** IANA zone (`Asia/Tokyo`), `\"\"` when Google omits it. */\n timeZone: string;\n /** `owner` / `writer` / `reader` / `freeBusyReader`, `\"\"` when unreported. */\n accessRole: string;\n}\n\nexport const toCalendarMeta = (value: unknown): CalendarMeta => {\n const record = asRecord(value);\n return { timeZone: stringField(record, \"timeZone\"), accessRole: stringField(record, \"accessRole\") };\n};\n\n/** One calendar's zone and the caller's role on it, read by id rather than\n * looked up in the user's list.\n *\n * `calendarList` only holds calendars the user has ADDED; a calendar shared\n * with them can be readable and writable by id without appearing there, and a\n * push still needs its zone to send an offset-less `dateTime`.\n *\n * Deliberately `events.list` and NOT `calendars.get`: the latter takes only\n * full-calendar scopes (`calendar`, `calendar.readonly`, `calendar.calendars*`)\n * and this app asks for none of them, so it answers 403 for every account it\n * links (#2735). The events list carries the same `timeZone` — plus the\n * `accessRole` the calendar resource does not have — under `calendar.events`,\n * which the push already holds because it writes with it. Only the envelope is\n * read; the one event asked for is the smallest page Google will serve. */\nexport async function getCalendarMeta(accessToken: string, calendarId: string | undefined): Promise<CalendarMeta> {\n const params = new URLSearchParams({ maxResults: String(CALENDAR_META_PAGE_SIZE) });\n return toCalendarMeta(await googleRequest(CALENDAR_API_LABEL, accessToken, `${eventsUrl(calendarId)}?${params.toString()}`));\n}\n\n/** The calendars the user has added/subscribed to (primary + secondary +\n * shared), each with its id, name and colour, following pagination. Needs the\n * calendar-list read scope (GOOGLE_CALENDARLIST_SCOPE). */\nexport async function listCalendars(accessToken: string): Promise<CalendarSummary[]> {\n return collectCalendarPages(async (pageToken) => {\n const params = new URLSearchParams({ maxResults: String(CALENDAR_LIST_PAGE_SIZE) });\n if (pageToken) params.set(\"pageToken\", pageToken);\n const payload = await googleRequest(CALENDAR_API_LABEL, accessToken, `${CALENDAR_BASE_URL}/users/me/calendarList?${params.toString()}`);\n const record = asRecord(payload);\n return { items: itemsOf(payload), nextPageToken: typeof record.nextPageToken === \"string\" ? record.nextPageToken : undefined };\n });\n}\n\n/** Resolve a `colorId` (on an event or calendar) to its hex background/foreground. */\nexport async function getCalendarColors(accessToken: string): Promise<CalendarColors> {\n const payload = await googleRequest(CALENDAR_API_LABEL, accessToken, `${CALENDAR_BASE_URL}/colors`);\n const record = asRecord(payload);\n return { event: toColorMap(record.event), calendar: toColorMap(record.calendar) };\n}\n","// Cross-process exclusion for the calendar state files (#2679).\n//\n// `.sync-state.json` and `.push-state.json` are both read-modify-write over a\n// whole file. Inside one process a write queue orders that; between processes\n// nothing did, so two hosts sharing a workspace could each read the same\n// snapshot and the later write would drop the earlier one's entry.\n//\n// Scoped to the file mutation, NOT to the sync run. The mutation is held for\n// milliseconds, which is what makes this cheap: no \"another host is busy\"\n// result to thread through the routes, and a stale lock can be reclaimed on a\n// short timer instead of needing a heartbeat sized against a multi-minute full\n// walk.\n//\n// What it does NOT do: stop two hosts walking the same calendar at once. That\n// wastes API calls, but the records are upserts and the baseline is now safe,\n// so nothing is lost by it.\nimport { hasStringProp } from \"@mulmoclaude/common\";\nimport { open, readFile, stat, unlink } from \"node:fs/promises\";\nimport { randomUUID } from \"node:crypto\";\nimport { log } from \"./host.js\";\n\nconst LOCK_MODE = 0o600;\n/** How long a lock may sit untouched before another process may reclaim it.\n *\n * Sized against the HOLD time, which is one read plus one atomic write — a few\n * milliseconds. Generous by three orders of magnitude so a slow disk never\n * looks like a crash, while a host that died mid-write blocks the others for\n * at most this long. */\nconst LOCK_STALE_MS = 10_000;\n/** How long a caller waits before giving up and proceeding unlocked. */\nconst LOCK_WAIT_MS = 5_000;\nconst LOCK_RETRY_MS = 20;\n\n/** Deliberately just the holder. There is no timestamp in here: an age written\n * by one host and read by another is two different wall clocks compared as if\n * they were one, which breaks staleness in BOTH directions — a live lock\n * reclaimed early, or a dead one that never expires (Codex review #2690). The\n * filesystem's own mtime is the single authority instead. */\ninterface LockFile {\n /** Not a secret — it only has to be unique per acquisition, so that a release\n * removes its OWN lock and never the one that replaced it. */\n holder: string;\n}\n\n/** The clock and the sleep, injected so the timing rules can be exercised\n * without real waits. */\nexport interface LockClock {\n now: () => number;\n sleep: (delay_ms: number) => Promise<void>;\n}\n\nexport const liveLockClock: LockClock = {\n now: () => Date.now(),\n sleep: (delay_ms) => new Promise((resolve) => setTimeout(resolve, delay_ms)),\n};\n\nconst isEexist = (error: unknown): boolean => typeof error === \"object\" && error !== null && \"code\" in error && error.code === \"EEXIST\";\n\n/** The file's bytes, or null when it is not there. */\nasync function readLockRaw(lockPath: string): Promise<string | null> {\n try {\n return await readFile(lockPath, \"utf-8\");\n } catch {\n return null;\n }\n}\n\n/** Absent means unheld; UNREADABLE does not — see `stealIfStale`. */\nfunction parseLock(raw: string): LockFile | null {\n try {\n const parsed: unknown = JSON.parse(raw);\n return hasStringProp(parsed, \"holder\") ? { holder: parsed.holder } : null;\n } catch {\n return null;\n }\n}\n\nasync function readLock(lockPath: string): Promise<LockFile | null> {\n const raw = await readLockRaw(lockPath);\n return raw === null ? null : parseLock(raw);\n}\n\n/** Take the lock, or answer false because someone else holds a live one. */\nasync function tryAcquire(lockPath: string, holder: string): Promise<boolean> {\n try {\n const handle = await open(lockPath, \"wx\", LOCK_MODE);\n try {\n await handle.writeFile(JSON.stringify({ holder }));\n } finally {\n await handle.close();\n }\n return true;\n } catch (error) {\n if (!isEexist(error)) throw error;\n return false;\n }\n}\n\n/** How long this lock has existed, per the filesystem that holds it.\n *\n * From mtime rather than anything the holder wrote down, and for every lock\n * rather than only the unreadable ones. A timestamp written by one host and\n * compared against another host's clock is two wall clocks pretending to be\n * one: skew either way reclaims a live lock or immortalises a dead one (Codex\n * review #2690). Every host reading this workspace sees the SAME mtime.\n *\n * This still assumes the reader's clock and the filesystem's are within a TTL\n * of each other — true by construction on a local disk, and a sub-second NTP\n * question on a network mount, against a window of ten seconds.\n *\n * Reading mtime also covers the moment `open(\"wx\")` has created the file but\n * its payload has not landed yet: \"unreadable\" is a state every healthy lock\n * passes through, and one being born reads as newborn rather than dead.\n *\n * Null when the file is gone, which is not an age but an absence. */\nasync function lockAgeMs(lockPath: string, clock: LockClock): Promise<number | null> {\n const stamped = await stat(lockPath).catch(() => null);\n return stamped === null ? null : clock.now() - stamped.mtimeMs;\n}\n\n/** Drop a lock that has sat untouched past the TTL, whatever it says.\n *\n * Readable or not, nothing is reclaimed until it is demonstrably stale. For a\n * readable one the holder is re-read and compared before the unlink, so a\n * holder that renewed in between is not evicted. That narrows the window\n * rather than closing it — two processes can still both decide to clear the\n * same corpse. The cost of losing that race is one lost update, which is what\n * this file reduces rather than a new failure, and reaching it takes a host\n * dying mid-write plus a full TTL of waiting. */\nasync function stealIfStale(lockPath: string, clock: LockClock): Promise<void> {\n const raw = await readLockRaw(lockPath);\n if (raw === null) return;\n const held = parseLock(raw);\n const age_ms = await lockAgeMs(lockPath, clock);\n if (age_ms === null || age_ms < LOCK_STALE_MS) return;\n // A lock nobody can read is a lock nobody can release — `release` only removes\n // a file whose holder matches — so once it IS stale it has to be cleared here\n // or it would cost every later mutation the full wait before giving up.\n if (held === null) {\n log.warn(\"google\", \"reclaiming an unreadable calendar state lock\", { lockPath, age_ms });\n await unlink(lockPath).catch(() => undefined);\n return;\n }\n const stillHeld = await readLock(lockPath);\n if (stillHeld?.holder !== held.holder) return;\n log.warn(\"google\", \"reclaiming a calendar state lock whose holder went quiet\", { lockPath, age_ms });\n await unlink(lockPath).catch(() => undefined);\n}\n\nasync function release(lockPath: string, holder: string): Promise<void> {\n const held = await readLock(lockPath);\n // Only ever remove OUR lock: after a steal the file may belong to whoever\n // took it next, and unlinking that would hand the same lock to two holders.\n if (held?.holder !== holder) return;\n await unlink(lockPath).catch(() => undefined);\n}\n\n/** Run `mutate` with no other process mutating the same file.\n *\n * Fails OPEN. A workspace that cannot create the lock file — a read-only\n * mount, a missing directory, an exhausted disk — still syncs, the way it did\n * before #2679; the lock removes a race, it is not a precondition for\n * correctness. The same applies to waiting the timeout out: proceeding\n * unlocked risks the lost update this exists to prevent, whereas refusing\n * would stop the sync outright.\n *\n * Note what this cannot promise. `O_EXCL` is atomic on a local filesystem and\n * on NFSv3+, but a workspace living in a consumer sync folder (Dropbox,\n * iCloud, Drive) has no such guarantee — there the file is replicated after\n * the fact and both hosts believe they hold it. */\nexport async function withCalendarStateLock<T>(lockPath: string, mutate: () => Promise<T>, clock: LockClock = liveLockClock): Promise<T> {\n const holder = randomUUID();\n const deadline = clock.now() + LOCK_WAIT_MS;\n let holding = false;\n try {\n holding = await acquireBefore(lockPath, holder, deadline, clock);\n if (!holding) log.warn(\"google\", \"proceeding without the calendar state lock — another host is holding it\", { lockPath });\n return await mutate();\n } finally {\n if (holding) await release(lockPath, holder);\n }\n}\n\n/** Poll until the lock is ours or the deadline passes. */\nasync function acquireBefore(lockPath: string, holder: string, deadline: number, clock: LockClock): Promise<boolean> {\n for (;;) {\n try {\n if (await tryAcquire(lockPath, holder)) return true;\n await stealIfStale(lockPath, clock);\n } catch (error) {\n log.warn(\"google\", \"could not take the calendar state lock — mutating without it\", { lockPath, error: String(error) });\n return false;\n }\n if (clock.now() >= deadline) return false;\n await clock.sleep(LOCK_RETRY_MS);\n }\n}\n\n/** `<state file>.lock`, so each state file is serialised on its own. */\nexport const stateLockPath = (statePath: string): string => `${statePath}.lock`;\n","// Persistence for the Google Calendar incremental-sync tokens (#2095) and the\n// cross-host sync markers (#2678).\n//\n// Unlike the OAuth material in `paths.ts`, this state lives INSIDE the\n// workspace. A syncToken is a claim about which records the workspace already\n// holds: if it survived a workspace reset, the next incremental sync would\n// report \"nothing changed\" against an empty calendar and stay silently empty.\n// Keeping it next to the data it describes makes the two reset together.\nimport path from \"node:path\";\nimport { getWorkspaceRoot } from \"../collection/server/host.js\";\nimport { readJsonOrNull, writeJsonAtomicWithMode } from \"./fsJson.js\";\nimport { canonicalCalendarId } from \"./calendar.js\";\nimport { stateLockPath, withCalendarStateLock } from \"./calendarStateLock.js\";\n\nconst SYNC_STATE_MODE = 0o600;\n\n/** `<workspace>/data/calendar/.sync-state.json` */\nexport function calendarSyncStatePath(workspaceRoot?: string): string {\n return path.join(workspaceRoot ?? getWorkspaceRoot(), \"data\", \"calendar\", \".sync-state.json\");\n}\n\ninterface CalendarSyncState {\n /** calendarId → the `nextSyncToken` returned by that calendar's last sync. */\n tokens: Record<string, string>;\n /** calendarId → when a sync of it last STARTED (ISO).\n *\n * A separate concern from the token, deliberately: the token says how far\n * the workspace has read, never when anyone last ran. Workspace state rather\n * than host state, because that is exactly what lets a second host see that\n * this calendar is already being synced (#2678, `calendarSyncDue.ts`). */\n lastSyncedAt: Record<string, string>;\n}\n\n// Shared with the REST layer so a stored token is keyed by exactly the\n// calendar the request addressed — an omitted id and an explicit \"primary\"\n// must never end up as two different keys.\nconst calendarKey = canonicalCalendarId;\n\n/** The key for a consumer that reads a calendar without KEEPING what it reads.\n *\n * A sync token is a claim about which events the consumer already holds, but\n * the key above names only the calendar — so every consumer of one calendar\n * shares one cursor. That is right for the collections bound to it (one fetch\n * fans out to all of them in the same pass) and wrong for the standalone\n * `google` tool's `calendarSync`, which walks the calendar, reports a summary\n * to the LLM and discards the events.\n *\n * Sharing it lost data both ways (#2850): the tool consumed windows the\n * collections then never received, and a collection created afterwards resumed\n * from the tool's cursor — so its \"first sync\" was a delta of a window it had\n * never held, and the calendar's history never arrived. Same file, separate\n * keyspace. Idempotent through `calendarKey`, since the result is non-empty. */\nexport const toolCalendarSyncKey = (calendarId: string | undefined): string => `tool:${canonicalCalendarId(calendarId)}`;\n\n/** Read one map out of whatever is on disk. Tolerant on purpose: a file written\n * before `lastSyncedAt` existed simply lacks the key, and a hand-edited one may\n * hold anything. A dropped entry costs a full re-walk or a duplicate run, never\n * data — throwing here would stop the sync entirely. */\nfunction stringRecord(value: unknown): Record<string, string> {\n if (typeof value !== \"object\" || value === null) return {};\n return Object.fromEntries(Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"));\n}\n\nasync function readState(workspaceRoot?: string): Promise<CalendarSyncState> {\n const stored = await readJsonOrNull<Partial<CalendarSyncState>>(calendarSyncStatePath(workspaceRoot));\n return { tokens: stringRecord(stored?.tokens), lastSyncedAt: stringRecord(stored?.lastSyncedAt) };\n}\n\n// One file holds every calendar's token, so an unguarded read-modify-write\n// loses updates: two calendars syncing at once both read the same snapshot and\n// the later write drops the earlier one's token — silently forcing that\n// calendar into a full re-walk next run. Serialising the whole cycle keeps\n// them ordered. Scope is this process, which is where the concurrency comes\n// from (parallel tool calls today, the scheduler fanning out over calendars\n// next). Held in a const wrapper so the tail can advance without a `let`.\nconst writeQueue: { tail: Promise<unknown> } = { tail: Promise.resolve() };\n\n/** Decide from the stored state and write the result, both inside the queue, so\n * nothing in this process can read between the two. A `null` state skips the\n * write; `result` is what the caller learns about the decision. */\nasync function updateStateWith<T>(decide: (state: CalendarSyncState) => { state: CalendarSyncState | null; result: T }, workspaceRoot?: string): Promise<T> {\n const statePath = calendarSyncStatePath(workspaceRoot);\n // The queue orders this process; the file lock orders the others. Both are\n // needed — the lock is per read-modify-write, so two of THIS process's own\n // mutations would still interleave without the queue (#2679).\n const run = writeQueue.tail.then(\n async () =>\n await withCalendarStateLock(stateLockPath(statePath), async () => {\n const { state, result } = decide(await readState(workspaceRoot));\n if (state) await writeJsonAtomicWithMode(statePath, state, SYNC_STATE_MODE);\n return result;\n }),\n );\n // Swallow on the queue only — the caller still sees the original rejection.\n writeQueue.tail = run.catch(() => undefined);\n return await run;\n}\n\nasync function updateState(mutate: (state: CalendarSyncState) => CalendarSyncState, workspaceRoot?: string): Promise<void> {\n await updateStateWith((state) => ({ state: mutate(state), result: undefined }), workspaceRoot);\n}\n\nconst withoutKey = (record: Record<string, string>, dropped: string): Record<string, string> =>\n Object.fromEntries(Object.entries(record).filter(([key]) => key !== dropped));\n\nexport async function loadCalendarSyncToken(calendarId?: string, workspaceRoot?: string): Promise<string | null> {\n const state = await readState(workspaceRoot);\n return state.tokens[calendarKey(calendarId)] ?? null;\n}\n\nexport async function saveCalendarSyncToken(calendarId: string | undefined, syncToken: string, workspaceRoot?: string): Promise<void> {\n const stored = calendarKey(calendarId);\n await updateState((state) => ({ ...state, tokens: { ...state.tokens, [stored]: syncToken } }), workspaceRoot);\n}\n\n/** Drop one calendar's token — used when Google answers 410, so the next run\n * starts a clean full sync. Leaves the sync marker alone: \"restart the walk\"\n * says nothing about \"nobody is running\". */\nexport async function clearCalendarSyncToken(calendarId?: string, workspaceRoot?: string): Promise<void> {\n const dropped = calendarKey(calendarId);\n await updateState((state) => ({ ...state, tokens: withoutKey(state.tokens, dropped) }), workspaceRoot);\n}\n\nexport async function loadCalendarLastSyncedAt(calendarId?: string, workspaceRoot?: string): Promise<string | null> {\n const state = await readState(workspaceRoot);\n return state.lastSyncedAt[calendarKey(calendarId)] ?? null;\n}\n\n/** Stamp when a sync of this calendar started, but only if `mayClaim` accepts\n * what the marker currently says. Answers whether THIS call is the one that\n * claimed it.\n *\n * The decision and the stamp share one pass through the queue on purpose.\n * Deciding from a snapshot read earlier — as a caller filtering a whole map of\n * calendars up front must — leaves both hosts believing the calendar is free,\n * which is the race this exists to close (Codex review #2680). What remains is\n * the read→write gap BETWEEN processes; closing that needs a real file lock\n * (#2679), and this stays a soft dedup until then.\n *\n * The time is passed in rather than read here so the rule stays testable\n * without freezing the clock. */\nexport async function claimCalendarSyncIfDue(\n calendarId: string | undefined,\n startedAt: string,\n mayClaim: (lastSyncedAt: string | null) => boolean,\n workspaceRoot?: string,\n): Promise<boolean> {\n const stored = calendarKey(calendarId);\n return await updateStateWith((state) => {\n const claimed = mayClaim(state.lastSyncedAt[stored] ?? null);\n return { state: claimed ? { ...state, lastSyncedAt: { ...state.lastSyncedAt, [stored]: startedAt } } : null, result: claimed };\n }, workspaceRoot);\n}\n\n/** Drop one calendar's marker, so the next tick — on either host — may sync it\n * immediately instead of waiting the window out. */\nexport async function clearCalendarLastSyncedAt(calendarId?: string, workspaceRoot?: string): Promise<void> {\n const dropped = calendarKey(calendarId);\n await updateState((state) => ({ ...state, lastSyncedAt: withoutKey(state.lastSyncedAt, dropped) }), workspaceRoot);\n}\n","// Whether a calendar may be synced now, given when this workspace last STARTED\n// a sync of it (#2678).\n//\n// `googleCalendarSyncTaskDef` is a factory several hosts register — MulmoClaude\n// and a standalone MulmoTerminal/MulmoBooks — and nothing kept them from syncing\n// one calendar at once: the Google link is machine-wide (`~/.config/mulmo`, no\n// app name), the calendar lock is module state, and interval schedules fire on\n// wall-clock boundaries, so every host ticks in the SAME minute. Since `autoPush`\n// (#2620) such a run also writes to Google and rewrites the push baseline.\n//\n// Same soft-dedup feeds already run on `lastFetchedAt`, and with the same limit:\n// the marker is workspace state, so it only speaks for hosts sharing a workspace.\n\n/** Slack between a tick and the marker it writes. Without it a lone host reads\n * its OWN marker as \"not due yet\" — elapsed lands a hair under the interval\n * every time — and syncs every other hour. */\nconst TICK_JITTER_TOLERANCE_MS = 5 * 60 * 1000;\n\n/** How long a fresh marker suppresses a sync: one interval, less the slack.\n * The slack is halved rather than subtracted flat for a very short configured\n * interval, where a fixed five minutes would swallow the window whole. */\nexport function calendarSyncDueWindowMs(intervalMs: number): number {\n return intervalMs - Math.min(TICK_JITTER_TOLERANCE_MS, intervalMs / 2);\n}\n\n/** True iff no recent enough run stands in for this one.\n *\n * A marker that cannot be read as a time is treated as due: a stuck calendar is\n * worse than a duplicate run, which the sync is built to tolerate anyway\n * (writes are upserts by event id). */\nexport function isCalendarSyncDue(lastSyncedAt: string | null, windowMs: number, now: number = Date.now()): boolean {\n if (!lastSyncedAt) return true;\n const elapsed = now - Date.parse(lastSyncedAt);\n if (!Number.isFinite(elapsed)) return true;\n // A marker from a host whose clock runs ahead would otherwise hold this\n // calendar back for as long as the skew lasts. Beyond a whole window it\n // cannot be a run worth waiting for.\n if (elapsed < -windowMs) return true;\n return elapsed >= windowMs;\n}\n","// Whether a collection has ever received the whole calendar (#2850).\n//\n// The sync token cannot answer this. It is keyed by `calendarId` and nothing\n// else, so it is a claim about the CALENDAR, shared by every consumer of it —\n// the standalone `google` tool's `calendarSync`, and every collection bound to\n// that calendar. Whoever walks first stores a cursor, and the next collection\n// resumes from a window it never received: it gets the delta, reports success\n// with no error, and never holds the history at all.\n//\n// So the claim this file stores is the per-collection one the token is not:\n// \"these records already cover the whole of calendar X\".\n//\n// Kept in the collection's OWN data dir rather than in the shared calendar\n// state, and that placement is the design. The marker describes the records, so\n// it must reset exactly when they do — deleting a collection's data (through\n// `deleteCollection` or by hand, which is what the #2850 reporter did six\n// times) leaves no marker behind claiming a backfill that is gone. The\n// alternative, a slug-keyed entry in `.sync-state.json`, needs a cleanup that\n// stays in step with every delete path — the shape #2428 already was.\n//\n// Dot-prefixed so `listItems` skips it: record files are `<id>.json`, and the\n// reader already ignores names starting with `.`.\nimport path from \"node:path\";\nimport { readJsonOrNull, writeJsonAtomicWithMode } from \"./fsJson.js\";\nimport { canonicalCalendarId } from \"./calendar.js\";\nimport { log } from \"./host.js\";\n\nconst BACKFILL_STATE_MODE = 0o600;\nconst BACKFILL_FILE = \".calendar-sync.json\";\n\ninterface BackfillState {\n /** The canonical calendar these records were walked in full against. */\n calendarId: string;\n /** When that walk landed (ISO). Diagnostics — but its PRESENCE is checked,\n * see `needsCalendarBackfill`. */\n walkedAt: string;\n}\n\n/** `<dataDir>/.calendar-sync.json` */\nexport const calendarBackfillPath = (dataDir: string): string => path.join(dataDir, BACKFILL_FILE);\n\n/** Whether these records still need the whole calendar walked into them.\n *\n * Compared against the calendar the marker NAMES, not merely its presence: a\n * schema repointed at another calendar holds a backfill of the wrong one, and\n * resuming that calendar's cursor would leave the same silent gap this exists\n * to close.\n *\n * Unreadable, absent or INCOMPLETE all answer \"yes\" — every field must be\n * there, not just the one being compared. A redundant full walk costs API\n * calls and rewrites records that already match; skipping a needed one loses\n * the history silently, which is the failure being fixed, so the bias only\n * ever goes one way (CodeRabbit review #2853). */\nexport async function needsCalendarBackfill(dataDir: string, calendarId: string | undefined): Promise<boolean> {\n const stored = await readJsonOrNull<Partial<BackfillState>>(calendarBackfillPath(dataDir));\n if (typeof stored?.walkedAt !== \"string\" || stored.walkedAt === \"\") return true;\n return stored.calendarId !== canonicalCalendarId(calendarId);\n}\n\n/** Record that these records now cover the whole calendar. Never throws: the\n * marker is an optimisation over re-walking, so a workspace that cannot write\n * it syncs correctly and simply walks in full again next time. */\nexport async function markCalendarBackfilled(dataDir: string, calendarId: string | undefined, walkedAt: string): Promise<void> {\n const state: BackfillState = { calendarId: canonicalCalendarId(calendarId), walkedAt };\n try {\n await writeJsonAtomicWithMode(calendarBackfillPath(dataDir), state, BACKFILL_STATE_MODE);\n } catch (error) {\n log.warn(\"google\", \"could not record the calendar backfill marker — the next sync will walk the calendar again\", { dataDir, error: String(error) });\n }\n}\n","// What Google last reported for each synced event — the baseline a push diffs\n// against (#2598).\n//\n// Without it, a local edit and an untouched record are indistinguishable: the\n// pull writes Google's value into the record and keeps no copy, so \"the record\n// says 09:30\" answers nothing about who put it there.\n//\n// It holds the RAW Google values, not a hash of them, because the push has to\n// REBUILD a Google time from a stored one and `toCollectionDateTime` is lossy —\n// it drops the zone offset and flattens all-day into `…T00:00`. The baseline is\n// where both come back from (see `pushDateTime.ts`).\n//\n// Lives inside the workspace next to the sync token, and for the same reason\n// (`calendarSyncStore.ts`): it is a claim about which records the workspace\n// holds, so it must reset when the workspace does.\nimport path from \"node:path\";\nimport { getWorkspaceRoot } from \"../collection/server/host.js\";\nimport { readJsonOrNull, writeJsonAtomicWithMode } from \"./fsJson.js\";\nimport { canonicalCalendarId, type CalendarEventSummary } from \"./calendar.js\";\nimport { stateLockPath, withCalendarStateLock } from \"./calendarStateLock.js\";\n\nconst PUSH_STATE_MODE = 0o600;\n\n/** The pushable subset of an event. `htmlLink` and `status` are read-only on\n * Google's side, so a baseline for them could never be acted on. */\nexport type ShadowEvent = Pick<CalendarEventSummary, \"summary\" | \"start\" | \"end\" | \"colorId\" | \"description\" | \"location\">;\n\n/** calendarId → eventId → what Google last said. */\ninterface PushState {\n events: Record<string, Record<string, ShadowEvent>>;\n}\n\n/** `<workspace>/data/calendar/.push-state.json` */\nexport function calendarPushStatePath(workspaceRoot?: string): string {\n return path.join(workspaceRoot ?? getWorkspaceRoot(), \"data\", \"calendar\", \".push-state.json\");\n}\n\n// A state file written before `description` / `location` joined the pushable set\n// simply lacks those keys. That needs no migration: `fieldText(undefined)` and\n// the `\"\"` Google reports for an unset field both compare as empty, so an\n// upgraded host does not read every record as locally edited.\nexport const toShadowEvent = (event: CalendarEventSummary): ShadowEvent => ({\n summary: event.summary,\n start: event.start,\n end: event.end,\n colorId: event.colorId,\n description: event.description,\n location: event.location,\n});\n\nasync function readState(workspaceRoot?: string): Promise<PushState> {\n const stored = await readJsonOrNull<PushState>(calendarPushStatePath(workspaceRoot));\n return stored?.events ? stored : { events: {} };\n}\n\n// One file holds every calendar's baseline, so an unguarded read-modify-write\n// loses updates — the same trap `calendarSyncStore.ts` documents. Serialising\n// the whole cycle keeps concurrent calendars ordered.\nconst writeQueue: { tail: Promise<unknown> } = { tail: Promise.resolve() };\n\nasync function updateState(mutate: (events: PushState[\"events\"]) => PushState[\"events\"], workspaceRoot?: string): Promise<void> {\n const statePath = calendarPushStatePath(workspaceRoot);\n // Same pairing as `calendarSyncStore.ts`: the queue orders this process, the\n // file lock orders the others (#2679).\n const run = writeQueue.tail.then(\n async () =>\n await withCalendarStateLock(stateLockPath(statePath), async () => {\n const state = await readState(workspaceRoot);\n await writeJsonAtomicWithMode(statePath, { events: mutate(state.events) }, PUSH_STATE_MODE);\n }),\n );\n // Swallow on the queue only — the caller still sees the original rejection.\n writeQueue.tail = run.catch(() => undefined);\n return await run;\n}\n\nexport async function loadCalendarShadow(calendarId?: string, workspaceRoot?: string): Promise<Record<string, ShadowEvent>> {\n const state = await readState(workspaceRoot);\n return state.events[canonicalCalendarId(calendarId)] ?? {};\n}\n\n/** Apply a batch to one calendar's baseline. `null` removes an entry (the event\n * was cancelled), so a cancel-then-recreate cannot resume from a stale one.\n * Merged rather than replaced: a sync window describes only what changed. */\nexport function mergeShadow(current: Record<string, ShadowEvent>, updates: Record<string, ShadowEvent | null>): Record<string, ShadowEvent> {\n const removed = new Set(\n Object.entries(updates)\n .filter(([, entry]) => entry === null)\n .map(([eventId]) => eventId),\n );\n const kept = Object.entries(current).filter(([eventId]) => !removed.has(eventId));\n const written = Object.entries(updates).filter((entry): entry is [string, ShadowEvent] => entry[1] !== null);\n return Object.fromEntries([...kept, ...written]);\n}\n\nexport async function saveCalendarShadow(calendarId: string | undefined, updates: Record<string, ShadowEvent | null>, workspaceRoot?: string): Promise<void> {\n if (Object.keys(updates).length === 0) return;\n const key = canonicalCalendarId(calendarId);\n await updateState((events) => ({ ...events, [key]: mergeShadow(events[key] ?? {}, updates) }), workspaceRoot);\n}\n\n/** Drop one calendar's whole baseline. Called wherever the sync token is\n * cleared: a baseline outliving its token would let a push diff against events\n * the workspace no longer holds. */\nexport async function clearCalendarShadow(calendarId?: string, workspaceRoot?: string): Promise<void> {\n const dropped = canonicalCalendarId(calendarId);\n await updateState((events) => Object.fromEntries(Object.entries(events).filter(([key]) => key !== dropped)), workspaceRoot);\n}\n","// Serialising everything that touches ONE calendar's sync state.\n//\n// A leaf on purpose: both directions need it (the pull in `collectionSync.ts`,\n// the push in `collectionPush.ts`), and the pull now drives the push, so leaving\n// the lock inside either one would make the two import each other.\nimport { canonicalCalendarId } from \"./calendar.js\";\n\n/** Serialise `run` against whatever is already running for `key`.\n *\n * `locks` is passed in so the queuing rule is testable without module state;\n * the key is dropped once nothing is queued behind it, so the map cannot grow\n * an entry per calendar forever. A failed predecessor still releases the\n * queue — `then(run, run)`. */\nexport async function withKeyedLock<T>(locks: Map<string, Promise<unknown>>, key: string, run: () => Promise<T>): Promise<T> {\n const previous = locks.get(key) ?? Promise.resolve();\n const result = previous.then(run, run);\n const tail = result.then(\n () => undefined,\n () => undefined,\n );\n locks.set(key, tail);\n try {\n return await result;\n } finally {\n if (locks.get(key) === tail) locks.delete(key);\n }\n}\n\n/** In-flight work per canonical calendar id. Module state on purpose: the\n * scheduler, the create trigger and the Refresh button are three doors into\n * the same calendar (CodeRabbit review #2566). */\nconst calendarLocks = new Map<string, Promise<unknown>>();\n\n/** Serialise anything that touches ONE calendar's sync state.\n *\n * Shared with the push path (#2598), not only the pull doors: a push that\n * overtakes an in-flight pull would record a baseline for events the pull is\n * still writing, so the next push would diff against a future it never saw.\n *\n * NOT reentrant — a holder must call the lock-free form of whatever it drives,\n * or it waits on itself forever. */\nexport async function withCalendarLock<T>(calendarId: string | undefined, run: () => Promise<T>): Promise<T> {\n return await withKeyedLock(calendarLocks, canonicalCalendarId(calendarId), run);\n}\n","// Collection `datetime` value → the Google Calendar event time to push (#2598).\n//\n// The inverse of `toCollectionDateTime`, which is deliberately LOSSY: it drops\n// the RFC3339 zone designator (so the stored clock stays the one the user reads\n// off Google, independent of the machine that synced) and flattens an all-day\n// `YYYY-MM-DD` into `…T00:00`. Neither the zone nor the all-day-ness survives in\n// the stored value, so they cannot be derived from it.\n//\n// What Google last reported for the event carries both, so a push that has that\n// value reuses it. Only a locally-created record has no such history, and it\n// falls back to the calendar's own timezone — never the host's, which is the\n// machine-dependence `collectionDateTime.ts` exists to avoid.\n//\n// Pure: no I/O, no clock, no locale.\nimport { parseIsoDate, parseIsoDateTime } from \"../collection/core/calendarGrid.js\";\nimport type { CalendarEventTime } from \"./calendar.js\";\n\n/** RFC3339 zone designator: `Z`, `+09:00`, `-0500`. */\nconst ZONE_SUFFIX_RE = /(?:Z|[+-]\\d{2}:?\\d{2})$/;\nconst FRACTIONAL_SECONDS_RE = /\\.\\d+$/;\nconst TWO_DIGITS_RE = /^\\d{2}$/;\nconst CLOCK_SEPARATOR = \":\";\nconst DATE_TIME_SEPARATOR = \"T\";\nconst MIN_CLOCK_SEGMENTS = 2;\nconst MAX_CLOCK_SEGMENTS = 3;\nconst WHOLE_MINUTE_SECONDS = \"00\";\nconst MAX_SECONDS = 59;\n\n/** Whether `value` is a real calendar day (`2026-02-30` is not).\n *\n * Delegated to the parser the record lint, the calendar grid and the day view\n * already share, so the push agrees with the rest of the app on what a valid\n * stored date IS — and rejects an impossible one here instead of turning it\n * into an opaque Google 400. */\nconst isCalendarDate = (value: string): boolean => parseIsoDate(value) !== null;\n\n/** A value Google already accepts verbatim, as the event time to send, or null.\n *\n * Reached when the schema maps `start` onto a `string` field: `projectValue`\n * normalises only `datetime` fields, so that column holds Google's own text,\n * zone and all. The civil part is still validated — matching a trailing `Z` is\n * not evidence that the rest is a date-time. */\nfunction rawGoogleTime(value: string): CalendarEventTime | null {\n if (isCalendarDate(value)) return { date: value };\n const zone = value.match(ZONE_SUFFIX_RE)?.[0];\n if (zone === undefined) return null;\n // Google may answer fractional seconds, which the shared parser rejects.\n // Validate without them, then send the original — Calendar accepts them.\n const civil = value.slice(0, value.length - zone.length).replace(FRACTIONAL_SECONDS_RE, \"\");\n return parseIsoDateTime(civil) === null ? null : { dateTime: value };\n}\n\n/** Google's own all-day `end` is EXCLUSIVE — a single day on the 12th ends on\n * the 13th. The pull stores that as `2026-05-13T00:00` and this takes the date\n * part straight back, so the exclusive end round-trips untouched. Looks\n * off-by-one; adjusting it would shorten every all-day event by a day. */\nfunction allDayFrom(datePart: string): CalendarEventTime {\n return { date: datePart };\n}\n\n/** Split `HH:MM[:SS]` into RFC3339's always-three segments, or null.\n *\n * Split rather than one regex: expressing the optional seconds as `(?::(\\d{2}))?`\n * nests a quantifier inside a quantified group, which the ReDoS lint rejects. */\nfunction clockSegments(clockPart: string): string | null {\n const segments = clockPart.split(CLOCK_SEPARATOR);\n if (segments.length < MIN_CLOCK_SEGMENTS || segments.length > MAX_CLOCK_SEGMENTS) return null;\n if (!segments.every((segment) => TWO_DIGITS_RE.test(segment))) return null;\n const [hours, minutes, seconds] = segments;\n // `parseIsoDateTime` validated the hours and minutes but ignores seconds.\n if (seconds !== undefined && Number(seconds) > MAX_SECONDS) return null;\n return [hours, minutes, seconds ?? WHOLE_MINUTE_SECONDS].join(CLOCK_SEPARATOR);\n}\n\n/** `YYYY-MM-DDTHH:MM[:SS]` → its date and its RFC3339 clock, or null.\n *\n * Validity comes from the shared strict parser, so an out-of-range clock or an\n * impossible day is refused here rather than sent to Google. */\nfunction parseStoredDateTime(value: string): { datePart: string; clock: string } | null {\n if (parseIsoDateTime(value) === null) return null;\n const [datePart, clockPart, ...extra] = value.split(DATE_TIME_SEPARATOR);\n if (datePart === undefined || clockPart === undefined || extra.length > 0) return null;\n const clock = clockSegments(clockPart);\n return clock === null ? null : { datePart, clock };\n}\n\n/** The zone designator to re-attach, or null when `previous` carries none. */\nexport function zoneSuffixOf(previous: string | undefined): string | null {\n if (previous === undefined) return null;\n return previous.match(ZONE_SUFFIX_RE)?.[0] ?? null;\n}\n\n/** Build the event time to send for one field.\n *\n * `previous` is the value Google last reported for this event, absent for a\n * locally-created record. Returns null when `local` is neither a stored\n * datetime nor a raw Google value — the caller reports that record rather than\n * letting this invent a time Google would silently accept. */\nexport function toGoogleEventTime(local: unknown, previous: string | undefined, calendarTimeZone: string): CalendarEventTime | null {\n if (typeof local !== \"string\") return null;\n const trimmed = local.trim();\n if (trimmed.length === 0) return null;\n const raw = rawGoogleTime(trimmed);\n if (raw !== null) return raw;\n const parts = parseStoredDateTime(trimmed);\n if (parts === null) return null;\n // An all-day event stays all-day even when its date moves: the user edited a\n // date in a collection, not the event's kind.\n if (previous !== undefined && isCalendarDate(previous)) return allDayFrom(parts.datePart);\n const zone = zoneSuffixOf(previous);\n const dateTime = `${parts.datePart}${DATE_TIME_SEPARATOR}${parts.clock}`;\n return zone === null ? { dateTime, timeZone: calendarTimeZone } : { dateTime: `${dateTime}${zone}` };\n}\n","// Google Calendar date/dateTime → the shape a collection `datetime` field is\n// validated against (#2310).\n//\n// Google answers with RFC3339 including a zone designator for timed events\n// (`2026-05-12T08:45:00+09:00`, sometimes `…Z`) and a bare `YYYY-MM-DD` for\n// all-day ones. `parseIsoDateTime` — which the record lint, the calendar grid\n// and the day view all share — accepts only `YYYY-MM-DDTHH:MM[:SS]`, so both\n// shapes were stored and then reported as data problems on every synced record.\n//\n// The offset is DROPPED, not applied: the stored clock is then the one the user\n// reads off Google Calendar, and it stays that clock wherever the workspace is\n// opened. Converting to the host's local time would make a record's value\n// depend on the machine that happened to run the sync, and silently shift every\n// record when that machine moves timezone.\n//\n// Pure: no I/O, no clock, no locale.\n\n/** All-day events carry no clock; midnight is where the day view places them. */\nconst ALL_DAY_CLOCK = \"00:00\";\nconst DATE_ONLY_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n/** RFC3339 zone designator: `Z`, `+09:00`, `-0500`. */\nconst ZONE_SUFFIX_RE = /(?:Z|[+-]\\d{2}:?\\d{2})$/;\n/** Fractional seconds have no place in the collection datetime shape. */\nconst FRACTIONAL_SECONDS_RE = /\\.\\d+$/;\n\n/** Normalise one Google Calendar time value for storage in a `datetime` field.\n * Anything that isn't a recognisable Google shape (empty string, non-string,\n * free text) is returned untouched — the record lint should report it rather\n * than have this function invent a value. */\nexport function toCollectionDateTime(value: unknown): unknown {\n if (typeof value !== \"string\") return value;\n const trimmed = value.trim();\n if (DATE_ONLY_RE.test(trimmed)) return `${trimmed}T${ALL_DAY_CLOCK}`;\n if (!trimmed.includes(\"T\")) return value;\n return trimmed.replace(ZONE_SUFFIX_RE, \"\").replace(FRACTIONAL_SECONDS_RE, \"\");\n}\n","// Google event → collection record. Pure: no I/O, no clock, no workspace.\n//\n// A leaf on purpose. The pull writes records with it and the push rebuilds its\n// baseline with it (`pushPlan.ts`), which is what makes \"unchanged\" mean exactly\n// \"the pull would produce this\". Leaving it inside the pull would make the push\n// import the pull, and the pull now drives the push.\nimport type { CollectionFieldSpec, CollectionItem } from \"../collection/core/schema.js\";\nimport type { GOOGLE_CALENDAR_SOURCE_FIELDS } from \"../collection/core/schemaZ.js\";\nimport type { CalendarEventSummary } from \"./calendar.js\";\nimport { toCollectionDateTime } from \"./collectionDateTime.js\";\n\n/** The event fields a schema may map from — narrowed to real keys of\n * `CalendarEventSummary` so the projection below needs no cast. */\nexport type GoogleCalendarSourceField = (typeof GOOGLE_CALENDAR_SOURCE_FIELDS)[number];\n\nconst DATETIME_FIELD_TYPE = \"datetime\";\n\n/** Own-property lookup, mirroring what the record lint reads: a spec reachable\n * only through the prototype chain is not a DECLARED field, so it must not\n * decide how a value is stored. */\nfunction declaredSpec(fields: Record<string, CollectionFieldSpec>, field: string): CollectionFieldSpec | undefined {\n return Object.hasOwn(fields, field) ? fields[field] : undefined;\n}\n\n/** Google's raw value is normalised only into a field the schema declares as\n * `datetime` — that is the type whose stored shape the record lint, the\n * calendar grid and the day view all parse (#2310). A user who maps `start`\n * onto a `string` field asked for Google's value verbatim and keeps it. */\nfunction projectValue(fields: Record<string, CollectionFieldSpec>, field: string, value: string): unknown {\n return declaredSpec(fields, field)?.type === DATETIME_FIELD_TYPE ? toCollectionDateTime(value) : value;\n}\n\n/** Project one Google event onto the collection's own field names. The\n * primary field always takes the event id — upsert-by-id is what keeps the\n * sync idempotent, so it is deliberately not remappable. */\nexport function toCollectionRecord(\n event: CalendarEventSummary,\n map: Record<string, GoogleCalendarSourceField>,\n primaryKey: string,\n fields: Record<string, CollectionFieldSpec>,\n): CollectionItem {\n const mapped = Object.entries(map).map(([field, source]): [string, unknown] => [field, projectValue(fields, field, event[source])]);\n return { ...Object.fromEntries(mapped), [primaryKey]: event.id };\n}\n","// Deciding what a Collection → Google push should do to each record (#2598).\n//\n// Pure: the whole diff rule is expressed against three plain values — the local\n// record, the baseline Google last reported (`calendarPushState.ts`), and\n// Google's value right now — so it is testable without a workspace or a grant.\nimport type { CollectionFieldSpec, CollectionItem } from \"../collection/core/schema.js\";\nimport type { CalendarEventSummary } from \"./calendar.js\";\nimport type { ShadowEvent } from \"./calendarPushState.js\";\nimport { toCollectionRecord, type GoogleCalendarSourceField } from \"./collectionProjection.js\";\n\n/** The event fields Google lets a caller write — a SUBSET of what the pull can\n * read (`GOOGLE_CALENDAR_SOURCE_FIELDS`). A column mapped to one of the\n * pull-only fields is ignored here rather than rejected: the mapping was\n * authored for the pull, and a push has no business invalidating it.\n *\n * `satisfies` is what keeps the two lists honest. A field pushable but not\n * pullable could never have its baseline rebuilt from a pull, so \"unchanged\"\n * would stop meaning anything — this fails the build instead of shipping. */\nexport const PUSHABLE_SOURCE_FIELDS = [\"summary\", \"start\", \"end\", \"colorId\", \"description\", \"location\"] as const satisfies readonly GoogleCalendarSourceField[];\n\nexport type PushableSourceField = (typeof PUSHABLE_SOURCE_FIELDS)[number];\n\nconst isPushableSource = (source: string): source is PushableSourceField => PUSHABLE_SOURCE_FIELDS.some((field) => field === source);\n\n/** The subset of a schema's `map` a push can act on: collection field → event field. */\nexport function pushableMap(map: Record<string, string>): Record<string, PushableSourceField> {\n return Object.fromEntries(Object.entries(map).filter((entry): entry is [string, PushableSourceField] => isPushableSource(entry[1])));\n}\n\n/** Event field → the record column feeding it. The schema authors the map the\n * other way round (for the pull); a push looks up by event field. Two columns\n * mapped to one event field is already ambiguous for the pull — last wins. */\nexport function bySourceField(map: Record<string, PushableSourceField>): Partial<Record<PushableSourceField, string>> {\n return Object.fromEntries(Object.entries(map).map(([field, source]) => [source, field]));\n}\n\n/** Google accepts a caller-chosen event id of 5-1024 base32hex characters. A\n * record id outside that (a semantic id like `team-standup`) cannot be used,\n * and v1 reports it instead of re-keying the record — see the plan. */\nconst CLIENT_EVENT_ID_RE = /^[0-9a-v]{5,1024}$/;\n\nexport const isClientSettableEventId = (eventId: string): boolean => CLIENT_EVENT_ID_RE.test(eventId);\n\n/** A field's value as comparable/sendable text.\n *\n * Absent, null and `\"\"` all collapse to `\"\"`: Google reports an inherited colour\n * as `\"\"` while the record file may omit the key entirely, and treating those as\n * different would report a local edit on every single push.\n *\n * A non-primitive is JSON-encoded rather than left to `String`, whose\n * `[object Object]` would make two DIFFERENT objects compare equal — a\n * silently-skipped edit. */\nexport const fieldText = (value: unknown): string => {\n if (value === undefined || value === null) return \"\";\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"boolean\" || typeof value === \"bigint\") return String(value);\n // `JSON.stringify` answers undefined for a function or symbol — neither is a\n // field value any schema produces, and \"\" keeps them comparing as absent.\n return JSON.stringify(value) ?? \"\";\n};\n\n/** A stored datetime with no seconds — what the collection's own date-time input\n * produces. Google's values always carry seconds, so the pull writes them and a\n * hand-typed value does not. */\nconst SECONDLESS_DATETIME_RE = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$/;\nconst WHOLE_MINUTE = \":00\";\n\n/** Field text with `…T09:30` and `…T09:30:00` collapsed to one spelling.\n *\n * Without this, a record the user typed a time into never stops looking edited:\n * the push writes it, Google answers with seconds, the baseline projection keeps\n * them, and the seconds-less record then differs from its own baseline on every\n * later push — re-PATCHing the same event on every click, forever.\n *\n * A value that carries a zone offset (a `start` mapped onto a `string` field) is\n * left alone: it is Google's own text on both sides already. */\nconst comparableText = (value: unknown): string => {\n const text = fieldText(value);\n return SECONDLESS_DATETIME_RE.test(text) ? `${text}${WHOLE_MINUTE}` : text;\n};\n\n/** The event fields a baseline does NOT carry, because none of them is pushable\n * — the baseline exists to answer \"did the local side change a field we could\n * send?\", and a pull-only field is by definition not one.\n *\n * Typed as the exact complement of `ShadowEvent` + `id`, so a field added to\n * `CalendarEventSummary` without being made pushable must be zeroed here or\n * the build fails. */\nconst UNPUSHABLE_EVENT_FIELDS: Omit<CalendarEventSummary, keyof ShadowEvent | \"id\"> = {\n htmlLink: \"\",\n status: \"\",\n recurringEventId: \"\",\n originalStartTime: \"\",\n updated: \"\",\n transparency: \"\",\n eventType: \"\",\n hangoutLink: \"\",\n selfResponseStatus: \"\",\n conferenceVideoUri: \"\",\n};\n\n/** A baseline shaped as the event it came from, so the comparison can run\n * through the very projection that wrote the record. */\nconst asEventSummary = (eventId: string, shadow: ShadowEvent): CalendarEventSummary => ({ id: eventId, ...UNPUSHABLE_EVENT_FIELDS, ...shadow });\n\n/** What the record WOULD hold if nobody had edited it since the baseline.\n *\n * Deliberately `toCollectionRecord` — the same function the pull writes with —\n * so \"unchanged\" here means exactly \"the pull would produce this\". A private\n * re-implementation would drift from it and report phantom edits. */\nexport function baselineRecord(\n eventId: string,\n shadow: ShadowEvent,\n map: Record<string, PushableSourceField>,\n primaryKey: string,\n fields: Record<string, CollectionFieldSpec>,\n): CollectionItem {\n return toCollectionRecord(asEventSummary(eventId, shadow), map, primaryKey, fields);\n}\n\n/** The mapped fields whose local value no longer matches the baseline. */\nexport function locallyChangedFields(record: CollectionItem, baseline: CollectionItem, map: Record<string, PushableSourceField>): PushableSourceField[] {\n return Object.entries(map)\n .filter(([field]) => comparableText(record[field]) !== comparableText(baseline[field]))\n .map(([, source]) => source);\n}\n\n/** The fields Google changed since the baseline, restricted to the ones the\n * local edit touches.\n *\n * Field-level on purpose: Google moving an event the user retitled locally is\n * not a conflict, because the patch only carries the title. Comparing whole\n * events would refuse those pushes for no reason. */\nexport function conflictingFields(shadow: ShadowEvent, current: ShadowEvent, changed: readonly PushableSourceField[]): PushableSourceField[] {\n return changed.filter((source) => comparableText(shadow[source]) !== comparableText(current[source]));\n}\n\nexport type RecordPlan =\n | { kind: \"create\" }\n | { kind: \"unchanged\" }\n /** Locally edited; `fields` is what to PATCH, pending the conflict check. */\n | { kind: \"changed\"; fields: PushableSourceField[] };\n\n/** Classify one record against its baseline, before Google is consulted.\n *\n * No baseline means the record never came from a sync, so it is local-only and\n * has to be created. Google is queried ONLY for a `changed` record, which is\n * what keeps a push over a large collection cheap. */\nexport function planRecord(\n eventId: string,\n record: CollectionItem,\n shadow: ShadowEvent | undefined,\n map: Record<string, PushableSourceField>,\n primaryKey: string,\n fields: Record<string, CollectionFieldSpec>,\n): RecordPlan {\n if (shadow === undefined) return { kind: \"create\" };\n const changed = locallyChangedFields(record, baselineRecord(eventId, shadow, map, primaryKey, fields), map);\n return changed.length === 0 ? { kind: \"unchanged\" } : { kind: \"changed\", fields: changed };\n}\n\n/** Whether an event that already holds this record's id may be adopted as its\n * baseline.\n *\n * A 409 from `insert` usually means a previous push created the event but its\n * baseline never landed. It can ALSO mean an unrelated event happens to hold\n * this id, and adopting that would bind the record to a stranger's event — so\n * adoption needs evidence the two are the same logical event, not just the same\n * id (Codex review).\n *\n * The evidence is that the remote event ALREADY equals what this record would\n * write. Adoption then records a baseline and writes nothing, so it cannot\n * modify whatever that event is. Anything else is refused. */\nexport function mayAdoptExisting(\n eventId: string,\n record: CollectionItem,\n existing: ShadowEvent,\n map: Record<string, PushableSourceField>,\n primaryKey: string,\n fields: Record<string, CollectionFieldSpec>,\n): boolean {\n return locallyChangedFields(record, baselineRecord(eventId, existing, map, primaryKey, fields), map).length === 0;\n}\n\n/** Record ids the baseline knows but the collection no longer holds — deleted\n * locally. v1 reports the count and pushes nothing: `calendarDeleteEvent`\n * removes the event for every attendee and cannot be undone. */\nexport function locallyDeletedIds(shadow: Record<string, ShadowEvent>, presentIds: readonly string[]): string[] {\n const present = new Set(presentIds);\n return Object.keys(shadow).filter((eventId) => !present.has(eventId));\n}\n","// Collection → Google Calendar push, on demand (#2598).\n//\n// The mirror of `collectionSync.ts`, and deliberately NOT its automatic twin:\n// this runs only when the user clicks, because a push writes to a calendar other\n// people may be reading. v1 creates and updates; it never deletes (a Google\n// delete removes the event for every attendee and cannot be undone).\n//\n// Both directions share `withCalendarLock`, so a push can never interleave with\n// the pull whose baseline it depends on.\nimport { discoverCollections } from \"../collection/server/discovery.js\";\nimport type { LoadedCollection } from \"../collection/server/discoveredCollection.js\";\nimport { storeFor } from \"../collection/server/store.js\";\nimport type { CollectionFieldSpec, CollectionItem } from \"../collection/core/schema.js\";\nimport { getGoogleAccessToken } from \"./auth.js\";\nimport { isGoogleApiError, HTTP_FORBIDDEN } from \"./apiClient.js\";\nimport {\n canonicalCalendarId,\n createCalendarEvent,\n deleteCalendarEvent,\n getCalendarEvent,\n getCalendarMeta,\n listCalendars,\n updateCalendarEvent,\n CANCELLED_EVENT_STATUS,\n EVENT_ABSENT_STATUSES,\n HTTP_CONFLICT,\n HTTP_PRECONDITION_FAILED,\n type CalendarEventInput,\n type CalendarEventSummary,\n type CalendarEventTime,\n type UpdateCalendarEventInput,\n} from \"./calendar.js\";\nimport { loadCalendarShadow, saveCalendarShadow, toShadowEvent, type ShadowEvent } from \"./calendarPushState.js\";\nimport { withCalendarLock } from \"./calendarLock.js\";\nimport { deleteRefusalMessage, planDelete } from \"./deletePlan.js\";\nimport { errorMessage } from \"./util.js\";\nimport { toGoogleEventTime } from \"./pushDateTime.js\";\nimport {\n bySourceField,\n conflictingFields,\n fieldText,\n isClientSettableEventId,\n locallyDeletedIds,\n mayAdoptExisting,\n planRecord,\n pushableMap,\n type PushableSourceField,\n} from \"./pushPlan.js\";\nimport { loadGoogleTokens } from \"./tokenStore.js\";\nimport { log } from \"./host.js\";\n\n/** Access roles that may write events. `reader` / `freeBusyReader` cannot, and a\n * push under them fails per event with an opaque 403 — checked once up front so\n * the user is told the real reason. */\nconst WRITABLE_ACCESS_ROLES: readonly string[] = [\"owner\", \"writer\"];\n\n/** What the up-front writability gate knows about the target calendar. */\nexport interface CalendarWriteTarget {\n /** The caller's role on the calendar, or null when neither source reported one.\n * Null means unknown, never read-only: hard-denying an unknown role would\n * refuse a calendar the user can in fact write to. */\n accessRole: string | null;\n /** IANA zone for rebuilding a zone-less stored clock, `\"\"` when unreported. */\n timeZone: string;\n}\n\n/** Whether to refuse the whole push before touching a single event.\n *\n * Only on POSITIVE evidence of a non-writable role. A calendar whose role\n * neither source reported is unknown and must fall through: hard-denying it\n * would block the feature outright for a calendar the user can in fact write\n * to. Those attempts surface Google's own 403 per record if the write really is\n * not allowed. */\nexport const isDeniedAccessRole = (accessRole: string | null): boolean => accessRole !== null && !WRITABLE_ACCESS_ROLES.includes(accessRole);\n\n/** A role Google left blank is UNKNOWN, not a denial. `isDeniedAccessRole`\n * refuses every role it does not recognise — `\"\"` included — so passing an\n * unreported one straight through would refuse a calendar Google simply said\n * nothing about. */\nexport const reportedAccessRole = (accessRole: string): string | null => accessRole || null;\n\nexport interface CalendarCollectionPushResult {\n slug: string;\n created: number;\n updated: number;\n /** Edited on both sides; skipped so neither version is destroyed. */\n conflicts: number;\n /** Records deleted locally. Reported whether or not the deletion carried, so\n * the count still answers \"how many rows went away here\". */\n localDeletes: number;\n /** Of those, how many were deleted in Google too. Always `0` unless the\n * collection opted in with `propagateDeletes` (#3234). */\n deletedInGoogle: number;\n /** Records that cannot be pushed as they stand, each with the reason. */\n skipped: string[];\n errors: string[];\n /** Records whose local edit did NOT reach Google and would be destroyed by\n * the pull that follows an automatic push (#2620).\n *\n * Only the two states where overwriting loses work with nowhere to recover\n * it from: a both-sides conflict, and an unexpected failure. A `skipped`\n * record is deliberately absent — several of its reasons (an id already\n * taken in Google, an unmappable span) are recovered BY letting the pull\n * write, so protecting them would stall the collection instead. */\n unpushedIds: string[];\n}\n\nexport type CalendarPushOutcome =\n | { kind: \"pushed\"; result: CalendarCollectionPushResult }\n | { kind: \"not-a-calendar\" }\n | { kind: \"not-linked\" }\n | { kind: \"read-only\"; accessRole: string }\n /** The push never got as far as a record: a revoked grant, an unreachable\n * Calendar API, a workspace read failure. Typed rather than left to throw so\n * the route answers in the same shape as the other setup problems instead of\n * an opaque 500. */\n | { kind: \"failed\"; message: string };\n\n/** The I/O a push crosses, injected so every outcome can be exercised with fakes\n * instead of a workspace on disk and a live Google grant. */\nexport interface CalendarPushDeps {\n findCollection: (slug: string, workspaceRoot: string) => Promise<LoadedCollection | null>;\n isLinked: () => Promise<boolean>;\n accessToken: () => Promise<string>;\n calendarMeta: (accessToken: string, calendarId: string | undefined) => Promise<CalendarWriteTarget>;\n /** Read and write for the delete guard. Injected like the rest so a test can\n * exercise \"this event has attendees\" without a grant (#3234).\n *\n * OPTIONAL, unlike the rest: `CalendarPushDeps` is an exported type, and a\n * deps object written before these existed must keep satisfying it — the\n * more so because a collection that never opted into `propagateDeletes`\n * cannot reach either of them. Absent means the live implementation. */\n fetchEvent?: typeof getCalendarEvent;\n deleteEvent?: typeof deleteCalendarEvent;\n}\n\n/** Resolve the calendar a schema names, for its writability and its timezone.\n *\n * The list is consulted first because it answers for every added calendar in\n * one call the push already makes. `\"primary\"` is matched on the `primary`\n * flag, not the id — the primary calendar's own id is the account's email\n * address, which never equals the literal the schema declares.\n *\n * A calendar absent from the list is NOT denied: `calendarList` holds only the\n * calendars the user has added, so one shared with write access can be missing\n * from it (Codex review). It is asked about itself instead, which reports the\n * same zone AND the real role. A 404 there means the calendar is genuinely\n * unreachable, and propagates as a `failed` outcome. */\nasync function liveCalendarMeta(accessToken: string, calendarId: string | undefined): Promise<CalendarWriteTarget> {\n const key = canonicalCalendarId(calendarId);\n const calendars = await listCalendars(accessToken);\n const listed = key === \"primary\" ? calendars.find((calendar) => calendar.primary) : calendars.find((calendar) => calendar.id === key);\n if (listed) return { accessRole: listed.accessRole, timeZone: listed.timeZone };\n const direct = await getCalendarMeta(accessToken, calendarId);\n log.info(\"google\", \"calendar is not in the user's list — role read from the calendar itself\", { calendarId: key, accessRole: direct.accessRole });\n return { accessRole: reportedAccessRole(direct.accessRole), timeZone: direct.timeZone };\n}\n\nasync function findCalendarCollection(slug: string, workspaceRoot: string): Promise<LoadedCollection | null> {\n const all = await discoverCollections({ workspaceRoot });\n return all.find((collection) => collection.slug === slug && collection.schema.googleCalendar) ?? null;\n}\n\nconst liveDeps: CalendarPushDeps = {\n findCollection: findCalendarCollection,\n isLinked: async () => Boolean((await loadGoogleTokens())?.refresh_token),\n accessToken: getGoogleAccessToken,\n calendarMeta: liveCalendarMeta,\n fetchEvent: getCalendarEvent,\n deleteEvent: deleteCalendarEvent,\n};\n\n/** Everything one record's push needs, resolved once for the whole run. */\ninterface PushContext {\n accessToken: string;\n calendarId: string | undefined;\n /** The calendar's IANA zone, `\"\"` when Google did not report one. */\n timeZone: string;\n map: Record<string, PushableSourceField>;\n bySource: Partial<Record<PushableSourceField, string>>;\n primaryKey: string;\n fields: Record<string, CollectionFieldSpec>;\n shadow: Record<string, ShadowEvent>;\n}\n\ntype PushOutcome =\n | { kind: \"created\"; event: CalendarEventSummary }\n | { kind: \"updated\"; event: CalendarEventSummary }\n | { kind: \"conflict\" }\n /** `event` present only when this record's baseline had to be repaired (an\n * event that already existed in Google), so the write path can persist it. */\n | { kind: \"unchanged\"; event?: CalendarEventSummary }\n | { kind: \"skipped\"; message: string }\n | { kind: \"error\"; message: string };\n\nexport type PushOutcomeKind = PushOutcome[\"kind\"];\n\nconst localValue = (ctx: PushContext, record: CollectionItem, source: PushableSourceField): unknown => {\n const field = ctx.bySource[source];\n return field === undefined ? undefined : record[field];\n};\n\ntype TimeResult = { ok: true; time: CalendarEventTime } | { ok: false; reason: string };\n\n/** One end of the span, rebuilt from the stored clock plus whatever the baseline\n * remembers about the original (zone, all-day). */\nfunction eventTime(ctx: PushContext, record: CollectionItem, source: \"start\" | \"end\", previous: string | undefined): TimeResult {\n const value = localValue(ctx, record, source);\n if (value === undefined) return { ok: false, reason: `no \\`${source}\\` field is mapped` };\n const time = toGoogleEventTime(value, previous, ctx.timeZone);\n if (time === null) return { ok: false, reason: `\\`${source}\\` is not a date-time Google accepts (got ${JSON.stringify(value)})` };\n // A zone-less dateTime needs `timeZone`; without one Google answers an opaque 400.\n if (\"dateTime\" in time && time.timeZone === \"\") return { ok: false, reason: `the calendar reports no timezone, so \\`${source}\\` cannot be sent without one` };\n return { ok: true, time };\n}\n\n/** `description` / `location` for a create — omitted when empty, since there is\n * nothing to state about an event that carries neither. */\nconst optionalText = (ctx: PushContext, record: CollectionItem): Pick<CalendarEventInput, \"description\" | \"location\"> => {\n const description = fieldText(localValue(ctx, record, \"description\"));\n const location = fieldText(localValue(ctx, record, \"location\"));\n return { ...(description ? { description } : {}), ...(location ? { location } : {}) };\n};\n\nasync function createFromRecord(ctx: PushContext, eventId: string, record: CollectionItem): Promise<PushOutcome> {\n if (!isClientSettableEventId(eventId)) {\n return { kind: \"skipped\", message: `${eventId}: the record id cannot be used as a Google event id (needs 5-1024 characters from 0-9a-v)` };\n }\n const start = eventTime(ctx, record, \"start\", undefined);\n const end = eventTime(ctx, record, \"end\", undefined);\n if (!start.ok) return { kind: \"skipped\", message: `${eventId}: ${start.reason}` };\n if (!end.ok) return { kind: \"skipped\", message: `${eventId}: ${end.reason}` };\n const colorId = fieldText(localValue(ctx, record, \"colorId\"));\n const event = await createCalendarEvent(ctx.accessToken, {\n eventId,\n calendarId: ctx.calendarId,\n summary: fieldText(localValue(ctx, record, \"summary\")),\n start: start.time,\n end: end.time,\n ...optionalText(ctx, record),\n ...(colorId ? { colorId } : {}),\n });\n return { kind: \"created\", event };\n}\n\ntype EventPatch = Pick<UpdateCalendarEventInput, \"summary\" | \"colorId\" | \"start\" | \"end\" | \"description\" | \"location\">;\ntype PatchResult = { ok: true; patch: EventPatch } | { ok: false; reason: string };\n\n/** The free-text fields of a patch, each only when the local edit touched it.\n * Unlike `colorId`, `\"\"` is a real value here — Google clears the field. */\nconst textPatch = (ctx: PushContext, record: CollectionItem, changed: readonly PushableSourceField[]): EventPatch => {\n const touched = ([\"summary\", \"description\", \"location\"] as const).filter((source) => changed.includes(source));\n return Object.fromEntries(touched.map((source) => [source, fieldText(localValue(ctx, record, source))]));\n};\n\n/** Only the fields the local edit touched — PATCH semantics, so an untouched\n * attendee list / recurrence rule is left alone. */\nfunction buildPatch(ctx: PushContext, record: CollectionItem, changed: readonly PushableSourceField[], shadow: ShadowEvent): PatchResult {\n const resolved = ([\"start\", \"end\"] as const)\n .filter((source) => changed.includes(source))\n .map((source) => ({ source, result: eventTime(ctx, record, source, shadow[source]) }));\n const [failure] = resolved.flatMap((entry) => (entry.result.ok ? [] : [entry.result.reason]));\n if (failure !== undefined) return { ok: false, reason: failure };\n const colorId = fieldText(localValue(ctx, record, \"colorId\"));\n // Calendar rejects \"\" as a palette id, so a cleared colour cannot be sent.\n // Reported rather than dropped: a silently dropped field leaves the record\n // looking edited forever, re-pushing it on every click.\n if (changed.includes(\"colorId\") && colorId === \"\") return { ok: false, reason: \"Google cannot clear an event colour once set\" };\n return {\n ok: true,\n patch: {\n ...textPatch(ctx, record, changed),\n ...(changed.includes(\"colorId\") ? { colorId } : {}),\n ...Object.fromEntries(resolved.flatMap((entry) => (entry.result.ok ? [[entry.source, entry.result.time]] : []))),\n },\n };\n}\n\n/** Google has dropped the event this record mirrors.\n *\n * The advice used to be \"sync first, then re-create it\", which sent people\n * nowhere: Google never resends a cancellation, so a sync sees nothing to do —\n * and before #2688 the sync that DID see it deleted the record along with the\n * edit. Deliberately silent on whether Google will hand the id back; that is\n * unverified. */\nconst goneFromGoogle = (eventId: string): PushOutcome => ({\n kind: \"skipped\",\n message: `${eventId}: Google no longer has this event, and your local edit was kept. Delete this record, or copy it into a new one.`,\n});\n\nasync function updateFromRecord(\n ctx: PushContext,\n eventId: string,\n record: CollectionItem,\n changed: readonly PushableSourceField[],\n shadow: ShadowEvent,\n): Promise<PushOutcome> {\n const fetched = await getCalendarEvent(ctx.accessToken, { calendarId: ctx.calendarId, eventId });\n if (fetched === null || fetched.event.status === CANCELLED_EVENT_STATUS) return goneFromGoogle(eventId);\n if (conflictingFields(shadow, toShadowEvent(fetched.event), changed).length > 0) return { kind: \"conflict\" };\n const built = buildPatch(ctx, record, changed, shadow);\n if (!built.ok) return { kind: \"skipped\", message: `${eventId}: ${built.reason}` };\n try {\n // `If-Match` closes the window between the read above and this write: without\n // it a change landing in between is silently overwritten, which is exactly\n // what the conflict check exists to prevent.\n const event = await updateCalendarEvent(ctx.accessToken, { eventId, calendarId: ctx.calendarId, ifMatch: fetched.etag, ...built.patch });\n return { kind: \"updated\", event };\n } catch (error) {\n if (isGoogleApiError(error) && error.status === HTTP_PRECONDITION_FAILED) return { kind: \"conflict\" };\n throw error;\n }\n}\n\n/** Create, recovering from the one 409 that is provably safe to recover from.\n *\n * A 409 means the id is taken, which is USUALLY a previous push whose baseline\n * never landed. It can also be an unrelated event that happens to hold this id,\n * so the recovery never writes: it adopts the baseline only when the remote\n * event already equals what this record would write, and otherwise reports the\n * fix. Patching whatever answered the 409 would let a push modify a stranger's\n * event (Codex review).\n *\n * Refusal is not a dead end — a Sync writes both the record and its baseline,\n * after which the record pushes normally. */\nasync function createOrAdopt(ctx: PushContext, eventId: string, record: CollectionItem): Promise<PushOutcome> {\n try {\n return await createFromRecord(ctx, eventId, record);\n } catch (error) {\n if (!isGoogleApiError(error) || error.status !== HTTP_CONFLICT) throw error;\n const fetched = await getCalendarEvent(ctx.accessToken, { calendarId: ctx.calendarId, eventId });\n if (fetched === null) throw error;\n if (mayAdoptExisting(eventId, record, toShadowEvent(fetched.event), ctx.map, ctx.primaryKey, ctx.fields)) {\n return { kind: \"unchanged\", event: fetched.event };\n }\n return {\n kind: \"skipped\",\n message: `${eventId}: an event with this id already exists in Google and differs from this record — press Sync to adopt it, then push again`,\n };\n }\n}\n\n/** A 403 on a write is a permissions answer, but `googleApiError` appends the\n * \"is the API enabled for the Cloud project?\" hint to every 403 — true for a\n * disabled API, misleading here. Reached when the up-front gate let the push\n * through on an unknown role, which is exactly when the user needs the real\n * reason. */\nfunction writeFailure(eventId: string, error: unknown): PushOutcome {\n if (isGoogleApiError(error) && error.status === HTTP_FORBIDDEN) {\n return { kind: \"skipped\", message: `${eventId}: Google refused the write — you may not have permission to change events on this calendar` };\n }\n return { kind: \"error\", message: `${eventId}: ${String(error)}` };\n}\n\nasync function pushRecord(ctx: PushContext, eventId: string, record: CollectionItem): Promise<PushOutcome> {\n try {\n const shadow = ctx.shadow[eventId];\n const plan = planRecord(eventId, record, shadow, ctx.map, ctx.primaryKey, ctx.fields);\n if (plan.kind === \"unchanged\") return { kind: \"unchanged\" };\n if (plan.kind === \"create\" || shadow === undefined) return await createOrAdopt(ctx, eventId, record);\n return await updateFromRecord(ctx, eventId, record, plan.fields, shadow);\n } catch (error) {\n // One rejected event must not abandon the rest of the collection.\n return writeFailure(eventId, error);\n }\n}\n\n/** The baseline an outcome establishes, so the next push sees the record as\n * unchanged instead of pushing it again. Empty for anything that wrote nothing\n * and had no baseline to repair. */\nfunction pushedShadow(outcomes: readonly PushOutcome[]): Record<string, ShadowEvent> {\n const written = outcomes.flatMap((outcome) => (\"event\" in outcome && outcome.event !== undefined ? [outcome.event] : []));\n return Object.fromEntries(written.map((event) => [event.id, toShadowEvent(event)]));\n}\n\n/** One record's push, kept with the id so the pull can be told which records to\n * leave alone. */\ninterface PushAttempt {\n eventId: string;\n outcome: PushOutcome;\n}\n\n/** See `unpushedIds` on the result for why `skipped` is not in this set. */\nconst UNPUSHED_KINDS: readonly PushOutcome[\"kind\"][] = [\"conflict\", \"error\"];\n\n/** Whether this outcome leaves a local edit that only exists locally, so the\n * pull must not overwrite the record and the baseline must not advance. */\nexport const isUnpushed = (kind: PushOutcomeKind): boolean => UNPUSHED_KINDS.includes(kind);\n\nfunction tally(slug: string, attempts: readonly PushAttempt[], deletes: DeleteSweep): CalendarCollectionPushResult {\n const outcomes = attempts.map((attempt) => attempt.outcome);\n const count = (kind: PushOutcome[\"kind\"]): number => outcomes.filter((outcome) => outcome.kind === kind).length;\n return {\n slug,\n created: count(\"created\"),\n updated: count(\"updated\"),\n conflicts: count(\"conflict\"),\n localDeletes: deletes.seen,\n deletedInGoogle: deletes.deleted.length,\n skipped: [...outcomes.flatMap((outcome) => (outcome.kind === \"skipped\" ? [outcome.message] : [])), ...deletes.skipped],\n errors: [...outcomes.flatMap((outcome) => (outcome.kind === \"error\" ? [outcome.message] : [])), ...deletes.errors],\n unpushedIds: attempts.filter((attempt) => isUnpushed(attempt.outcome.kind)).map((attempt) => attempt.eventId),\n };\n}\n\n/** Record ids whose local value no longer matches the baseline — edits that have\n * not reached Google.\n *\n * Pure, and deliberately built from `planRecord`, so it answers with exactly the\n * records a push WOULD have acted on. A private re-implementation would drift\n * from the push and protect the wrong set. */\nexport function locallyEditedIds(\n records: readonly CollectionItem[],\n shadow: Record<string, ShadowEvent>,\n map: Record<string, PushableSourceField>,\n primaryKey: string,\n fields: Record<string, CollectionFieldSpec>,\n): string[] {\n const ids = records.map((record) => ({ eventId: fieldText(record[primaryKey]), record }));\n return ids\n .filter(({ eventId, record }) => planRecord(eventId, record, shadow[eventId], map, primaryKey, fields).kind !== \"unchanged\")\n .map(({ eventId }) => eventId);\n}\n\n/** The same set, read from the workspace.\n *\n * Needed when a push cannot run AT ALL — a calendar whose role degraded to\n * reader, a revoked grant, an IO failure. The pull still runs, since reading\n * needs no write access, and would overwrite exactly these records while\n * advancing their baseline past the edit (CodeRabbit review #2666). Computed\n * from the stored baseline alone, so it works precisely when Google is the\n * thing that is unreachable. */\nexport async function unsentLocalEdits(collection: LoadedCollection, workspaceRoot: string): Promise<string[]> {\n const { schema } = collection;\n const shadow = await loadCalendarShadow(schema.googleCalendar?.calendarId, workspaceRoot);\n const records = await storeFor(collection, { workspaceRoot }).list();\n return locallyEditedIds(records, shadow, pushableMap(schema.googleCalendar?.map ?? {}), schema.primaryKey, schema.fields);\n}\n\n/** What one run's deletions did. `seen` counts every record that went away\n * locally, whether or not the deletion carried — so the count means the same\n * thing with the feature off as it always did. */\nexport interface DeleteSweep {\n seen: number;\n deleted: string[];\n skipped: string[];\n errors: string[];\n}\n\nconst noDeletes = (seen: number): DeleteSweep => ({ seen, deleted: [], skipped: [], errors: [] });\n\n/** What `sweepDeletes` needs, narrowed to the three effects it has. Taken as an\n * argument rather than reached for, so the guard can be exercised against\n * \"this event has attendees\" with no grant and no workspace. */\nexport interface DeleteSweepDeps {\n /** What Google currently holds for the event, or null when it is gone. The\n * `etag` is the version the decision was made against. */\n fetchEvent: (eventId: string) => Promise<{ attendeeCount: number; etag: string } | null>;\n /** Delete it, conditional on that version. */\n deleteEvent: (eventId: string, ifMatch: string) => Promise<void>;\n /** Drop the event's baseline entry. */\n forget: (eventId: string) => Promise<void>;\n}\n\n/** A delete whose event changed in Google between the guard reading it and the\n * delete landing. Reported rather than retried: what changed may be the very\n * thing the guard refuses, and re-reading in a loop would race the same way. */\nconst staleDeleteMessage = (eventId: string): string =>\n `${eventId}: left in Google because it changed there while this push was checking it — press Push again to re-check`;\n\n/** Delete ONE event and forget its baseline, or say why it was left alone.\n *\n * The baseline entry goes only after Google confirms, and it must go: the\n * baseline is what `locallyDeletedIds` reads, so a kept entry would re-report\n * the same deletion on every run forever. A refusal keeps its entry on\n * purpose — the event IS still standing in Google, and the report is the only\n * thing saying so.\n *\n * The delete carries the etag the guard read. Without it, an attendee added\n * between the two requests would be withdrawn by a decision taken before they\n * existed — the same race `updateCalendarEvent` already guards, and it costs\n * more here because a delete cannot be undone from this side. */\nasync function propagateOneDelete(eventId: string, deps: DeleteSweepDeps): Promise<Partial<DeleteSweep>> {\n const decision = planDelete(await deps.fetchEvent(eventId));\n if (!decision.ok) {\n // An event already gone needs no report and no baseline: nothing is left to\n // diverge, so dropping the entry stops a permanent phantom deletion.\n if (decision.kind === \"already-gone\") {\n await deps.forget(eventId);\n return {};\n }\n return { skipped: [deleteRefusalMessage(eventId, decision)] };\n }\n try {\n await deps.deleteEvent(eventId, decision.etag);\n } catch (error) {\n if (!isGoogleApiError(error)) throw error;\n // Gone between the read and the delete: the outcome asked for, reached by\n // someone else. Forgetting the baseline is what stops the phantom.\n if (EVENT_ABSENT_STATUSES.includes(error.status)) {\n await deps.forget(eventId);\n return {};\n }\n if (error.status !== HTTP_PRECONDITION_FAILED) throw error;\n return { skipped: [staleDeleteMessage(eventId)] };\n }\n await deps.forget(eventId);\n // Ids only — a summary is personal content, and Google's own Trash is where a\n // deleted event is recovered from, never from this log.\n log.info(\"google\", \"calendar event deleted by push\", { id: eventId });\n return { deleted: [eventId] };\n}\n\n/** Carry every local deletion, when the collection opted in.\n *\n * Sequential rather than concurrent: each one writes the shared baseline file,\n * and an interrupted run has to leave a state the next run resumes from rather\n * than a partially applied batch. */\nexport async function sweepDeletes(eventIds: readonly string[], propagate: boolean, deps: DeleteSweepDeps): Promise<DeleteSweep> {\n if (!propagate || eventIds.length === 0) return noDeletes(eventIds.length);\n const sweep = noDeletes(eventIds.length);\n for (const eventId of eventIds) {\n try {\n const one = await propagateOneDelete(eventId, deps);\n sweep.deleted.push(...(one.deleted ?? []));\n sweep.skipped.push(...(one.skipped ?? []));\n } catch (error) {\n // Reported, not thrown: one unreachable event must not abandon the rest,\n // and its baseline stays, so the next run tries again.\n sweep.errors.push(`${eventId}: could not be deleted in Google — ${errorMessage(error)}`);\n }\n }\n return sweep;\n}\n\n/** Push one collection WITHOUT taking the calendar lock.\n *\n * The caller must already hold it. The scheduled push→pull cycle\n * (`collectionSync.ts`) runs inside the lock it took for the pull, and the lock\n * is not reentrant — going through `pushCalendarForCollection` there would make\n * the sync wait on itself forever. */\nexport async function pushCollectionNow(collection: LoadedCollection, workspaceRoot: string, deps: CalendarPushDeps = liveDeps): Promise<CalendarPushOutcome> {\n const { schema, slug } = collection;\n const calendarId = schema.googleCalendar?.calendarId;\n const accessToken = await deps.accessToken();\n const meta = await deps.calendarMeta(accessToken, calendarId);\n if (isDeniedAccessRole(meta.accessRole)) return { kind: \"read-only\", accessRole: meta.accessRole ?? \"\" };\n\n const map = pushableMap(schema.googleCalendar?.map ?? {});\n const shadow = await loadCalendarShadow(calendarId, workspaceRoot);\n const records = await storeFor(collection, { workspaceRoot }).list();\n const ctx: PushContext = {\n accessToken,\n calendarId,\n timeZone: meta.timeZone,\n map,\n bySource: bySourceField(map),\n primaryKey: schema.primaryKey,\n fields: schema.fields,\n shadow,\n };\n\n const attempts: PushAttempt[] = [];\n for (const record of records) {\n const eventId = fieldText(record[schema.primaryKey]);\n const outcome = await pushRecord(ctx, eventId, record);\n attempts.push({ eventId, outcome });\n // Persisted per record, not once at the end: an interruption after a\n // successful write would otherwise leave that event with no baseline, and\n // the next push would retry it as a create and hit Google's duplicate-id\n // 409. Costs nothing for a record that wrote nothing (empty batch).\n await saveCalendarShadow(calendarId, pushedShadow([outcome]), workspaceRoot);\n }\n const deletedIds = locallyDeletedIds(\n shadow,\n records.map((record) => fieldText(record[schema.primaryKey])),\n );\n const deletes = await sweepDeletes(deletedIds, schema.googleCalendar?.propagateDeletes === true, {\n fetchEvent: (eventId) => (deps.fetchEvent ?? getCalendarEvent)(accessToken, { calendarId, eventId }),\n deleteEvent: (eventId, ifMatch) => (deps.deleteEvent ?? deleteCalendarEvent)(accessToken, { calendarId, eventId, ifMatch }),\n forget: (eventId) => saveCalendarShadow(calendarId, { [eventId]: null }, workspaceRoot),\n });\n const result = tally(slug, attempts, deletes);\n if (result.errors.length > 0) log.warn(\"google\", \"calendar push finished with errors\", { slug, errors: result.errors.length });\n return { kind: \"pushed\", result };\n}\n\n/** Push ONE collection's records to the calendar it declares.\n *\n * \"Does this collection sync at all\" is answered before \"is Google linked\", for\n * the reason `syncCalendarForCollection` gives: telling someone to link their\n * account for a collection that never declared a calendar sends them fixing the\n * wrong thing. */\nexport async function pushCalendarForCollection(slug: string, workspaceRoot: string, deps: CalendarPushDeps = liveDeps): Promise<CalendarPushOutcome> {\n try {\n const collection = await deps.findCollection(slug, workspaceRoot);\n if (collection === null) return { kind: \"not-a-calendar\" };\n if (!(await deps.isLinked())) return { kind: \"not-linked\" };\n return await withCalendarLock(collection.schema.googleCalendar?.calendarId, () => pushCollectionNow(collection, workspaceRoot, deps));\n } catch (error) {\n // A throw here is a setup failure (revoked grant, Calendar API unreachable,\n // unreadable workspace), not a per-record one. Reported in the same typed\n // shape as the other setup problems so the route need not guess.\n log.warn(\"google\", \"calendar push could not start\", { slug, error: String(error) });\n return { kind: \"failed\", message: String(error) };\n }\n}\n","// LLM-free Google Calendar → collection sync (#2095).\n//\n// The whole point of #2095 is that syncing must not cost tokens: this path\n// runs on the scheduler, calls the Calendar REST API directly, and writes\n// records itself. No chat, no agent, no MCP round-trip.\n//\n// Destination is not hardcoded — a collection opts in by declaring\n// `googleCalendar` in its schema, exactly the way a feed opts in by declaring\n// `ingest`. There is no preset calendar collection (the standalone Calendar\n// view was removed in 0.7.0); the user asks for one and the agent authors it.\nimport { stat } from \"node:fs/promises\";\nimport { MISSED_RUN_POLICIES, SCHEDULE_TYPES } from \"@receptron/task-scheduler\";\nimport type { SystemTaskDef } from \"../scheduler/adapter.js\";\nimport type { CollectionItem } from \"../collection/core/schema.js\";\nimport { discoverCollections } from \"../collection/server/discovery.js\";\nimport { getWorkspaceRoot } from \"../collection/server/host.js\";\nimport type { LoadedCollection } from \"../collection/server/discoveredCollection.js\";\nimport type { DeleteItemResult, WriteItemResult } from \"../collection/server/io.js\";\nimport { storeFor } from \"../collection/server/store.js\";\nimport { getGoogleAccessToken } from \"./auth.js\";\nimport { canonicalCalendarId, syncCalendarEvents, CANCELLED_EVENT_STATUS, type CalendarEventSummary } from \"./calendar.js\";\nimport { withCalendarLock } from \"./calendarLock.js\";\nimport { mergeIntoExisting } from \"../collection/core/project.js\";\nimport { toCollectionRecord } from \"./collectionProjection.js\";\nimport { pushCollectionNow, unsentLocalEdits, type CalendarCollectionPushResult, type CalendarPushOutcome } from \"./collectionPush.js\";\nimport {\n claimCalendarSyncIfDue,\n clearCalendarLastSyncedAt,\n clearCalendarSyncToken,\n loadCalendarSyncToken,\n saveCalendarSyncToken,\n} from \"./calendarSyncStore.js\";\nimport { calendarSyncDueWindowMs, isCalendarSyncDue } from \"./calendarSyncDue.js\";\nimport { markCalendarBackfilled, needsCalendarBackfill } from \"./calendarBackfillState.js\";\nimport { clearCalendarShadow, loadCalendarShadow, saveCalendarShadow, toShadowEvent, type ShadowEvent } from \"./calendarPushState.js\";\nimport { baselineRecord, locallyChangedFields, pushableMap } from \"./pushPlan.js\";\nimport { loadGoogleTokens } from \"./tokenStore.js\";\nimport { log } from \"./host.js\";\n\nexport const GOOGLE_CALENDAR_SYNC_TASK_ID = \"system:google-calendar-sync\";\nconst DEFAULT_SYNC_INTERVAL_MS = 60 * 60 * 1000;\n\nexport interface CalendarCollectionSyncResult {\n slug: string;\n written: number;\n removed: number;\n /** Events that can NEVER be stored — e.g. an id the record-file sanitiser\n * rejects. Reported and skipped rather than retried; see `classifyWrite`. */\n unwritable: string[];\n /** Events left alone because the record they would overwrite holds an edit\n * Google has not seen. NOT an error — the token still advances past them; only\n * the baseline is held back, so the next push reports the conflict (#2684). */\n withheld: string[];\n /** Retryable failures. Any of these hold the sync token back. */\n errors: string[];\n}\n\n/** `skipped` is a benign no-op; `unwritable` can never succeed so it must NOT\n * hold the token; `error` is retryable and does hold it; `withheld` is a\n * deliberate refusal to overwrite a local edit. */\ntype ApplyOutcome =\n | { kind: \"written\" }\n | { kind: \"removed\" }\n | { kind: \"skipped\" }\n | { kind: \"withheld\" }\n | { kind: \"unwritable\"; message: string }\n | { kind: \"error\"; message: string };\n\n// `writeItem` / `deleteItem` report most failures by RETURNING a non-`ok` kind\n// rather than throwing. Ignoring that would let the token advance past events\n// that never landed, and Google never resends them (Codex review #2184).\n//\n// But not every failure is worth retrying: an id the record-file sanitiser\n// rejects can never become valid, so holding the token for it would re-fetch\n// the same window forever and permanently kill that calendar's sync — far\n// worse than dropping the one event. Google's own ids are base32hex and pass,\n// but an imported/client-set id may contain characters the sanitiser refuses.\n// (Observed during Claude review, not flagged by a bot.)\nexport function classifyWrite(eventId: string, kind: WriteItemResult[\"kind\"]): ApplyOutcome {\n if (kind === \"ok\") return { kind: \"written\" };\n if (kind === \"invalid-id\") return { kind: \"unwritable\", message: `write ${eventId}: invalid-id` };\n return { kind: \"error\", message: `write ${eventId}: ${kind}` };\n}\n\nexport function classifyDelete(eventId: string, kind: DeleteItemResult[\"kind\"]): ApplyOutcome {\n if (kind === \"ok\") return { kind: \"removed\" };\n // Cancelling an event we never stored is normal, not a failure.\n if (kind === \"not-found\") return { kind: \"skipped\" };\n if (kind === \"invalid-id\") return { kind: \"unwritable\", message: `delete ${eventId}: invalid-id` };\n return { kind: \"error\", message: `delete ${eventId}: ${kind}` };\n}\n\n/** Whether this record still says what the workspace last saw Google say.\n *\n * Compared against the baseline as it stood when this run STARTED READING, not\n * as it stands now: a full re-walk clears the baseline before writing a new one\n * (`restartFullSync`), so reading it live would answer \"no baseline\" for every\n * event and protect nothing exactly when the window is widest (#2684).\n *\n * A record with no baseline at all is NOT withheld — that is an event this\n * workspace has never held, so there is no local edit to lose. */\nexport function unsentEditGuard(\n schema: LoadedCollection[\"schema\"],\n baseline: Record<string, ShadowEvent>,\n): (existing: CollectionItem, eventId: string) => boolean {\n // Built once per collection rather than per event: a full walk runs this over\n // every event the calendar has (Sourcery review #2687).\n const map = pushableMap(schema.googleCalendar?.map ?? {});\n return (existing, eventId) => {\n const shadow = baseline[eventId];\n if (shadow === undefined) return false;\n return locallyChangedFields(existing, baselineRecord(eventId, shadow, map, schema.primaryKey, schema.fields), map).length > 0;\n };\n}\n\n/** What the pull may do to the record behind one event.\n *\n * The guard runs BEFORE the status is consulted, and that order is the whole\n * fix: \"is there something local to lose here?\" outranks \"what did Google do\n * to it?\". Asking about the status first is how a cancellation kept deleting\n * records that held an edit Google had never seen (#2688), long after the\n * same guard had been put in front of the overwrite (#2684). */\nexport function applyPlanFor(\n existing: CollectionItem | null,\n event: CalendarEventSummary,\n hasUnsentEdit: (existing: CollectionItem, eventId: string) => boolean,\n): \"withhold\" | \"delete\" | \"write\" {\n if (existing !== null && hasUnsentEdit(existing, event.id)) return \"withhold\";\n return event.status === CANCELLED_EVENT_STATUS ? \"delete\" : \"write\";\n}\n\n/** Lay Google's mapped values over whatever the record already holds. Split out\n * so `applyEvent` reads as read → decide → act rather than carrying the write\n * itself (CodeRabbit review #2689). */\nasync function writeProjected(\n write: NonNullable<ReturnType<typeof storeFor>[\"write\"]>,\n event: CalendarEventSummary,\n schema: LoadedCollection[\"schema\"],\n existing: CollectionItem | null,\n): Promise<ApplyOutcome> {\n const record = toCollectionRecord(event, schema.googleCalendar?.map ?? {}, schema.primaryKey, schema.fields);\n return classifyWrite(event.id, (await write(event.id, mergeIntoExisting(existing, record))).kind);\n}\n\nasync function applyEvent(\n collection: LoadedCollection,\n event: CalendarEventSummary,\n workspaceRoot: string,\n hasUnsentEdit: (existing: CollectionItem, eventId: string) => boolean,\n): Promise<ApplyOutcome> {\n const { schema } = collection;\n try {\n // Discovery rejects googleCalendar on a read-only (dataSource) schema,\n // so absent write/delete is defense in depth, not a live path. The store\n // threads the slug into the change publish, so an open view updates live.\n const store = storeFor(collection, { workspaceRoot });\n if (!store.write || !store.delete) return { kind: \"unwritable\", message: `collection '${collection.slug}' is read-only` };\n // Read and decide immediately before acting. The set computed back at push\n // time cannot cover an edit made while the window was in flight — minutes\n // of it, on a full walk (#2684).\n const existing = await store.read(event.id);\n const plan = applyPlanFor(existing, event, hasUnsentEdit);\n if (plan === \"withhold\") return { kind: \"withheld\" };\n if (plan === \"delete\") return classifyDelete(event.id, (await store.delete(event.id)).kind);\n return await writeProjected(store.write, event, schema, existing);\n } catch (error) {\n // A thrown IO error (EACCES, ENOSPC, …) must not abort the remaining events\n // or the other collections on this calendar — record it as retryable so the\n // token holds and the next run retries only what failed (CodeRabbit #2184).\n return { kind: \"error\", message: `apply ${event.id}: ${String(error)}` };\n }\n}\n\n/** The events of a window a pull may act on.\n *\n * A record the push just refused to send is edited on BOTH sides. Writing\n * Google's value over it destroys the local edit the push declined to resolve,\n * so the local one stands. Single-sourced because the record write and the\n * baseline write must agree exactly on which events they skip — disagreeing is\n * what would silently overwrite Google on the next push (#2620). */\nexport function pullableEvents(events: readonly CalendarEventSummary[], unpushed: ReadonlySet<string>): CalendarEventSummary[] {\n return events.filter((event) => !unpushed.has(event.id));\n}\n\n/** Report what an automatic push did, since nobody is watching a scheduled run.\n * A conflict or an error means that record now diverges from Google until\n * someone resolves it, which must not be silent. */\nfunction reportAutoPush(slug: string, result: CalendarCollectionPushResult): void {\n const { created, updated, conflicts, skipped, errors, unpushedIds } = result;\n if (created + updated > 0) log.info(\"google\", \"auto-pushed local calendar edits\", { slug, created, updated });\n if (unpushedIds.length > 0) {\n log.warn(\"google\", \"records the auto push could not send — the pull will leave them alone\", { slug, conflicts, unpushedIds });\n }\n if (skipped.length > 0) log.warn(\"google\", \"records the auto push skipped\", { slug, skipped });\n if (errors.length > 0) log.warn(\"google\", \"auto push errors\", { slug, errors });\n}\n\n/** What an automatic push protected, per collection slug. `null` means it could\n * not be worked out at all — see `PROTECTION_UNKNOWN`. */\nexport type UnpushedBySlug = ReadonlyMap<string, ReadonlySet<string> | null>;\n\nconst NOTHING_UNPUSHED: ReadonlySet<string> = new Set();\n\n/** A collection whose protection is unknown must not be pulled this run.\n *\n * Reported through the retryable `errors` channel rather than as a special case:\n * that already holds the sync token AND skips the baseline save, so the window\n * simply replays next run with nothing lost. Failing OPEN here — pulling with no\n * protection — would overwrite the very edits this exists to protect; the read\n * that failed is no evidence that the pull's own writes would fail too, so they\n * would land (CodeRabbit review #2666). */\nexport const PROTECTION_UNKNOWN = \"could not work out which records to protect from the pull\";\n\n/** What ONE collection's pull must leave alone: only what ITS OWN push did not\n * send.\n *\n * Scoped per collection because a calendar can back several of them, and one\n * collection's unsent edit says nothing about the others. Sharing one set across\n * the group starved a collection that never even declares `autoPush`: a\n * neighbour's conflict froze its records — and the sync token still advanced, so\n * Google never resent them (Codex review #2666). */\nexport const unpushedFor = (unpushed: UnpushedBySlug, slug: string): ReadonlySet<string> | null => {\n const protection = unpushed.get(slug);\n return protection === undefined ? NOTHING_UNPUSHED : protection;\n};\n\n/** What the calendar's BASELINE must leave alone: the union over every\n * collection.\n *\n * Deliberately not per collection, unlike the records above. `.push-state.json`\n * holds ONE baseline per calendar, shared by every collection on it, so there is\n * no per-collection baseline to hold back. Advancing it while any collection\n * still has an unresolved conflict is the failure that silently overwrites\n * Google on the next push, and holding it back only ever means \"keep reporting\n * the conflict\" — so the union is the safe side of an asymmetry the shared\n * storage forces.\n *\n * A `null` (unknown) entry contributes nothing, because that collection reports\n * a retryable error instead — which stops the baseline being saved at all. */\nexport const allUnpushed = (unpushed: UnpushedBySlug): ReadonlySet<string> => new Set([...unpushed.values()].flatMap((ids) => (ids === null ? [] : [...ids])));\n\n/** What a collection's pull must protect when its push did not run AT ALL.\n *\n * Registering nothing here was a silent data loss of exactly the kind this\n * feature exists to prevent: a calendar whose role degrades to reader refuses\n * the whole push, yet the pull still runs — reading needs no write access — and\n * overwrote every unsent local edit while advancing its baseline past it, so the\n * next push could not even report the conflict (CodeRabbit review #2666). A\n * collection that never declares `autoPush` is in that same state on every\n * single run, which is how the loss reappeared without a failure in sight\n * (#2683).\n *\n * Protects the edited records rather than all of them, so an unchanged record\n * keeps syncing normally. `null` when even that could not be worked out — the\n * caller then refuses to pull the collection at all, because failing open here\n * destroys exactly what this protects. */\nasync function protectUnsentEdits(collection: LoadedCollection, workspaceRoot: string, deps: PullProtectionDeps): Promise<ReadonlySet<string> | null> {\n try {\n const edited = await deps.unsentEdits(collection, workspaceRoot);\n if (edited.length > 0) log.warn(\"google\", \"protecting local edits that have not reached Google\", { slug: collection.slug, edited });\n return new Set(edited);\n } catch (error) {\n log.warn(\"google\", PROTECTION_UNKNOWN, { slug: collection.slug, error: String(error) });\n return null;\n }\n}\n\n/** The I/O the protection rule crosses, injected so every branch of it can be\n * exercised with fakes instead of a workspace on disk and a live Google grant.\n * The rule is what #2666 and #2683 both got wrong, so it is worth pinning. */\nexport interface PullProtectionDeps {\n pushNow: (collection: LoadedCollection, workspaceRoot: string) => Promise<CalendarPushOutcome>;\n unsentEdits: (collection: LoadedCollection, workspaceRoot: string) => Promise<string[]>;\n}\n\nconst livePullProtectionDeps: PullProtectionDeps = { pushNow: pushCollectionNow, unsentEdits: unsentLocalEdits };\n\n/** What ONE collection's pull must leave alone, pushing it first if it asked to\n * be pushed.\n *\n * A collection WITHOUT `autoPush` never pushes, so its local edits are unsent by\n * definition — the same state a failed push leaves behind, and it needs the same\n * protection. Pulling over them destroys the edit AND advances the baseline past\n * it, after which no conflict can be detected any more (#2683). \"The push did not\n * run\" is the condition that matters here; why it did not run is not.\n *\n * MUST run inside the calendar lock the caller already holds — hence\n * `pushCollectionNow` rather than `pushCalendarForCollection`, which would take\n * the same non-reentrant lock and wait on itself forever.\n *\n * A failed push must not stop the pull: the pull is what keeps the collection\n * fresh, and a revoked write grant is no reason to freeze reading. */\nexport async function pullProtectionFor(\n collection: LoadedCollection,\n workspaceRoot: string,\n deps: PullProtectionDeps = livePullProtectionDeps,\n): Promise<ReadonlySet<string> | null> {\n if (!collection.schema.googleCalendar?.autoPush) return await protectUnsentEdits(collection, workspaceRoot, deps);\n try {\n const outcome = await deps.pushNow(collection, workspaceRoot);\n if (outcome.kind === \"pushed\") {\n reportAutoPush(collection.slug, outcome.result);\n return new Set(outcome.result.unpushedIds);\n }\n log.warn(\"google\", \"auto push did not run\", { slug: collection.slug, reason: outcome.kind });\n } catch (error) {\n log.warn(\"google\", \"auto push failed — pulling anyway\", { slug: collection.slug, error: String(error) });\n }\n return await protectUnsentEdits(collection, workspaceRoot, deps);\n}\n\n/** Push the `autoPush` collections in this group, and answer with what every\n * collection's pull must leave alone, keyed by collection. */\nexport async function pushAndProtect(\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n deps: PullProtectionDeps = livePullProtectionDeps,\n): Promise<UnpushedBySlug> {\n const unpushed = new Map<string, ReadonlySet<string> | null>();\n for (const collection of collections) {\n unpushed.set(collection.slug, await pullProtectionFor(collection, workspaceRoot, deps));\n }\n return unpushed;\n}\n\nasync function restartFullSync(accessToken: string, calendarId: string | undefined, workspaceRoot: string) {\n await clearCalendarSyncToken(calendarId, workspaceRoot);\n // The push baseline describes the records the consumed token accounted for, so\n // it must not outlive that token — a full re-walk rewrites it from scratch.\n await clearCalendarShadow(calendarId, workspaceRoot);\n return await syncCalendarEvents(accessToken, { calendarId });\n}\n\n/** Whether a run may take the calendar, given what the shared marker says.\n *\n * The scheduled door defers to it; every user-facing door claims regardless,\n * because a Refresh click that silently returns nothing reads as an empty\n * calendar, not as \"another host has this\". */\nexport type ClaimGuard = (lastSyncedAt: string | null) => boolean;\n\nconst ALWAYS_CLAIM: ClaimGuard = () => true;\n\n/** Sync ONE calendar and fan its events out to every collection bound to it.\n *\n * The fan-out is not an optimisation, it is correctness: the sync token is\n * keyed by `calendarId`, so syncing collection-by-collection would let the\n * first collection advance the shared token and leave every later collection\n * on the same calendar reading an already-consumed window — silently missing\n * those events forever. Fetch once, apply to all, then advance the token.\n * (Codex + CodeRabbit review on #2184.)\n *\n * Queued per calendar for the same reason the fan-out exists: two passes over\n * one calendar (a Refresh click landing during the scheduled run) would each\n * load the SAME stored token and walk the same window. That is idempotent —\n * writes are upserts by event id — but it is a wasted full walk. Queued, the\n * second pass resumes from the token the first just stored and fetches only\n * what is genuinely new. That queue is module state, so it orders the doors\n * into THIS process only; `claimThenSync` is what other hosts can see. */\nexport async function syncCalendarGroup(\n calendarId: string | undefined,\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n mayClaim: ClaimGuard = ALWAYS_CLAIM,\n): Promise<CalendarCollectionSyncResult[]> {\n return await withCalendarLock(calendarId, () => claimThenSync(calendarId, collections, workspaceRoot, mayClaim));\n}\n\n/** Take the workspace-shared marker BEFORE the calendar is touched, sync only if\n * this run got it, and drop it again if the run could not happen at all.\n *\n * Claiming first is the whole point: hosts tick in the same minute (interval\n * schedules align to wall-clock boundaries), so a marker written on completion\n * would leave the entire run — minutes, for a first full walk — open for a\n * second host to start alongside this one and lose the push baseline it writes.\n *\n * Released only when the sync could not run at all. A run that finished with\n * retryable per-collection errors KEEPS the marker: it already held the token\n * and the baseline back, so it replays on the next tick either way, and\n * releasing would hand a permanently-failing calendar back to both hosts at\n * once — the very overlap this guards (observed during Claude review). */\nasync function claimThenSync(\n calendarId: string | undefined,\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n mayClaim: ClaimGuard,\n): Promise<CalendarCollectionSyncResult[]> {\n if (!(await claimCalendarSync(calendarId, workspaceRoot, mayClaim))) {\n log.info(\"google\", \"skipping a calendar this workspace synced recently — another host may be on it\", { calendarId });\n return [];\n }\n try {\n return await syncCalendarGroupNow(calendarId, collections, workspaceRoot);\n } catch (error) {\n await releaseCalendarSyncClaim(calendarId, workspaceRoot);\n throw error;\n }\n}\n\n/** Never throws, and fails OPEN: the marker guards against duplicate work, it is\n * not a precondition for syncing correctly. A workspace that cannot store it\n * syncs the way it did before #2678. */\nasync function claimCalendarSync(calendarId: string | undefined, workspaceRoot: string, mayClaim: ClaimGuard): Promise<boolean> {\n try {\n return await claimCalendarSyncIfDue(calendarId, new Date().toISOString(), mayClaim, workspaceRoot);\n } catch (error) {\n log.warn(\"google\", \"could not stamp the calendar sync marker — another host may sync this calendar in parallel\", { calendarId, error: String(error) });\n return true;\n }\n}\n\nasync function releaseCalendarSyncClaim(calendarId: string | undefined, workspaceRoot: string): Promise<void> {\n try {\n await clearCalendarLastSyncedAt(calendarId, workspaceRoot);\n } catch (error) {\n log.warn(\"google\", \"could not release the calendar sync marker after a failed run\", { calendarId, error: String(error) });\n }\n}\n\n/** Every event whose baseline must NOT advance: what the push could not send,\n * plus what the apply refused to overwrite.\n *\n * The two must agree exactly. The apply's refusals only became visible after it\n * ran, so they join the union here rather than at push time — leaving them out\n * would advance the baseline past a record the pull deliberately left holding a\n * local edit, which is the silent overwrite this whole path exists to stop. */\nexport const heldBack = (unpushed: UnpushedBySlug, results: readonly CalendarCollectionSyncResult[]): ReadonlySet<string> =>\n new Set([...allUnpushed(unpushed), ...results.flatMap((result) => result.withheld)]);\n\n/** The collections in this group that have never received the whole calendar,\n * by slug. Asked of each collection's OWN records (`calendarBackfillState.ts`),\n * because the sync token cannot answer it — it is keyed by calendar and shared\n * by every consumer of that calendar (#2850). */\nexport async function collectionsNeedingBackfill(collections: readonly LoadedCollection[]): Promise<string[]> {\n const checked = await Promise.all(\n collections.map(async (collection) => {\n const pending = await needsCalendarBackfill(collection.dataDir, collection.schema.googleCalendar?.calendarId);\n return pending ? collection.slug : null;\n }),\n );\n return checked.filter((slug): slug is string => slug !== null);\n}\n\n/** The token this run may resume from — none while any collection in the group\n * still needs the whole calendar.\n *\n * A stored token says how far THE CALENDAR has been read, by whoever read it:\n * the standalone `google` tool's `calendarSync`, or a sibling collection that\n * synced before this one existed. Resuming from it hands a brand-new collection\n * a delta of a window it never received — a handful of records, reported as a\n * success with no error, and never the history the docs promise (#2850).\n *\n * The full walk fans out to the whole group, which is free: writes are upserts,\n * so the collections that were already current simply rewrite what they hold. */\nexport async function resumableToken(\n calendarId: string | undefined,\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n): Promise<string | undefined> {\n const pending = await collectionsNeedingBackfill(collections);\n if (pending.length === 0) return (await loadCalendarSyncToken(calendarId, workspaceRoot)) ?? undefined;\n log.info(\"google\", \"walking the whole calendar — these collections have never received it\", { calendarId, collections: pending });\n return undefined;\n}\n\n/** Record the backfill for every collection the window landed in. Called only\n * after a FULL walk that fully landed: marking one after an incremental window\n * would claim a history the records do not hold. */\nasync function markGroupBackfilled(calendarId: string | undefined, collections: readonly LoadedCollection[]): Promise<void> {\n const walkedAt = new Date().toISOString();\n await Promise.all(collections.map((collection) => markCalendarBackfilled(collection.dataDir, collection.schema.googleCalendar?.calendarId, walkedAt)));\n}\n\n/** What a partial window reports. Names the cause the user can actually act on:\n * a page guard is reached when Google has to expand an enormous number of\n * recurring instances, and one series with no end date is enough. */\nexport const PARTIAL_CALENDAR_WINDOW =\n \"Google returned more pages of events than one sync pass walks, so only part of the calendar was copied. \" +\n \"Give any recurring event with no end date a finite end date, then sync again.\";\n\n/** A truncated walk must not read as a completed one (#2850).\n *\n * Added AFTER the token/baseline gate, not through it. A short fetch is not a\n * failed write: the events that did arrive are Google's own and landed\n * correctly, so their baseline is true and holding it back would freeze them —\n * a record with no baseline reads as an unsent local edit, which the next pull\n * then refuses to touch (#2683). What must not happen is the walk claiming\n * completeness, and that is covered without the gate: `nextSyncToken` only\n * appears on Google's last page, so a truncated walk has no token to advance,\n * and the backfill marker is withheld separately. */\nexport const withPartialWindowError = (results: readonly CalendarCollectionSyncResult[], pagesExhausted: boolean): CalendarCollectionSyncResult[] =>\n pagesExhausted ? results.map((result) => ({ ...result, errors: [...result.errors, PARTIAL_CALENDAR_WINDOW] })) : [...results];\n\n/** What a finished window is allowed to advance.\n *\n * One pure rule rather than three conditions spread through the write-back,\n * because the three answers genuinely differ and each wrong one is a SILENT\n * data bug rather than a visible failure: a baseline held back freezes the\n * records it describes (#2683), a baseline advanced past a failed write hides\n * a conflict (#2620), a backfill marker set on a partial walk re-creates the\n * #2850 gap, and a token advanced past either loses the window for good. */\nexport interface WindowAdvance {\n /** Record what Google now says, so the next push can tell a local edit from a pull. */\n baseline: boolean;\n /** Claim these records now hold the WHOLE calendar. */\n backfill: boolean;\n /** Move the shared cursor past this window. */\n token: boolean;\n}\n\n/** `landed` is the write-side verdict (`windowFullyLanded`); the rest describe\n * the window Google returned.\n *\n * A page-capped walk (`pagesExhausted`) keeps its baseline — the events that\n * arrived are Google's own and were written correctly — but must NOT claim the\n * backfill, or the collection would stop asking for the rest of its calendar.\n * Its token is refused too, though Google makes that moot by sending\n * `nextSyncToken` only on the last page. */\nexport function windowAdvance(window: { landed: boolean; walkedInFull: boolean; pagesExhausted: boolean; nextSyncToken?: string | undefined }): WindowAdvance {\n if (!window.landed) return { baseline: false, backfill: false, token: false };\n const complete = !window.pagesExhausted;\n return { baseline: true, backfill: window.walkedInFull && complete, token: complete && window.nextSyncToken !== undefined };\n}\n\n/** The window this run should read, and whether it was a full walk. A 410\n * restart is a full walk too, so it backfills just as well as a forced one. */\nasync function readWindow(\n calendarId: string | undefined,\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n): Promise<{ result: Awaited<ReturnType<typeof syncCalendarEvents>>; walkedInFull: boolean }> {\n const accessToken = await getGoogleAccessToken();\n const resumeFrom = await resumableToken(calendarId, collections, workspaceRoot);\n const first = await syncCalendarEvents(accessToken, { calendarId, syncToken: resumeFrom });\n const result = first.fullResyncRequired ? await restartFullSync(accessToken, calendarId, workspaceRoot) : first;\n if (result.pagesExhausted) {\n log.warn(\"google\", \"the calendar walk ran out of pages — only part of it was copied\", { calendarId, events: result.events.length });\n }\n return { result, walkedInFull: resumeFrom === undefined || first.fullResyncRequired };\n}\n\nasync function syncCalendarGroupNow(\n calendarId: string | undefined,\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n): Promise<CalendarCollectionSyncResult[]> {\n // Push BEFORE the window is fetched, so a local edit is already in Google when\n // the pull reads it: the record then comes back holding Google's own canonical\n // value, and the baseline agrees with both. Pulling first would overwrite the\n // very edit that was waiting to go up (#2620).\n const unpushed = await pushAndProtect(collections, workspaceRoot);\n\n // Snapshot AFTER the push and BEFORE the window is read. After, because the\n // push writes a baseline per record it sends, and an earlier snapshot would\n // read those records as locally edited. Before, because `restartFullSync`\n // clears the baseline — the snapshot is what survives that clear and lets the\n // apply still tell an edit from an untouched record (#2684).\n const baseline = await loadCalendarShadow(calendarId, workspaceRoot);\n\n const { result, walkedInFull } = await readWindow(calendarId, collections, workspaceRoot);\n const applied = await applyWindowToGroup(collections, result.events, workspaceRoot, unpushed, baseline);\n const advance = windowAdvance({\n landed: windowFullyLanded(calendarId, applied),\n walkedInFull,\n pagesExhausted: result.pagesExhausted,\n nextSyncToken: result.nextSyncToken,\n });\n\n if (advance.baseline) await saveCalendarShadow(calendarId, shadowUpdates(result.events, heldBack(unpushed, applied), baseline), workspaceRoot);\n if (advance.backfill) await markGroupBackfilled(calendarId, collections);\n if (advance.token && result.nextSyncToken) await advanceToken(calendarId, result.nextSyncToken, collections, workspaceRoot);\n return withPartialWindowError(applied, result.pagesExhausted);\n}\n\n/** Apply one window to every collection on the calendar, honouring what each\n * one's own push protected. A collection whose protection could not be worked\n * out is not pulled at all — it reports a retryable error instead, which holds\n * the token and the baseline back for the whole group. */\nasync function applyWindowToGroup(\n collections: readonly LoadedCollection[],\n events: readonly CalendarEventSummary[],\n workspaceRoot: string,\n unpushed: UnpushedBySlug,\n baseline: Record<string, ShadowEvent>,\n): Promise<CalendarCollectionSyncResult[]> {\n const results: CalendarCollectionSyncResult[] = [];\n for (const collection of collections) {\n const protection = unpushedFor(unpushed, collection.slug);\n results.push(\n protection === null\n ? { slug: collection.slug, written: 0, removed: 0, unwritable: [], withheld: [], errors: [PROTECTION_UNKNOWN] }\n : await applyEventsToCollection(collection, events, workspaceRoot, protection, baseline),\n );\n }\n return results;\n}\n\n/** Whether the token and baseline may advance past this window.\n *\n * Google never resends a window, so advancing past a failed write would lose\n * those events for good; holding the token back just replays them next run\n * (writes are idempotent). An `unwritable` event can never succeed, so it does\n * NOT hold the token — but it is logged loudly, since it will silently never\n * appear in the collection. */\nfunction windowFullyLanded(calendarId: string | undefined, results: readonly CalendarCollectionSyncResult[]): boolean {\n // Logged per collection, not flattened across the group: a calendar can back\n // several, and \"one of them is stuck\" is unactionable without the slug — the\n // more so now that `autoPush` runs this unattended (CodeRabbit review #2666).\n results\n .filter((entry) => entry.unwritable.length > 0)\n .forEach((entry) =>\n log.warn(\"google\", \"skipping calendar events that can never be stored\", { calendarId, slug: entry.slug, unwritable: entry.unwritable }),\n );\n // A withheld event is a decision, not a failure: the record kept a local edit\n // Google has not seen, so re-fetching the window would only refuse it again.\n // The token advances past it and the BASELINE is what holds back, which is\n // what keeps the next push able to report the conflict (#2684).\n results\n .filter((entry) => entry.withheld.length > 0)\n .forEach((entry) =>\n log.warn(\"google\", \"leaving calendar records alone — they hold edits Google has not seen\", { calendarId, slug: entry.slug, withheld: entry.withheld }),\n );\n const failed = results.filter((entry) => entry.errors.length > 0);\n failed.forEach((entry) => log.warn(\"google\", \"holding back calendar sync token after failed writes\", { calendarId, slug: entry.slug, errors: entry.errors }));\n return failed.length === 0;\n}\n\n/** The baseline this window establishes: what Google now says per event, and\n * `null` for a cancelled one so a recreate cannot resume from a dead baseline.\n *\n * An event whose record the push could not send is left OUT, and that omission\n * is load-bearing. Advancing its baseline to Google's new value while the record\n * keeps the local one would make the next push read a plain one-sided edit —\n * no conflict to detect any more — and quietly overwrite Google. Held back, the\n * baseline stays older than both sides, so the conflict keeps being reported\n * until someone resolves it (#2620).\n *\n * `held` carries what those events must KEEP. Omitting them is enough on an\n * incremental run, where the file is merged rather than replaced — but a full\n * re-walk CLEARS the baseline first (`restartFullSync`), and there omission\n * drops the entry for good. The next push would then read a conflicted record\n * as a brand-new create, hit Google's duplicate-id 409 and refuse it, instead\n * of reporting the conflict it actually is. Re-stating the pre-run value makes\n * a held-back event behave the same either way (observed during Claude review;\n * no bot flagged it). */\nexport function shadowUpdates(\n events: readonly CalendarEventSummary[],\n unpushed: ReadonlySet<string> = new Set(),\n held: Record<string, ShadowEvent> = {},\n): Record<string, ShadowEvent | null> {\n const advanced = pullableEvents(events, unpushed).map((event): [string, ShadowEvent | null] => [\n event.id,\n event.status === CANCELLED_EVENT_STATUS ? null : toShadowEvent(event),\n ]);\n const kept = [...unpushed].flatMap((eventId): [string, ShadowEvent][] => (held[eventId] === undefined ? [] : [[eventId, held[eventId]]]));\n return Object.fromEntries([...kept, ...advanced]);\n}\n\n/** Save the window's token unless every collection that consumed it was deleted\n * while the sync was in flight.\n *\n * The sync opens with a `discoverCollections()` snapshot, so a delete landing\n * mid-run has already cleared this calendar's token by the time we get here\n * (`releaseOrphanedCalendarToken`). Saving anyway would resurrect exactly the\n * orphan the delete removed, and the next collection on this calendar would\n * resume from a token describing records it never received (#2428).\n *\n * One survivor is enough: it still needs the incremental position. */\nasync function advanceToken(\n calendarId: string | undefined,\n nextSyncToken: string,\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n): Promise<void> {\n if (!(await anySyncedCollectionSurvives(collections))) {\n log.info(\"google\", \"not advancing the sync token — every collection on this calendar was deleted mid-sync\", { calendarId });\n return;\n }\n await saveCalendarSyncToken(calendarId, nextSyncToken, workspaceRoot);\n}\n\nasync function pathExists(absPath: string): Promise<boolean> {\n try {\n await stat(absPath);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Liveness of the collections a sync just wrote to, checked against the skill\n * dir `deleteCollection` removes. `exists` is injected so the rule is testable\n * without a filesystem. An empty group has no survivor by definition. */\nexport async function anySyncedCollectionSurvives(\n collections: readonly Pick<LoadedCollection, \"skillDir\">[],\n exists: (absPath: string) => Promise<boolean> = pathExists,\n): Promise<boolean> {\n const alive = await Promise.all(collections.map((collection) => exists(collection.skillDir)));\n return alive.some(Boolean);\n}\n\nasync function applyEventsToCollection(\n collection: LoadedCollection,\n events: readonly CalendarEventSummary[],\n workspaceRoot: string,\n unpushed: ReadonlySet<string>,\n baseline: Record<string, ShadowEvent>,\n): Promise<CalendarCollectionSyncResult> {\n const hasUnsentEdit = unsentEditGuard(collection.schema, baseline);\n const attempts: { eventId: string; outcome: ApplyOutcome }[] = [];\n for (const event of pullableEvents(events, unpushed)) {\n attempts.push({ eventId: event.id, outcome: await applyEvent(collection, event, workspaceRoot, hasUnsentEdit) });\n }\n const outcomes = attempts.map((attempt) => attempt.outcome);\n return {\n slug: collection.slug,\n written: outcomes.filter((outcome) => outcome.kind === \"written\").length,\n removed: outcomes.filter((outcome) => outcome.kind === \"removed\").length,\n withheld: attempts.flatMap((attempt) => (attempt.outcome.kind === \"withheld\" ? [attempt.eventId] : [])),\n unwritable: outcomes.flatMap((outcome) => (outcome.kind === \"unwritable\" ? [outcome.message] : [])),\n errors: outcomes.flatMap((outcome) => (outcome.kind === \"error\" ? [outcome.message] : [])),\n };\n}\n\n/** A refresh token is what lets the scheduler run unattended; without one\n * there is nothing to sync with. */\nasync function isGoogleLinked(): Promise<boolean> {\n return Boolean((await loadGoogleTokens())?.refresh_token);\n}\n\n/** Group the declaring collections by the calendar they read, so each calendar\n * is fetched exactly once.\n *\n * Keyed by the CANONICAL id, not the declared one: an omitted `calendarId` and\n * an explicit `\"primary\"` address the same calendar and therefore share one\n * sync token, so grouping them apart would let one group advance the token out\n * from under the other — the very loss this grouping exists to prevent\n * (Codex review #2184). */\nexport function groupByCalendar(collections: readonly LoadedCollection[]): Map<string, LoadedCollection[]> {\n const groups = new Map<string, LoadedCollection[]>();\n for (const collection of collections) {\n const key = canonicalCalendarId(collection.schema.googleCalendar?.calendarId);\n groups.set(key, [...(groups.get(key) ?? []), collection]);\n }\n return groups;\n}\n\n/** The minimum a value needs for the orphan check: just the calendar it reads.\n * Structural so the rule can be exercised without building a LoadedCollection. */\nexport interface CalendarDeclaring {\n googleCalendar?: { calendarId?: string | undefined } | undefined;\n}\n\n/** The canonical calendar whose sync token nothing needs any more, or null.\n *\n * Sync tokens are keyed by calendar, NOT by collection, so a deleted\n * collection's token outlives it. Recreating a collection on the same calendar\n * then resumes from that token and receives only the delta — the new\n * collection never gets the history (#2428).\n *\n * Returns null while ANY remaining collection still reads that calendar:\n * clearing a live calendar's token costs a full re-walk on the next sync. The\n * comparison is on the CANONICAL id for the same reason `groupByCalendar` is —\n * an omitted `calendarId` and an explicit `\"primary\"` are one calendar. */\nexport function orphanedCalendarId(deleted: CalendarDeclaring, remaining: readonly CalendarDeclaring[]): string | null {\n if (!deleted.googleCalendar) return null;\n const key = canonicalCalendarId(deleted.googleCalendar.calendarId);\n const stillRead = remaining.some((other) => other.googleCalendar && canonicalCalendarId(other.googleCalendar.calendarId) === key);\n return stillRead ? null : key;\n}\n\n/** Drop the sync token of a just-deleted collection's calendar, unless another\n * collection still reads it. Call AFTER the delete lands — the check reads the\n * collections that survive it.\n *\n * Returns the cleared calendar id, or null when nothing was cleared. Never\n * throws: a failed cleanup must not fail the delete it follows. */\nexport async function releaseOrphanedCalendarToken(deleted: CalendarDeclaring, workspaceRoot: string): Promise<string | null> {\n try {\n if (!deleted.googleCalendar) return null;\n const remaining = await discoverCollections({ workspaceRoot });\n const orphaned = orphanedCalendarId(\n deleted,\n remaining.map((collection) => collection.schema),\n );\n if (orphaned === null) return null;\n await clearCalendarSyncToken(orphaned, workspaceRoot);\n await clearCalendarShadow(orphaned, workspaceRoot);\n await clearCalendarLastSyncedAt(orphaned, workspaceRoot);\n log.info(\"google\", \"cleared the sync token of a calendar no collection reads any more\", { calendarId: orphaned });\n return orphaned;\n } catch (error) {\n log.warn(\"google\", \"could not release the deleted collection's calendar sync token\", { error: String(error) });\n return null;\n }\n}\n\n/** Every declaring collection, grouped by the calendar it reads. */\nasync function declaringGroups(workspaceRoot: string): Promise<Map<string, LoadedCollection[]>> {\n const all = await discoverCollections({ workspaceRoot });\n return groupByCalendar(all.filter((collection) => collection.schema.googleCalendar));\n}\n\n/** Whether a background sync of these groups may run at all.\n *\n * Authoring the collection before linking the account is an expected state,\n * not a failure. Checking once here keeps it a quiet skip instead of an\n * access-token throw per calendar, every hour, until the user links (#2188).\n * A user-triggered sync answers differently — it says so out loud.\n *\n * Deliberately not phrased as a per-host state: the link lives in\n * `~/.config/mulmo`, which carries no app name, so every host on the machine\n * shares one answer. Reading it as \"linked on this host only\" is what made the\n * calendar look implicitly exclusive when it never was (#2678). */\nasync function backgroundSyncAllowed(groups: Map<string, LoadedCollection[]>): Promise<boolean> {\n if (groups.size === 0) return false;\n if (await isGoogleLinked()) return true;\n log.info(\"google\", \"skipping calendar sync — no Google account linked\", { calendars: groups.size });\n return false;\n}\n\n/** Run each group, isolating failures per calendar — one unreachable calendar\n * (or a revoked grant) must not stop the others. `mayClaim` is evaluated per\n * calendar as it comes up, never once for the whole map: an earlier calendar's\n * full walk takes minutes, and a decision made before it started says nothing\n * about who holds this one now. */\nasync function runCalendarGroups(\n groups: Map<string, LoadedCollection[]>,\n workspaceRoot: string,\n mayClaim: ClaimGuard = ALWAYS_CLAIM,\n): Promise<CalendarCollectionSyncResult[]> {\n const results: CalendarCollectionSyncResult[] = [];\n for (const [calendarId, collections] of groups) {\n try {\n results.push(...(await syncCalendarGroup(calendarId, collections, workspaceRoot, mayClaim)));\n } catch (error) {\n log.warn(\"google\", \"calendar sync failed\", { calendarId, error: String(error) });\n results.push(\n ...collections.map((collection) => ({ slug: collection.slug, written: 0, removed: 0, unwritable: [], withheld: [], errors: [String(error)] })),\n );\n }\n }\n return results;\n}\n\n/** Sync every collection whose calendar is due — no host in this workspace has\n * started one within `intervalMs` (#2678). Without that gate this walked every\n * declaring group on every tick, so a second host registering the same task\n * simply doubled the runs, concurrently.\n *\n * Dueness is not decided here, only described: the guard is handed down and\n * evaluated where the marker is written, so the answer cannot go stale between\n * deciding and claiming (Codex review #2680). */\nexport async function syncDueCalendarCollections(\n workspaceRoot: string,\n intervalMs: number = DEFAULT_SYNC_INTERVAL_MS,\n): Promise<CalendarCollectionSyncResult[]> {\n const groups = await declaringGroups(workspaceRoot);\n if (!(await backgroundSyncAllowed(groups))) return [];\n const windowMs = calendarSyncDueWindowMs(intervalMs);\n return await runCalendarGroups(groups, workspaceRoot, (lastSyncedAt) => isCalendarSyncDue(lastSyncedAt, windowMs));\n}\n\n/** The groups holding a collection that still needs the whole calendar.\n *\n * This used to ask whether the CALENDAR had a stored token, which is not the\n * same question and is why the trigger silently did nothing for the #2850\n * reporter: their calendar already had a token — from the standalone `google`\n * tool, and on later attempts from the collection they had just deleted by\n * hand — so the brand-new collection matched nothing and its first sync never\n * ran at all. `pending` is injected so the rule is testable without a\n * workspace on disk.\n *\n * Still self-silencing: a landed full walk marks its collections, so a group\n * stops matching once every collection in it holds the history. */\nexport async function groupsNeedingBackfill<T>(groups: Map<string, T>, pending: (value: T) => Promise<boolean>): Promise<Map<string, T>> {\n const checked = await Promise.all([...groups].map(async (entry) => ((await pending(entry[1])) ? entry : null)));\n return new Map(checked.filter((entry): entry is [string, T] => entry !== null));\n}\n\n/** Sync only the calendars a collection has never received in full — the first\n * sync for a just-created collection, which otherwise stays empty until the\n * hourly scheduler run (#2427). Cheap and safe to call on every config write. */\nexport async function syncNewCalendarCollections(workspaceRoot: string): Promise<CalendarCollectionSyncResult[]> {\n const groups = await declaringGroups(workspaceRoot);\n const pending = await groupsNeedingBackfill(groups, async (collections) => (await collectionsNeedingBackfill(collections)).length > 0);\n if (!(await backgroundSyncAllowed(pending))) return [];\n log.info(\"google\", \"running the first sync for calendars a collection has never received in full\", { calendars: [...pending.keys()] });\n return await runCalendarGroups(pending, workspaceRoot);\n}\n\n/** A user-triggered sync's outcome. `not-a-calendar` and `not-linked` are\n * states the caller must report rather than swallow: a Refresh click that\n * quietly returns \"0 written\" reads as an empty calendar, not as a setup gap. */\nexport type ManualCalendarSyncOutcome = { kind: \"synced\"; results: CalendarCollectionSyncResult[] } | { kind: \"not-a-calendar\" } | { kind: \"not-linked\" };\n\n/** The I/O a manual sync crosses, injectable so the three outcomes can be\n * exercised with fakes instead of a workspace on disk and a live Google grant\n * (CodeRabbit review #2566). */\nexport interface ManualCalendarSyncDeps {\n loadGroups: (workspaceRoot: string) => Promise<Map<string, LoadedCollection[]>>;\n isLinked: () => Promise<boolean>;\n runGroups: (groups: Map<string, LoadedCollection[]>, workspaceRoot: string) => Promise<CalendarCollectionSyncResult[]>;\n}\n\nconst liveManualSyncDeps: ManualCalendarSyncDeps = { loadGroups: declaringGroups, isLinked: isGoogleLinked, runGroups: runCalendarGroups };\n\n/** Sync the calendar ONE collection reads, on demand (the Refresh button).\n *\n * Deliberately syncs the whole group, not just `slug`: the sync token is keyed\n * by calendar, so consuming a window for one collection would leave the others\n * on that calendar reading an already-consumed one. Returns every result of the\n * group so the caller can report the requested slug's own counts.\n *\n * \"Does this collection sync at all\" is answered BEFORE \"is Google linked\":\n * telling someone to link their account for a collection that never declared a\n * calendar sends them fixing the wrong thing. */\nexport async function syncCalendarForCollection(\n slug: string,\n workspaceRoot: string,\n deps: ManualCalendarSyncDeps = liveManualSyncDeps,\n): Promise<ManualCalendarSyncOutcome> {\n const groups = await deps.loadGroups(workspaceRoot);\n const owning = [...groups].filter(([, collections]) => collections.some((collection) => collection.slug === slug));\n if (owning.length === 0) return { kind: \"not-a-calendar\" };\n if (!(await deps.isLinked())) return { kind: \"not-linked\" };\n return { kind: \"synced\", results: await deps.runGroups(new Map(owning), workspaceRoot) };\n}\n\n/** The interval this task is scheduled on right now. A non-interval schedule\n * cannot be reached — the factory below builds an interval one — but the type\n * allows it, so it falls back rather than asserting. */\nfunction scheduledIntervalMs(task: SystemTaskDef): number {\n return task.schedule.type === SCHEDULE_TYPES.interval ? task.schedule.intervalMs : DEFAULT_SYNC_INTERVAL_MS;\n}\n\n/** Scheduler registration, shaped like `feedRefreshTaskDef` so hosts wire it\n * with a single line.\n *\n * `run` reads the interval back off the definition instead of closing over the\n * option: a host rewrites `schedule` from its own overrides file AFTER this\n * returns, and the due window has to follow it. Frozen at the default, a\n * shortened interval would tick often and skip nearly every tick. */\nexport function googleCalendarSyncTaskDef(opts?: { workspaceRoot?: string; intervalMs?: number }): SystemTaskDef {\n const def: SystemTaskDef = {\n id: GOOGLE_CALENDAR_SYNC_TASK_ID,\n name: \"Google Calendar sync\",\n description: \"Pulls changed Google Calendar events into any collection declaring `googleCalendar`, without invoking the LLM.\",\n schedule: { type: SCHEDULE_TYPES.interval, intervalMs: opts?.intervalMs ?? DEFAULT_SYNC_INTERVAL_MS },\n missedRunPolicy: MISSED_RUN_POLICIES.runOnce,\n run: () => syncDueCalendarCollections(opts?.workspaceRoot ?? getWorkspaceRoot(), scheduledIntervalMs(def)).then(() => {}),\n };\n return def;\n}\n","// Google Tasks v1 REST calls. `@default` is Google's alias for the user's\n// default task list, so callers can stay list-agnostic.\nimport { asRecord, googleRequest, itemsOf, stringField, DEFAULT_LIST_MAX_RESULTS } from \"./apiClient.js\";\n\nconst TASKS_BASE_URL = \"https://tasks.googleapis.com/tasks/v1\";\nconst TASKS_API_LABEL = \"Google Tasks API\";\nconst DEFAULT_TASK_LIST_ID = \"@default\";\nconst TASK_STATUS_COMPLETED = \"completed\";\nconst TASK_STATUS_NEEDS_ACTION = \"needsAction\";\nconst MAX_TASK_LISTS = 50;\n\nexport interface TaskListSummary {\n id: string;\n title: string;\n}\n\nexport interface TaskSummary {\n id: string;\n title: string;\n status: string;\n due: string;\n notes: string;\n}\n\nexport interface ListTasksInput {\n taskListId?: string | undefined;\n maxResults?: number | undefined;\n showCompleted?: boolean | undefined;\n}\n\nexport interface CreateTaskInput {\n title: string;\n notes?: string | undefined;\n /** RFC3339. Google stores a DATE only — the time part is recorded but\n * ignored by the UI, so callers should not promise time-of-day fidelity. */\n due?: string | undefined;\n taskListId?: string | undefined;\n}\n\nexport interface CompleteTaskInput {\n taskId: string;\n taskListId?: string | undefined;\n}\n\nexport interface UpdateTaskInput {\n taskId: string;\n title?: string | undefined;\n /** `\"\"` clears the notes; omit to leave them untouched. */\n notes?: string | undefined;\n /** RFC3339. Same DATE-only caveat as `CreateTaskInput.due`. Google rejects\n * `\"\"`, so a due date can be changed but not removed through this call. */\n due?: string | undefined;\n taskListId?: string | undefined;\n}\n\nexport const toTaskListSummary = (value: unknown): TaskListSummary => {\n const record = asRecord(value);\n return { id: stringField(record, \"id\"), title: stringField(record, \"title\") };\n};\n\nexport const toTaskSummary = (value: unknown): TaskSummary => {\n const record = asRecord(value);\n return {\n id: stringField(record, \"id\"),\n title: stringField(record, \"title\"),\n status: stringField(record, \"status\"),\n due: stringField(record, \"due\"),\n notes: stringField(record, \"notes\"),\n };\n};\n\n/** Resolve a declared taskListId to the one the API addresses. `||` (not `??`)\n * so a blank string also falls back instead of building a malformed\n * `/lists//tasks` URL — the same rule, and the same reason, as\n * `canonicalCalendarId`. */\nexport const canonicalTaskListId = (taskListId: string | undefined): string => taskListId?.trim() || DEFAULT_TASK_LIST_ID;\n\nconst tasksUrl = (taskListId: string | undefined, suffix = \"\"): string =>\n `${TASKS_BASE_URL}/lists/${encodeURIComponent(canonicalTaskListId(taskListId))}/tasks${suffix}`;\n\nexport async function listTaskLists(accessToken: string): Promise<TaskListSummary[]> {\n const listed = await googleRequest(TASKS_API_LABEL, accessToken, `${TASKS_BASE_URL}/users/@me/lists?maxResults=${MAX_TASK_LISTS}`);\n return itemsOf(listed).map(toTaskListSummary);\n}\n\nexport async function listTasks(accessToken: string, input: ListTasksInput = {}): Promise<TaskSummary[]> {\n const params = new URLSearchParams({\n maxResults: String(input.maxResults ?? DEFAULT_LIST_MAX_RESULTS),\n showCompleted: String(input.showCompleted ?? false),\n });\n const listed = await googleRequest(TASKS_API_LABEL, accessToken, `${tasksUrl(input.taskListId)}?${params.toString()}`);\n return itemsOf(listed).map(toTaskSummary);\n}\n\nexport async function createTask(accessToken: string, input: CreateTaskInput): Promise<TaskSummary> {\n const body = { title: input.title, notes: input.notes, due: input.due };\n const created = await googleRequest(TASKS_API_LABEL, accessToken, tasksUrl(input.taskListId), { method: \"POST\", body: JSON.stringify(body) });\n return toTaskSummary(created);\n}\n\n/** PATCH body for a task edit — only the fields the caller supplied. As with\n * events, `undefined` means \"leave as is\" and `\"\"` means \"clear it\", so the\n * two must stay distinct. `status` is deliberately absent: `completeTask` /\n * `uncompleteTask` own that transition, and two ways to set it would drift\n * apart. */\nexport const buildTaskPatch = (input: UpdateTaskInput): Record<string, unknown> => ({\n ...(input.title !== undefined ? { title: input.title } : {}),\n ...(input.notes !== undefined ? { notes: input.notes } : {}),\n ...(input.due !== undefined ? { due: input.due } : {}),\n});\n\nexport async function updateTask(accessToken: string, input: UpdateTaskInput): Promise<TaskSummary> {\n const url = tasksUrl(input.taskListId, `/${encodeURIComponent(input.taskId)}`);\n const updated = await googleRequest(TASKS_API_LABEL, accessToken, url, { method: \"PATCH\", body: JSON.stringify(buildTaskPatch(input)) });\n return toTaskSummary(updated);\n}\n\n/** The only two states this module transitions a task between. Spelled as a\n * union rather than `string` so the compiler, not just the doc comment,\n * enforces that there is no third one. */\ntype TaskStatus = typeof TASK_STATUS_COMPLETED | typeof TASK_STATUS_NEEDS_ACTION;\n\n/** Shared body of the two status transitions. PATCH keeps the rest of the task\n * intact — a PUT would need the full body and would silently drop fields the\n * caller never read. Private on purpose: the exported `completeTask` /\n * `uncompleteTask` names are the API, so no caller can invent a third target\n * state by passing an arbitrary status through. */\nasync function patchTaskStatus(accessToken: string, input: CompleteTaskInput, status: TaskStatus): Promise<TaskSummary> {\n const url = tasksUrl(input.taskListId, `/${encodeURIComponent(input.taskId)}`);\n const updated = await googleRequest(TASKS_API_LABEL, accessToken, url, { method: \"PATCH\", body: JSON.stringify({ status }) });\n return toTaskSummary(updated);\n}\n\nexport async function completeTask(accessToken: string, input: CompleteTaskInput): Promise<TaskSummary> {\n return patchTaskStatus(accessToken, input, TASK_STATUS_COMPLETED);\n}\n\n/** Send a completed task back to the to-do list.\n *\n * Its own function rather than a flag on `completeTask`, and deliberately not\n * a `status` field on `updateTask`: one kind per target state keeps the name\n * honest and leaves exactly one code path setting each value.\n *\n * The patch carries `status` alone — mirroring `completeTask`, which also\n * sets only `status` and lets Google fill in the `completed` timestamp. Note\n * that whether Google *clears* that timestamp on the way back is its\n * behaviour, not ours, and is unverified here: `TaskSummary` doesn't carry\n * `completed`, so nothing in this codebase would show a stale one. If a\n * reopened task ever displays a completion date in Google's own UI, this is\n * the place to add `completed: null` to the patch. */\nexport async function uncompleteTask(accessToken: string, input: CompleteTaskInput): Promise<TaskSummary> {\n return patchTaskStatus(accessToken, input, TASK_STATUS_NEEDS_ACTION);\n}\n\nexport async function deleteTask(accessToken: string, input: CompleteTaskInput): Promise<void> {\n const url = tasksUrl(input.taskListId, `/${encodeURIComponent(input.taskId)}`);\n await googleRequest(TASKS_API_LABEL, accessToken, url, { method: \"DELETE\" });\n}\n","// Google Drive v3 REST calls under the `drive.file` scope — the app only\n// ever sees files IT created, never the user's wider Drive. That narrow\n// scope is why this is non-sensitive and needs no Google verification\n// review; keep every call inside it.\nimport { randomBytes } from \"node:crypto\";\nimport { asRecord, googleRequest, stringField, DEFAULT_LIST_MAX_RESULTS } from \"./apiClient.js\";\n\nconst DRIVE_FILES_URL = \"https://www.googleapis.com/drive/v3/files\";\nconst DRIVE_UPLOAD_URL = \"https://www.googleapis.com/upload/drive/v3/files\";\nconst DRIVE_API_LABEL = \"Google Drive API\";\nconst DEFAULT_MIME_TYPE = \"text/plain\";\nconst FILE_FIELDS = \"id,name,mimeType,webViewLink,modifiedTime\";\n// Reading a huge blob into a tool result would blow the context window; the\n// tool surface is for app-created text documents, not media.\nconst MAX_READ_CHARS = 100_000;\nconst TEXT_MIME_PREFIXES = [\"text/\", \"application/json\", \"application/xml\", \"application/javascript\"];\n\nexport interface DriveFileSummary {\n id: string;\n name: string;\n mimeType: string;\n webViewLink: string;\n modifiedTime: string;\n}\n\nexport interface ListDriveFilesInput {\n maxResults?: number | undefined;\n}\n\nexport interface CreateDriveFileInput {\n name: string;\n content: string;\n mimeType?: string | undefined;\n}\n\nexport interface ReadDriveFileInput {\n fileId: string;\n}\n\nexport const toDriveFileSummary = (value: unknown): DriveFileSummary => {\n const record = asRecord(value);\n return {\n id: stringField(record, \"id\"),\n name: stringField(record, \"name\"),\n mimeType: stringField(record, \"mimeType\"),\n webViewLink: stringField(record, \"webViewLink\"),\n modifiedTime: stringField(record, \"modifiedTime\"),\n };\n};\n\nexport const isTextMimeType = (mimeType: string): boolean => TEXT_MIME_PREFIXES.some((prefix) => mimeType.startsWith(prefix));\n\nexport async function listDriveFiles(accessToken: string, input: ListDriveFilesInput = {}): Promise<DriveFileSummary[]> {\n const params = new URLSearchParams({\n pageSize: String(input.maxResults ?? DEFAULT_LIST_MAX_RESULTS),\n fields: `files(${FILE_FIELDS})`,\n orderBy: \"modifiedTime desc\",\n });\n const listed = await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}?${params.toString()}`);\n const record = asRecord(listed);\n const files = Array.isArray(record.files) ? record.files : [];\n return files.map(toDriveFileSummary);\n}\n\n// Multipart upload: metadata part + media part in one request. Built by hand\n// because the `googleapis` SDK is the only alternative and this is the sole\n// multipart call in the engine.\nconst BOUNDARY_BYTES = 16;\n\n/** Fresh per request: a fixed boundary appearing inside file content would\n * split the payload at the wrong place, so Drive would store a truncated or\n * mangled file. Random 128 bits makes an accidental — or crafted — collision\n * infeasible; `newMultipartBoundary` is re-derived until it is absent from\n * the body it must delimit. */\nconst newMultipartBoundary = (): string => `mulmo-drive-${randomBytes(BOUNDARY_BYTES).toString(\"hex\")}`;\n\n// RFC 2045 token + parameters: no CR/LF (header injection into the part\n// headers) and no quotes/semicolons that would let a crafted value forge\n// extra headers or parameters.\nconst MIME_TYPE_RE = /^[A-Za-z0-9!#$&^_.+-]+\\/[A-Za-z0-9!#$&^_.+-]+$/;\n\nexport const assertSafeMimeType = (mimeType: string): string => {\n if (!MIME_TYPE_RE.test(mimeType)) throw new Error(`Google Drive API: invalid mimeType '${mimeType}' — expected a plain type/subtype such as text/plain`);\n return mimeType;\n};\n\nexport const buildMultipartBody = (metadata: Record<string, string>, content: string, mimeType: string, boundary: string): string =>\n [\n `--${boundary}`,\n \"Content-Type: application/json; charset=UTF-8\",\n \"\",\n JSON.stringify(metadata),\n `--${boundary}`,\n `Content-Type: ${mimeType}`,\n \"\",\n content,\n `--${boundary}--`,\n \"\",\n ].join(\"\\r\\n\");\n\n/** A boundary that appears nowhere in the parts it delimits. */\nexport const pickBoundary = (parts: string[], generate: () => string = newMultipartBoundary): string => {\n const collides = (candidate: string): boolean => parts.some((part) => part.includes(candidate));\n let boundary = generate();\n while (collides(boundary)) boundary = generate();\n return boundary;\n};\n\nexport async function createDriveFile(accessToken: string, input: CreateDriveFileInput): Promise<DriveFileSummary> {\n const mimeType = assertSafeMimeType(input.mimeType ?? DEFAULT_MIME_TYPE);\n const metadata = { name: input.name, mimeType };\n const boundary = pickBoundary([input.content, JSON.stringify(metadata)]);\n const params = new URLSearchParams({ uploadType: \"multipart\", fields: FILE_FIELDS });\n const created = await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_UPLOAD_URL}?${params.toString()}`, {\n method: \"POST\",\n contentType: `multipart/related; boundary=${boundary}`,\n body: buildMultipartBody(metadata, input.content, mimeType, boundary),\n });\n return toDriveFileSummary(created);\n}\n\nexport async function readDriveFile(accessToken: string, input: ReadDriveFileInput): Promise<{ file: DriveFileSummary; content: string }> {\n const fileId = encodeURIComponent(input.fileId);\n const metadata = await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}/${fileId}?fields=${FILE_FIELDS}`);\n const file = toDriveFileSummary(metadata);\n if (!isTextMimeType(file.mimeType)) {\n throw new Error(`Google Drive API: '${file.name}' is ${file.mimeType || \"a binary file\"} — only text files can be read as content`);\n }\n const raw = await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}/${fileId}?alt=media`, { expectText: true });\n const content = typeof raw === \"string\" ? raw.slice(0, MAX_READ_CHARS) : \"\";\n return { file, content };\n}\n\nexport async function deleteDriveFile(accessToken: string, input: ReadDriveFileInput): Promise<void> {\n await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}/${encodeURIComponent(input.fileId)}`, { method: \"DELETE\" });\n}\n"],"mappings":"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"}