@mulmoclaude/core 1.4.0 → 1.6.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.
@@ -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/calendarSyncStore.ts","../../src/google/collectionDateTime.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\";\n\nexport function googleConfigDir(home?: string): string {\n return join(home ?? homedir(), \".config\", \"mulmo\");\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 });\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;\nconst 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}\n\nexport async function googleRequest(apiLabel: string, accessToken: string, url: string, init: GoogleRequestInit = {}): Promise<unknown> {\n const { contentType = \"application/json\", expectText = false, ...rest } = init;\n const response = await fetchWithTimeout(url, {\n ...rest,\n timeoutMs: GOOGLE_API_TIMEOUT_MS,\n headers: { 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// An expired/invalidated calendar syncToken.\nconst HTTP_GONE = 410;\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\nexport interface CalendarEventInput {\n summary: string;\n startDateTime: string;\n endDateTime: string;\n description?: string;\n /** Calendar to create the event on; defaults to the user's primary. */\n calendarId?: string;\n /** Event colour (Google event palette id \"1\"..\"11\"); omit to inherit the calendar's colour. */\n colorId?: string;\n}\n\nexport interface UpdateCalendarEventInput {\n eventId: string;\n summary?: string;\n startDateTime?: string;\n endDateTime?: string;\n /** `\"\"` clears the description; omit to leave it untouched. */\n description?: string;\n calendarId?: string;\n colorId?: string;\n}\n\nexport interface DeleteCalendarEventInput {\n eventId: string;\n calendarId?: string;\n}\n\nexport interface ListEventsInput {\n timeMin?: string;\n maxResults?: number;\n /** Calendar to read; defaults to the user's primary. */\n calendarId?: string;\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}\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}\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 };\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 };\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\nexport async function createCalendarEvent(accessToken: string, input: CalendarEventInput): Promise<CalendarEventSummary> {\n const body = {\n summary: input.summary,\n description: input.description,\n start: { dateTime: input.startDateTime },\n end: { dateTime: input.endDateTime },\n ...(input.colorId ? { colorId: input.colorId } : {}),\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. */\nexport const buildEventPatch = (input: UpdateCalendarEventInput): Record<string, unknown> => ({\n ...(input.summary !== undefined ? { summary: input.summary } : {}),\n ...(input.description !== undefined ? { description: input.description } : {}),\n ...(input.startDateTime !== undefined ? { start: { dateTime: input.startDateTime } } : {}),\n ...(input.endDateTime !== undefined ? { end: { dateTime: 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, { method: \"PATCH\", body: JSON.stringify(buildEventPatch(input)) });\n return toEventSummary(updated);\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;\n /** Token from the previous sync. Omit for a full sync. */\n syncToken?: string;\n /** Page size for the underlying list calls. */\n maxResults?: number;\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;\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}\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. */\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 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\n const payload = await fetchSyncPage(accessToken, input.calendarId, params);\n if (payload === GONE) return { events: [], fullResyncRequired: true };\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 }\n\n return { events, nextSyncToken, fullResyncRequired: false };\n}\n\nexport interface CalendarListPage {\n items: unknown[];\n nextPageToken?: string;\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/** 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","// Persistence for the Google Calendar incremental-sync tokens (#2095).\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\";\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}\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\nasync function readState(workspaceRoot?: string): Promise<CalendarSyncState> {\n const stored = await readJsonOrNull<CalendarSyncState>(calendarSyncStatePath(workspaceRoot));\n return stored?.tokens ? stored : { tokens: {} };\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\nasync function updateState(mutate: (tokens: Record<string, string>) => Record<string, string>, workspaceRoot?: string): Promise<void> {\n const run = writeQueue.tail.then(async () => {\n const state = await readState(workspaceRoot);\n await writeJsonAtomicWithMode(calendarSyncStatePath(workspaceRoot), { tokens: mutate(state.tokens) }, SYNC_STATE_MODE);\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 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((tokens) => ({ ...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. */\nexport async function clearCalendarSyncToken(calendarId?: string, workspaceRoot?: string): Promise<void> {\n const dropped = calendarKey(calendarId);\n await updateState((tokens) => Object.fromEntries(Object.entries(tokens).filter(([key]) => key !== dropped)), workspaceRoot);\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","// 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 { 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 type { CollectionFieldSpec, CollectionItem } from \"../collection/core/schema.js\";\nimport type { GOOGLE_CALENDAR_SOURCE_FIELDS } from \"../collection/core/schemaZ.js\";\nimport { getGoogleAccessToken } from \"./auth.js\";\nimport { canonicalCalendarId, syncCalendarEvents, type CalendarEventSummary } from \"./calendar.js\";\nimport { toCollectionDateTime } from \"./collectionDateTime.js\";\nimport { clearCalendarSyncToken, loadCalendarSyncToken, saveCalendarSyncToken } from \"./calendarSyncStore.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;\nconst CANCELLED_STATUS = \"cancelled\";\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 /** Retryable failures. Any of these hold the sync token back. */\n errors: string[];\n}\n\n/** The event fields a schema may map from — narrowed to real keys of\n * `CalendarEventSummary` so the projection below needs no cast. */\ntype 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]) => [field, projectValue(fields, field, event[source])]);\n return { ...Object.fromEntries(mapped), [primaryKey]: event.id };\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. */\ntype ApplyOutcome =\n { kind: \"written\" } | { kind: \"removed\" } | { kind: \"skipped\" } | { kind: \"unwritable\"; message: string } | { 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\nasync function applyEvent(collection: LoadedCollection, event: CalendarEventSummary, workspaceRoot: string): 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 if (event.status === CANCELLED_STATUS) {\n const deleted = await store.delete(event.id);\n return classifyDelete(event.id, deleted.kind);\n }\n const record = toCollectionRecord(event, schema.googleCalendar?.map ?? {}, schema.primaryKey, schema.fields);\n const written = await store.write(event.id, record);\n return classifyWrite(event.id, written.kind);\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\nasync function restartFullSync(accessToken: string, calendarId: string | undefined, workspaceRoot: string) {\n await clearCalendarSyncToken(calendarId, workspaceRoot);\n return await syncCalendarEvents(accessToken, { calendarId });\n}\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 sync 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/** 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. */\nexport async function syncCalendarGroup(\n calendarId: string | undefined,\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n): Promise<CalendarCollectionSyncResult[]> {\n return await withKeyedLock(calendarLocks, canonicalCalendarId(calendarId), () => syncCalendarGroupNow(calendarId, collections, workspaceRoot));\n}\n\nasync function syncCalendarGroupNow(\n calendarId: string | undefined,\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n): Promise<CalendarCollectionSyncResult[]> {\n const accessToken = await getGoogleAccessToken();\n const storedToken = await loadCalendarSyncToken(calendarId, workspaceRoot);\n const first = await syncCalendarEvents(accessToken, { calendarId, syncToken: storedToken ?? undefined });\n const result = first.fullResyncRequired ? await restartFullSync(accessToken, calendarId, workspaceRoot) : first;\n\n const results: CalendarCollectionSyncResult[] = [];\n for (const collection of collections) {\n results.push(await applyEventsToCollection(collection, result.events, workspaceRoot));\n }\n // Advance the token only after every collection in the group consumed the\n // window AND every record actually landed. Google never resends a window, so\n // advancing past a failed write would lose those events for good; holding the\n // token back just replays them next run (writes are idempotent).\n const unwritable = results.flatMap((entry) => entry.unwritable);\n if (unwritable.length > 0) {\n // Never retryable, so the token still advances — but say so loudly, since\n // these events will silently never appear in the collection.\n log.warn(\"google\", \"skipping calendar events that can never be stored\", { calendarId, unwritable });\n }\n const failed = results.flatMap((entry) => entry.errors);\n if (failed.length > 0) {\n log.warn(\"google\", \"holding back calendar sync token after failed writes\", { calendarId, failed: failed.length });\n return results;\n }\n if (result.nextSyncToken) await advanceToken(calendarId, result.nextSyncToken, collections, workspaceRoot);\n return results;\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): Promise<CalendarCollectionSyncResult> {\n const outcomes: ApplyOutcome[] = [];\n for (const event of events) {\n outcomes.push(await applyEvent(collection, event, workspaceRoot));\n }\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 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 };\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 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. */\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 on this host\", { 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. */\nasync function runCalendarGroups(groups: Map<string, LoadedCollection[]>, workspaceRoot: string): Promise<CalendarCollectionSyncResult[]> {\n const results: CalendarCollectionSyncResult[] = [];\n for (const [calendarId, collections] of groups) {\n try {\n results.push(...(await syncCalendarGroup(calendarId, collections, workspaceRoot)));\n } catch (error) {\n log.warn(\"google\", \"calendar sync failed\", { calendarId, error: String(error) });\n results.push(...collections.map((collection) => ({ slug: collection.slug, written: 0, removed: 0, unwritable: [], errors: [String(error)] })));\n }\n }\n return results;\n}\n\n/** Sync every collection that declares `googleCalendar`. */\nexport async function syncDueCalendarCollections(workspaceRoot: string): Promise<CalendarCollectionSyncResult[]> {\n const groups = await declaringGroups(workspaceRoot);\n return (await backgroundSyncAllowed(groups)) ? await runCalendarGroups(groups, workspaceRoot) : [];\n}\n\n/** The groups whose calendar has never synced. A missing token IS the \"created\n * since the last sync\" signal — nothing else distinguishes a new collection\n * from an edited one on the write path this feeds (#2427).\n *\n * Self-silencing by construction: the first sync stores a token, so a calendar\n * matches at most once. `loadToken` is injected so the rule is testable without\n * a workspace on disk. */\nexport async function unsyncedGroups<T>(groups: Map<string, T>, loadToken: (calendarId: string) => Promise<string | null>): Promise<Map<string, T>> {\n const checked = await Promise.all([...groups].map(async (entry) => ((await loadToken(entry[0])) === null ? entry : null)));\n return new Map(checked.filter((entry): entry is [string, T] => entry !== null));\n}\n\n/** Sync only the calendars that have never synced — the first sync for a\n * just-created collection, which otherwise stays empty until the hourly\n * 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 unsyncedGroups(groups, (calendarId) => loadCalendarSyncToken(calendarId, workspaceRoot));\n if (!(await backgroundSyncAllowed(pending))) return [];\n log.info(\"google\", \"running the first sync for newly declared calendars\", { 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/** Scheduler registration, shaped like `feedRefreshTaskDef` so hosts wire it\n * with a single line. */\nexport function googleCalendarSyncTaskDef(opts?: { workspaceRoot?: string; intervalMs?: number }): SystemTaskDef {\n return {\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()).then(() => {}),\n };\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 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;\n maxResults?: number;\n showCompleted?: boolean;\n}\n\nexport interface CreateTaskInput {\n title: string;\n notes?: string;\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;\n taskListId?: string;\n}\n\nexport interface CompleteTaskInput {\n taskId: string;\n taskListId?: string;\n}\n\nexport interface UpdateTaskInput {\n taskId: string;\n title?: string;\n /** `\"\"` clears the notes; omit to leave them untouched. */\n notes?: string;\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;\n taskListId?: string;\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 * owns that transition, and two ways to set it would drift 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\nexport async function completeTask(accessToken: string, input: CompleteTaskInput): Promise<TaskSummary> {\n // PATCH keeps the rest of the task intact — a PUT would need the full body\n // and would silently drop fields the caller never read.\n const url = tasksUrl(input.taskListId, `/${encodeURIComponent(input.taskId)}`);\n const updated = await googleRequest(TASKS_API_LABEL, accessToken, url, {\n method: \"PATCH\",\n body: JSON.stringify({ status: TASK_STATUS_COMPLETED }),\n });\n return toTaskSummary(updated);\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;\n}\n\nexport interface CreateDriveFileInput {\n name: string;\n content: string;\n mimeType?: string;\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":";;;;;;;;;;;;;;;;;;AAWA,IAAM,WAAW,kBAAA,eAA6B,iDAAiD;AAE/F,SAAgB,oBAAoB,SAAsC;CACxE,SAAS,IAAI,QAAQ,GAAG;AAC1B;AAEA,IAAa,MAAoB,kBAAA,6BAA6B,SAAS,KAAK,CAAC;;;ACV7E,IAAM,+BAA+B;AACrC,IAAM,0BAAwB;AAE9B,IAAM,WAAW;AACjB,IAAM,aAAa;AACnB,IAAM,aAAa;AAKnB,IAAM,sBAAsB,MAAc,OAAe,QAAyB;CAChF,MAAM,YAAY,IAAI,KAAK,KAAK,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC;CACzD,OAAO,UAAU,eAAe,MAAM,QAAQ,UAAU,YAAY,MAAM,QAAQ,KAAK,UAAU,WAAW,MAAM;AACpH;AAEA,IAAa,2BAA2B,UAA2B;CACjE,MAAM,QAAQ,6BAA6B,KAAK,MAAM,QAAQ,yBAAuB,EAAE,CAAC;CACxF,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,CAAC,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,KAAK,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,MAAM;CACrG,MAAM,cAAc,QAAQ,YAAY,UAAU,cAAc,UAAU;CAE1E,MAAM,gBAAgB,MAAM,OAAO,KAAA,KAAc,OAAO,MAAM,EAAE,KAAK,YAAY,OAAO,MAAM,EAAE,KAAK;CACrG,OAAO,mBAAmB,MAAM,OAAO,GAAG,KAAK,eAAe;AAChE;;;ACrBA,SAAgB,gBAAgB,MAAuB;CACrD,QAAA,GAAA,UAAA,KAAA,CAAY,SAAA,GAAA,QAAA,QAAA,CAAgB,GAAG,WAAW,OAAO;AACnD;;AAGA,SAAgB,sBAAsB,MAAuB;CAC3D,QAAA,GAAA,UAAA,KAAA,CAAY,SAAA,GAAA,QAAA,QAAA,CAAgB,GAAG,WAAW,eAAe,mBAAmB;AAC9E;AAEA,SAAgB,gBAAgB,MAAuB;CACrD,QAAA,GAAA,UAAA,KAAA,CAAY,gBAAgB,IAAI,GAAG,mBAAmB;AACxD;AAEA,SAAgB,iBAAiB,MAAuB;CACtD,QAAA,GAAA,UAAA,KAAA,CAAY,SAAA,GAAA,QAAA,QAAA,CAAgB,GAAG,UAAU;AAC3C;;;AChBA,IAAa,gBAAgB;AAC7B,IAAa,gBAAgB;AAE7B,SAAgB,SAAS,OAAkD;CACzE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;;;ACQA,IAAM,oBAAoB,UAAoC,OAAO,UAAU,YAAY,UAAU;AAErG,IAAM,2BAA2B,UAAkE;CACjG,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,MAAM,SAAS,GAAG,OAAO;CAC3D,OAAO,iBAAiB,MAAM,UAAU,SAAS,KAAK,iBAAiB,MAAM,UAAU,aAAa;AACtG;AAEA,IAAM,0BAA0B,SAA0B,KAAK,WAAW,gBAAgB,KAAK,KAAK,SAAS,OAAO;AAEpH,IAAM,sBAAsB,OAAO,aAA6C;CAC9E,IAAI;EAEF,OAAO,wBADiB,KAAK,MAAM,OAAA,GAAA,iBAAA,SAAA,CAAe,UAAU,OAAO,CACpC,CAAM,IAAI,WAAW;CACtD,QAAQ;EAGN,OAAO;CACT;AACF;;AAGA,eAAe,6BAA6B,MAAkC;CAC5E,MAAM,MAAM,iBAAiB,IAAI;CAEjC,MAAM,cAAa,OAAA,GAAA,iBAAA,QAAA,CADW,GAAG,CAAC,CAAC,YAAsB,CAAC,CAAC,EAAA,CAChC,OAAO,sBAAsB,CAAC,CAAC,KAAK;CAE/D,QAAO,MADe,QAAQ,IAAI,WAAW,KAAK,SAAS,qBAAA,GAAA,UAAA,KAAA,CAAyB,KAAK,IAAI,CAAC,CAAC,CAAC,EAAA,CACjF,QAAQ,SAAyB,SAAS,IAAI;AAC/D;AAQA,eAAsB,qBAAqB,MAA8C;CACvF,MAAM,UAAU,MAAM,6BAA6B,IAAI;CACvD,IAAI,QAAQ,WAAW,GAAG,OAAO;CACjC,OAAO,QAAQ,WAAW,IAAI,UAAU;AAC1C;AAEA,eAAsB,qBAAqB,MAAgC;CACzE,MAAM,MAAM,iBAAiB,IAAI;CACjC,MAAM,UAAU,MAAM,6BAA6B,IAAI;CACvD,MAAM,CAAC,OAAO,GAAG,QAAQ;CACzB,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,gDAAgD,IAAI,uDAAuD;CAE7H,IAAI,KAAK,SAAS,GAAG;EACnB,MAAM,QAAQ,QAAQ,KAAK,SAAS,KAAK,MAAM,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;EACzE,MAAM,IAAI,MAAM,4DAA4D,IAAI,IAAI,MAAM,qBAAqB;CACjH;CACA,OAAO;AACT;AAEA,eAAsB,iBAAiB,MAA+C;CACpF,MAAM,WAAW,MAAM,qBAAqB,IAAI;CAChD,MAAM,SAAkB,KAAK,MAAM,OAAA,GAAA,iBAAA,SAAA,CAAe,UAAU,OAAO,CAAC;CACpE,IAAI,CAAC,wBAAwB,MAAM,GACjC,MAAM,IAAI,MAAM,GAAG,SAAS,+FAA+F;CAE7H,OAAO;EAAE,WAAW,OAAO,UAAU;EAAW,eAAe,OAAO,UAAU;CAAc;AAChG;;;AC9EA,IAAM,cAAc;AAEpB,eAAsB,eAAkB,UAAqC;CAC3E,IAAI;EACF,MAAM,MAAM,MAAM,QAAA,SAAI,SAAS,UAAU,OAAO;EAEhD,OADkB,KAAK,MAAM,GACtB;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;AAKA,eAAsB,wBAAwB,UAAkB,MAAe,MAA6B;CAC1G,MAAM,eAAA,gBAAgB,UAAU,KAAK,UAAU,MAAM,MAAM,WAAW,GAAG;EAAE;EAAM,WAAW;CAAM,CAAC;AACrG;;;ACZA,IAAM,kBAAkB;AAYxB,SAAgB,kBAAkB,UAAqC,UAAkD;CACvH,MAAM,SAAS;EAAE,GAAG;EAAU,GAAG;CAAS;CAC1C,IAAI,CAAC,SAAS,iBAAiB,UAAU,eAAe,OAAO,gBAAgB,SAAS;CAExF,IAAI,CAAC,SAAS,aAAa,UAAU,WAAW,OAAO,YAAY,SAAS;CAC5E,OAAO;AACT;AAEA,IAAM,aAAa,OAAO,aACxB,OAAA,GAAA,iBAAA,KAAA,CAAW,QAAQ,CAAC,CAAC,WACb,YACA,KACR;AASF,eAAe,uBAAuB,MAA8B;CAClE,MAAM,UAAU,gBAAgB,IAAI;CACpC,MAAM,SAAS,sBAAsB,IAAI;CACzC,IAAI,CAAE,MAAM,WAAW,MAAM,GAAI;CACjC,OAAA,GAAA,iBAAA,MAAA,CAAY,UAAA,QAAK,QAAQ,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;CACtD,IAAI;EACF,OAAA,GAAA,iBAAA,SAAA,CAAe,QAAQ,SAAS,iBAAA,UAAY,aAAa;CAC3D,QAAQ;EACN;CACF;CACA,OAAA,GAAA,iBAAA,GAAA,CAAS,QAAQ,EAAE,OAAO,KAAK,CAAC;AAClC;AAEA,eAAsB,iBAAiB,MAAmD;CACxF,MAAM,uBAAuB,IAAI,CAAC,CAAC,YAAY,KAAA,CAAS;CACxD,MAAM,UAAU,MAAM,eAAmC,gBAAgB,IAAI,CAAC;CAC9E,IAAI,SAAS,OAAO;CAGpB,OAAO,MAAM,eAAmC,sBAAsB,IAAI,CAAC;AAC7E;AAEA,eAAsB,iBAAiB,UAA8B,MAA4C;CAC/G,MAAM,SAAS,kBAAkB,MAAM,iBAAiB,IAAI,GAAG,QAAQ;CACvE,MAAM,wBAAwB,gBAAgB,IAAI,GAAG,QAAQ,eAAe;CAC5E,OAAO;AACT;AAEA,eAAsB,mBAAmB,MAA8B;CACrE,OAAA,GAAA,iBAAA,GAAA,CAAS,gBAAgB,IAAI,GAAG,EAAE,OAAO,KAAK,CAAC;AACjD;;;AC3DA,IAAM,0BAA0B;AAChC,IAAM,oBAAoB,KAAK;AAI/B,IAAM,0BAA0B,QAAwB;CACtD,IAAI,MAAM,IAAI;CACd,OAAO,MAAM,KAAK,IAAI,MAAM,OAAO,KAAK,OAAO;CAC/C,OAAO,IAAI,MAAM,GAAG,GAAG;AACzB;;;AAIA,IAAa,iBAAiB,WAA+B,QAAQ,IAAI,4BACvE,uBAAuB,YAAY,uBAAuB;AAO5D,IAAM,qCAAqB,IAAI,IAAI;CAAC;CAAa;CAAa;CAAO;AAAO,CAAC;;;;;AAM7E,IAAM,yBAAyB,QAAsB;CACnD,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,IAAI,GAAG;CACtB,QAAQ;EACN,MAAM,IAAI,MAAM,uCAAuC,KAAK;CAC9D;CACA,IAAI,OAAO,aAAa,UAAU;CAClC,IAAI,OAAO,aAAa,WAAW,mBAAmB,IAAI,OAAO,QAAQ,GAAG;CAC5E,MAAM,IAAI,MAAM,8DAA8D,OAAO,SAAS,IAAI,OAAO,KAAK,EAAE;AAClH;AAEA,IAAM,cAAc,OAAO,KAAa,OAA2C,CAAC,MAAwB;CAC1G,sBAAsB,GAAG;CACzB,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,oBAAA,iBAAiB,KAAK;GACrC,GAAG;GACH,WAAW;GACX,SAAS,EAAE,gBAAgB,mBAAmB;EAChD,CAAC;CACH,SAAS,KAAK;EACZ,MAAM,IAAI,MAAM,gFAAgF,aAAA,aAAa,GAAG,EAAE,EAAE;CACtH;CACA,IAAI,CAAC,SAAS,IAGZ,MAAM,IAAI,MAAM,wCAAwC,SAAS,QAAQ;CAE3E,IAAI;EACF,OAAO,MAAM,SAAS,KAAK;CAC7B,QAAQ;EAGN,MAAM,IAAI,MAAM,sDAAsD;CACxE;AACF;AAEA,IAAM,sBAAsB;;;;;AAM5B,IAAM,0BAA0B,YAA0B;CACxD,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,IAAI,OAAO;CAC1B,QAAQ;EACN,MAAM,IAAI,MAAM,+DAA+D;CACjF;CACA,IAAI,OAAO,aAAa,YAAY,OAAO,aAAa,qBACtD,MAAM,IAAI,MAAM,2FAA2F,OAAO,SAAS,IAAI,OAAO,KAAK,EAAE;AAEjJ;AAEA,eAAsB,YAAY,MAAc,eAAuB,UAAU,cAAc,GAAiC;CAE9H,MAAM,UAAU,MAAM,YAAY,GAAG,QAAQ,oBAAoB,IAD9C,gBAAgB;EAAE,MAAM,OAAO,IAAI;EAAG,gBAAgB;CAAc,CACtB,CAAA,CAAO,SAAS,GAAG;CACpF,MAAM,SAAS,SAAS,OAAO,IAAI,UAAU,CAAC;CAC9C,IAAI,OAAO,OAAO,aAAa,YAAY,OAAO,OAAO,UAAU,UACjE,MAAM,IAAI,MAAM,mFAAmF;CAErG,uBAAuB,OAAO,QAAQ;CACtC,OAAO;EAAE,SAAS,OAAO;EAAU,OAAO,OAAO;CAAM;AACzD;AAEA,IAAM,iBAAiB,SAAkB,yBAA+C;CACtF,MAAM,SAAS,SAAS,OAAO,IAAI,UAAU,CAAC;CAC9C,IAAI,OAAO,OAAO,iBAAiB,UACjC,MAAM,IAAI,MAAM,iDAAiD;CAInE,MAAM,eAAe,OAAO,OAAO,kBAAkB,WAAW,OAAO,gBAAgB;CACvF,OAAO;EACL,cAAc,OAAO;EACrB,GAAI,eAAe,EAAE,eAAe,aAAa,IAAI,CAAC;EACtD,GAAI,OAAO,OAAO,gBAAgB,WAAW,EAAE,aAAa,OAAO,YAAY,IAAI,CAAC;CACtF;AACF;AAEA,eAAsB,eAAe,OAA8D,UAAU,cAAc,GAAyB;CAKlJ,MAAM,cAAc,cAAc,MAJZ,YAAY,GAAG,QAAQ,uBAAuB;EAClE,QAAQ;EACR,MAAM,KAAK,UAAU;GAAE,MAAM,MAAM;GAAM,OAAO,MAAM;GAAO,eAAe,MAAM;EAAa,CAAC;CAClG,CAAC,CACwC;CACzC,IAAI,CAAC,YAAY,eACf,MAAM,IAAI,MAAM,qHAAqH;CAEvI,OAAO;AACT;AAEA,eAAsB,cAAc,cAAsB,UAAU,cAAc,GAAyB;CAKzG,OAAO,cAAc,MAJC,YAAY,GAAG,QAAQ,sBAAsB;EACjE,QAAQ;EACR,MAAM,KAAK,UAAU,EAAE,eAAe,aAAa,CAAC;CACtD,CAAC,GAC6B,YAAY;AAC5C;;;ACtHA,IAAa,wBAAwB;;;;;;;AAOrC,IAAa,4BAA4B;AACzC,IAAa,qBAAqB;AAClC,IAAa,0BAA0B;;;AAGvC,IAAa,gBAAgB;CAAC;CAAuB;CAA2B;CAAoB;AAAuB;AAC3H,IAAM,gBAAgB;AACtB,IAAM,kBAAkB,IAAI;AAC5B,IAAM,cAAc;;;AAGpB,IAAM,mBAAmB;AAYzB,IAAM,gBAAgB,QAA+B,gBACnD,IAAI,oBAAA,aAAa;CAAE,UAAU,OAAO;CAAW,cAAc,OAAO;CAAe;AAAY,CAAC;AAElG,IAAM,wBAAwB,QAAsB,SAAwB;CAC1E,OAAO,GAAG,WAAW,WAAW;EAC9B,iBAAiB,QAAQ,IAAI,CAAC,CAAC,OAAO,QAAiB;GACrD,IAAI,MAAM,UAAU,oCAAoC,EAAE,OAAO,OAAO,GAAG,EAAE,CAAC;EAChF,CAAC;CACH,CAAC;AACH;AAEA,IAAM,wBAAwB;AAE9B,IAAM,mBAAmB,OAAO,OAAoB,SAAmC;CACrF,MAAM,WAAW,MAAM,qBAAqB,IAAI;CAIhD,IAAI,aAAa,aAAa,MAAM,iBAAiB,IAAI;CAKzD,IAAI,aAAa,WACf,MAAM,IAAI,MACR,2KACF;CAEF,MAAM,SAAS,aAAa,MAAM,iBAAiB,IAAI,CAAC;CACxD,OAAO,eAAe,KAAK;CAC3B,qBAAqB,QAAQ,IAAI;CACjC,MAAM,EAAE,UAAU,MAAM,OAAO,eAAe;CAC9C,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,qBAAqB;CACjD,OAAO;AACT;AAKA,IAAM,oBAAoB,OAAO,OAAoB,SAAmC;CACtF,IAAI,OAAO,MAAM,gBAAgB,YAAY,MAAM,gBAAgB,MAAM,cAAc,mBAAmB,KAAK,IAAI,GACjH,OAAO,MAAM;CAEf,MAAM,YAAY,MAAM,cAAc,MAAM,iBAAiB,EAAE;CAC/D,IAAI,CAAC,UAAU,cAAc,MAAM,IAAI,MAAM,qBAAqB;CAClE,MAAM,iBAAiB,WAAW,IAAI;CACtC,OAAO,UAAU;AACnB;AAEA,eAAsB,qBAAqB,MAAgC;CACzE,MAAM,QAAQ,MAAM,iBAAiB,IAAI;CACzC,IAAI,CAAC,OAAO,eAGV,MAAM,IAAI,MAAM,uHAAuH;CAIzI,OAAO,MAAM,cAAc,WAAW,MAAM,kBAAkB,OAAO,IAAI,IAAI,MAAM,iBAAiB,OAAO,IAAI;AACjH;AAEA,IAAM,aAAa;;;;;AASnB,eAAsB,aAAa,MAAe,cAA2B,oBAAA,kBAAiC;CAC5G,MAAM,QAAQ,MAAM,iBAAiB,IAAI;CACzC,MAAM,QAAQ,OAAO,iBAAiB,OAAO;CAC7C,IAAI,OACF,IAAI;EACF,MAAM,WAAW,MAAM,YAAY,YAAY;GAC7C,QAAQ;GACR,SAAS,EAAE,gBAAgB,oCAAoC;GAC/D,MAAM,IAAI,gBAAgB,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS;EAChD,CAAC;EACD,IAAI,CAAC,SAAS,IAAI,IAAI,KAAK,UAAU,gCAAgC,EAAE,QAAQ,SAAS,OAAO,CAAC;CAClG,SAAS,KAAK;EACZ,IAAI,KAAK,UAAU,qDAAqD,EAAE,OAAO,aAAA,aAAa,GAAG,EAAE,CAAC;CACtG;CAEF,MAAM,mBAAmB,IAAI;AAC/B;AAEA,IAAM,4BACJ,IAAI,SAAS,SAAS,WAAW;CAC/B,MAAM,SAAS,UAAA,QAAK,aAAa;CACjC,OAAO,KAAK,SAAS,MAAM;CAC3B,OAAO,OAAO,GAAG,mBAAmB;EAClC,MAAM,UAAU,OAAO,QAAQ;EAC/B,IAAI,YAAY,QAAQ,OAAO,YAAY,UAAU;GACnD,uBAAO,IAAI,MAAM,6BAA6B,CAAC;GAC/C;EACF;EACA,QAAQ;GAAE;GAAQ,MAAM,QAAQ;EAAK,CAAC;CACxC,CAAC;AACH,CAAC;AAKH,IAAM,wBAAwB,KAAU,kBAAkC;CACxE,IAAI,IAAI,aAAa,IAAI,OAAO,MAAM,eAAe,MAAM,IAAI,MAAM,gDAAgD;CACrH,MAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;CAC1C,IAAI,OAAO,MAAM,IAAI,MAAM,gCAAgC,OAAO;CAClE,MAAM,OAAO,IAAI,aAAa,IAAI,MAAM;CACxC,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,wCAAwC;CACnE,OAAO;AACT;AAEA,IAAM,eAAe,KAA0B,QAAgB,YAA0B;CACvF,IAAI,UAAU,QAAQ,EAAE,gBAAgB,2BAA2B,CAAC;CACpE,IAAI,IAAI,mBAAmB,QAAQ,oBAAoB;AACzD;;;;AAKA,IAAa,wBAAwB;AAUrC,IAAM,yBAAyB,KAA2B,KAA0B,eAAuB,WAAiC;CAC1I,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,kBAAkB;CACtD,IAAI,IAAI,aAAa,eAAe;EAClC,IAAI,UAAU,GAAG;EACjB,IAAI,IAAI;EACR;CACF;CACA,IAAI,IAAI,aAAa,IAAI,OAAO,MAAM,eAAe;EACnD,YAAY,KAAK,KAAK,yDAAyD;EAC/E;CACF;CACA,IAAI;EACF,MAAM,OAAO,qBAAqB,KAAK,aAAa;EACpD,YAAY,KAAK,KAAK,kDAAkD;EACxE,OAAO,QAAQ,IAAI;CACrB,SAAS,KAAK;EAGZ,YAAY,KAAK,KAAK,8EAA8E;EACpG,OAAO,OAAO,GAAG;CACnB;AACF;AAIA,IAAa,mBAAmB,QAAqB,eAAuB,WAAmB,WAC7F,IAAI,SAAiB,SAAS,WAAW;CACvC,MAAM,QAAQ,iBAAiB,uBAAO,IAAI,MAAM,iCAAiC,YAAY,cAAc,EAAE,CAAC,GAAG,SAAS;CAC1H,MAAM,SAAyB;EAC7B,UAAU,SAAS;GACjB,aAAa,KAAK;GAClB,QAAQ,IAAI;EACd;EACA,SAAS,QAAQ;GACf,aAAa,KAAK;GAClB,OAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;EAC5D;CACF;CACA,IAAI,QAAQ;EACV,IAAI,OAAO,SAAS;GAClB,OAAO,uBAAO,IAAI,MAAM,qBAAqB,CAAC;GAC9C;EACF;EACA,OAAO,iBAAiB,eAAe,OAAO,uBAAO,IAAI,MAAM,qBAAqB,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC;CACxG;CACA,OAAO,GAAG,YAAY,KAAK,QAAQ,sBAAsB,KAAK,KAAK,eAAe,MAAM,CAAC;AAC3F,CAAC;AAIH,IAAM,mBAAmB,QAAsB,eAAuB,UACpE,OAAO,gBAAgB;CACrB,aAAa;CACb,QAAQ;CACR,OAAO;CACP,uBAAuB,oBAAA,oBAAoB;CAC3C,gBAAgB;CAChB;AACF,CAAC;AAIH,IAAM,eAAe,YAAsE;CACzF,MAAM,EAAE,cAAc,kBAAkB,MAAM,IAAI,oBAAA,aAAa,CAAC,CAAC,0BAA0B;CAC3F,IAAI,CAAC,eAAe,MAAM,IAAI,MAAM,wCAAwC;CAC5E,OAAO;EAAE;EAAc;CAAc;AACvC;AAEA,IAAM,2BAA2B,OAC/B,QACA,QACA,MACA,SACyB;CACzB,MAAM,SAAS,aAAa,QAAQ,oBAAoB,OAAO,eAAe;CAC9E,MAAM,EAAE,cAAc,kBAAkB,MAAM,OAAO,0BAA0B;CAC/E,IAAI,CAAC,eAAe,MAAM,IAAI,MAAM,wCAAwC;CAC5E,MAAM,SAAA,GAAA,YAAA,YAAA,CAAoB,WAAW,CAAC,CAAC,SAAS,KAAK;CACrD,KAAK,YAAY,gBAAgB,QAAQ,eAAe,KAAK,CAAC;CAC9D,MAAM,OAAO,MAAM,gBAAgB,QAAQ,OAAO,KAAK,aAAa,iBAAiB,KAAK,MAAM;CAChG,MAAM,EAAE,WAAW,MAAM,OAAO,SAAS;EAAE;EAAM;CAAa,CAAC;CAC/D,IAAI,CAAC,OAAO,eACV,MAAM,IAAI,MAAM,qHAAqH;CAEvI,OAAO;AACT;AAKA,IAAM,sBAAsB,OAAO,QAAqB,MAAc,SAAuD;CAC3H,MAAM,EAAE,cAAc,kBAAkB,MAAM,aAAa;CAC3D,MAAM,EAAE,SAAS,UAAU,MAAM,YAAY,MAAM,aAAa;CAChE,KAAK,YAAY,OAAO;CAExB,OAAO,MAAM,eAAe;EAAE,MAAA,MADX,gBAAgB,QAAQ,OAAO,KAAK,aAAa,iBAAiB,KAAK,MAAM;EAC5D;EAAO;CAAa,CAAC;AAC3D;AAOA,IAAa,qBAAqB,OAAO,QAAqB,WAAsB,SAAuD;CACzI,IAAI,KAAK,QAAQ,SAAS,MAAM,IAAI,MAAM,qBAAqB;CAC/D,OAAO,MAAM,iBAAiB;EAAE,GAAG;EAAQ;CAAU,GAAG,KAAK,IAAI;AACnE;AAEA,eAAsB,gBAAgB,OAA+B,CAAC,GAAyB;CAK7F,MAAM,WAAW,MAAM,qBAAqB,KAAK,IAAI;CACrD,IAAI,aAAa,aAGf,MAAM,iBAAiB,KAAK,IAAI;CAElC,MAAM,iBAAiB,aAAa;CACpC,MAAM,EAAE,QAAQ,SAAS,MAAM,oBAAoB;CACnD,IAAI;EACF,MAAM,YAAuB,iBAAiB,UAAU;EAIxD,OAAO,MAAM,mBAHE,iBACX,MAAM,yBAAyB,MAAM,iBAAiB,KAAK,IAAI,GAAG,QAAQ,MAAM,IAAI,IACpF,MAAM,oBAAoB,QAAQ,MAAM,IAAI,GACR,WAAW,IAAI;CACzD,UAAU;EACR,OAAO,MAAM;CACf;AACF;;;ACrSA,IAAa,wBAAwB,cAAsD;CACzF,IAAI,cAAc;CAClB,IAAI,YAA2B;CAC/B,IAAI,SAAiC;CAErC,MAAM,eAA6C;EACjD,MAAM,aAAa,IAAI,gBAAgB;EACvC,SAAS;EACT,cAAc;EACd,OAAO,IAAI,SAAS,SAAS,WAAW;GACtC,UAAU;IAAE,YAAY,QAAQ,QAAQ,EAAE,SAAS,IAAI,CAAC;IAAG,QAAQ,WAAW;GAAO,CAAC,CAAC,CACpF,WAAW,IAAI,KAAK,UAAU,0BAA0B,CAAC,CAAC,CAC1D,OAAO,QAAiB;IAGvB,IAAI,CAAC,WAAW,OAAO,SAAS;KAC9B,YAAY,aAAA,aAAa,GAAG;KAC5B,IAAI,KAAK,UAAU,yBAAyB,EAAE,OAAO,UAAU,CAAC;IAClE;IACA,OAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;GAC5D,CAAC,CAAC,CACD,cAAc;IAGb,IAAI,WAAW,YAAY;KACzB,cAAc;KACd,SAAS;IACX;GACF,CAAC;EACL,CAAC;CACH;CAEA,MAAM,eAAqB;EACzB,QAAQ,MAAM;CAChB;CAEA,MAAM,cAA4C;EAChD,OAAO;EACP,YAAY;EACZ,OAAO,OAAO;CAChB;CAEA,MAAM,gBAAsC;EAAE,SAAS;EAAa;CAAU;CAE9E,OAAO;EAAE;EAAO;EAAQ;CAAO;AACjC;AAEA,IAAa,iBAAiB,qBAAqB,eAAe;;;AC9DlE,IAAa,wBAAwB,KAAK;AAC1C,IAAM,uBAAuB;AAC7B,IAAM,iBAAiB;AACvB,IAAa,2BAA2B;AACxC,IAAa,mBAAmB;;;AAIhC,IAAa,iBAAb,cAAoC,MAAM;CACxC;CACA,YAAY,QAAgB,SAAiB;EAC3C,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,SAAS;CAChB;AACF;AAEA,IAAa,oBAAoB,UAA4C,iBAAiB;;;AAI9F,IAAa,kBAAkB,UAAkB,QAAgB,SAAiC;CAGhG,OAAO,IAAI,eAAe,QAAQ,GAAG,SAAS,SAAS,SAF1C,WAAW,iBAAiB,YAAY,SAAS,oCAAoC,KACnF,OAAO,MAAM,aAAA,SAAS,MAAM,oBAAoB,MAAM,IACU;AACjF;AAWA,eAAsB,cAAc,UAAkB,aAAqB,KAAa,OAA0B,CAAC,GAAqB;CACtI,MAAM,EAAE,cAAc,oBAAoB,aAAa,OAAO,GAAG,SAAS;CAC1E,MAAM,WAAW,MAAM,oBAAA,iBAAiB,KAAK;EAC3C,GAAG;EACH,WAAW;EACX,SAAS;GAAE,eAAe,UAAU;GAAe,gBAAgB;EAAY;CACjF,CAAC;CACD,IAAI,CAAC,SAAS,IAAI;EAChB,MAAM,OAAO,MAAM,SAAS,KAAK,CAAC,CAAC,OAAO,QAAiB,aAAA,aAAa,GAAG,CAAC;EAC5E,MAAM,eAAe,UAAU,SAAS,QAAQ,IAAI;CACtD;CACA,IAAI,YAAY,OAAO,MAAM,SAAS,KAAK;CAE3C,IAAI,SAAS,WAAW,KAAK,OAAO,CAAC;CACrC,OAAO,MAAM,SAAS,KAAK;AAC7B;AAEA,IAAa,eAAe,QAAiC,QAAyB,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO;AAEtI,IAAa,YAAY,UAA6C,SAAS,KAAK,IAAI,QAAQ,CAAC;AAEjG,IAAa,WAAW,UAA8B;CACpD,MAAM,SAAS,SAAS,KAAK;CAC7B,OAAO,MAAM,QAAQ,OAAO,KAAK,IAAI,OAAO,QAAQ,CAAC;AACvD;;;AC7DA,IAAM,oBAAoB;AAC1B,IAAM,qBAAqB;AAC3B,IAAM,sBAAsB;AAI5B,IAAM,0BAA0B;AAChC,IAAM,0BAA0B;AAGhC,IAAM,uBAAuB;AAC7B,IAAM,uBAAuB;AAE7B,IAAM,YAAY;;;;;;AAOlB,IAAa,uBAAuB,eAA2C,cAAc;AAE7F,IAAM,aAAa,eAA2C,GAAG,kBAAkB,aAAa,mBAAmB,oBAAoB,UAAU,CAAC,EAAE;AAyEpJ,IAAM,aAAa,UAA2B;CAC5C,IAAI,CAAC,SAAS,KAAK,GAAG,OAAO;CAC7B,IAAI,OAAO,MAAM,aAAa,UAAU,OAAO,MAAM;CACrD,IAAI,OAAO,MAAM,SAAS,UAAU,OAAO,MAAM;CACjD,OAAO;AACT;AAEA,IAAa,kBAAkB,UAAyC;CACtE,MAAM,SAAS,SAAS,KAAK;CAC7B,OAAO;EACL,IAAI,YAAY,QAAQ,IAAI;EAC5B,SAAS,YAAY,QAAQ,SAAS;EACtC,OAAO,UAAU,OAAO,KAAK;EAC7B,KAAK,UAAU,OAAO,GAAG;EACzB,UAAU,YAAY,QAAQ,UAAU;EACxC,QAAQ,YAAY,QAAQ,QAAQ;EACpC,SAAS,YAAY,QAAQ,SAAS;CACxC;AACF;AAEA,IAAa,qBAAqB,UAAoC;CACpE,MAAM,SAAS,SAAS,KAAK;CAC7B,OAAO;EACL,IAAI,YAAY,QAAQ,IAAI;EAC5B,SAAS,YAAY,QAAQ,SAAS;EACtC,aAAa,YAAY,QAAQ,aAAa;EAC9C,SAAS,OAAO,YAAY;EAC5B,YAAY,YAAY,QAAQ,YAAY;EAC5C,iBAAiB,YAAY,QAAQ,iBAAiB;EACtD,iBAAiB,YAAY,QAAQ,iBAAiB;EACtD,SAAS,YAAY,QAAQ,SAAS;CACxC;AACF;AAEA,IAAM,cAAc,UAAuD;CACzE,MAAM,UAAU,OAAO,QAAQ,SAAS,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,WAAyC;EACtG,MAAM,SAAS,SAAS,KAAK;EAC7B,OAAO,CAAC,SAAS;GAAE,YAAY,YAAY,QAAQ,YAAY;GAAG,YAAY,YAAY,QAAQ,YAAY;EAAE,CAAC;CACnH,CAAC;CACD,OAAO,OAAO,YAAY,OAAO;AACnC;;;AAIA,IAAa,oBAAoB,QAAgB,SAAwB,eAAe,oBAAoB,QAAQ,IAAI;AAExH,eAAsB,oBAAoB,aAAqB,OAA0D;CACvH,MAAM,OAAO;EACX,SAAS,MAAM;EACf,aAAa,MAAM;EACnB,OAAO,EAAE,UAAU,MAAM,cAAc;EACvC,KAAK,EAAE,UAAU,MAAM,YAAY;EACnC,GAAI,MAAM,UAAU,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;CACpD;CAEA,OAAO,eAAe,MADA,cAAc,oBAAoB,aAAa,UAAU,MAAM,UAAU,GAAG;EAAE,QAAQ;EAAQ,MAAM,KAAK,UAAU,IAAI;CAAE,CAAC,CACnH;AAC/B;AAEA,IAAM,YAAY,YAAgC,YAA4B,GAAG,UAAU,UAAU,EAAE,GAAG,mBAAmB,OAAO;;;;;;AAOpI,IAAa,mBAAmB,WAA8D;CAC5F,GAAI,MAAM,YAAY,KAAA,IAAY,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;CAChE,GAAI,MAAM,gBAAgB,KAAA,IAAY,EAAE,aAAa,MAAM,YAAY,IAAI,CAAC;CAC5E,GAAI,MAAM,kBAAkB,KAAA,IAAY,EAAE,OAAO,EAAE,UAAU,MAAM,cAAc,EAAE,IAAI,CAAC;CACxF,GAAI,MAAM,gBAAgB,KAAA,IAAY,EAAE,KAAK,EAAE,UAAU,MAAM,YAAY,EAAE,IAAI,CAAC;CAClF,GAAI,MAAM,UAAU,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AACpD;;;;AAKA,eAAsB,oBAAoB,aAAqB,OAAgE;CAG7H,OAAO,eAAe,MADA,cAAc,oBAAoB,aAD5C,SAAS,MAAM,YAAY,MAAM,OACwB,GAAK;EAAE,QAAQ;EAAS,MAAM,KAAK,UAAU,gBAAgB,KAAK,CAAC;CAAE,CAAC,CAC9G;AAC/B;;;;AAKA,eAAsB,oBAAoB,aAAqB,OAAgD;CAC7G,MAAM,cAAc,oBAAoB,aAAa,SAAS,MAAM,YAAY,MAAM,OAAO,GAAG,EAAE,QAAQ,SAAS,CAAC;AACtH;AAEA,eAAsB,mBAAmB,aAAqB,QAAyB,CAAC,GAAoC;CAC1H,MAAM,SAAS,IAAI,gBAAgB;EACjC,SAAS,MAAM,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EACjD,YAAY,OAAO,MAAM,cAAA,EAAsC;EAC/D,cAAc;EACd,SAAS;CACX,CAAC;CAED,OAAO,QAAQ,MADM,cAAc,oBAAoB,aAAa,GAAG,UAAU,MAAM,UAAU,EAAE,GAAG,OAAO,SAAS,GAAG,CACpG,CAAC,CAAC,IAAI,cAAc;AAC3C;AAwBA,IAAM,OAAO,OAAO,oBAAoB;AAExC,eAAe,cAAc,aAAqB,YAAgC,QAA2C;CAC3H,IAAI;EACF,OAAO,MAAM,cAAc,oBAAoB,aAAa,GAAG,UAAU,UAAU,EAAE,GAAG,OAAO,SAAS,GAAG;CAC7G,SAAS,KAAc;EACrB,IAAI,iBAAiB,GAAG,KAAK,IAAI,WAAW,WAAW,OAAO;EAC9D,MAAM;CACR;AACF;;;;;;;;;;;AAYA,eAAsB,mBAAmB,aAAqB,QAAyB,CAAC,GAAgC;CACtH,MAAM,SAAiC,CAAC;CACxC,IAAI;CACJ,IAAI;CAEJ,KAAK,IAAI,OAAO,GAAG,OAAO,sBAAsB,QAAQ,GAAG;EACzD,MAAM,SAAS,IAAI,gBAAgB;GACjC,cAAc;GACd,aAAa;GACb,YAAY,OAAO,MAAM,cAAc,oBAAoB;EAC7D,CAAC;EACD,IAAI,MAAM,WAAW,OAAO,IAAI,aAAa,MAAM,SAAS;EAC5D,IAAI,WAAW,OAAO,IAAI,aAAa,SAAS;EAEhD,MAAM,UAAU,MAAM,cAAc,aAAa,MAAM,YAAY,MAAM;EACzE,IAAI,YAAY,MAAM,OAAO;GAAE,QAAQ,CAAC;GAAG,oBAAoB;EAAK;EAEpE,MAAM,SAAS,SAAS,OAAO;EAC/B,OAAO,KAAK,GAAG,QAAQ,OAAO,CAAC,CAAC,IAAI,cAAc,CAAC;EACnD,gBAAgB,YAAY,QAAQ,eAAe,KAAK,KAAA;EACxD,YAAY,YAAY,QAAQ,eAAe,KAAK,KAAA;EACpD,IAAI,CAAC,WAAW;CAClB;CAEA,OAAO;EAAE;EAAQ;EAAe,oBAAoB;CAAM;AAC5D;;;AASA,eAAsB,qBACpB,WACA,WAAW,yBACiB;CAC5B,MAAM,YAA+B,CAAC;CACtC,IAAI;CACJ,KAAK,IAAI,OAAO,GAAG,OAAO,UAAU,QAAQ,GAAG;EAC7C,MAAM,EAAE,OAAO,kBAAkB,MAAM,UAAU,SAAS;EAC1D,UAAU,KAAK,GAAG,MAAM,IAAI,iBAAiB,CAAC;EAC9C,IAAI,CAAC,eAAe;EACpB,YAAY;CACd;CACA,OAAO;AACT;;;;AAKA,eAAsB,cAAc,aAAiD;CACnF,OAAO,qBAAqB,OAAO,cAAc;EAC/C,MAAM,SAAS,IAAI,gBAAgB,EAAE,YAAY,OAAO,uBAAuB,EAAE,CAAC;EAClF,IAAI,WAAW,OAAO,IAAI,aAAa,SAAS;EAChD,MAAM,UAAU,MAAM,cAAc,oBAAoB,aAAa,GAAG,kBAAkB,yBAAyB,OAAO,SAAS,GAAG;EACtI,MAAM,SAAS,SAAS,OAAO;EAC/B,OAAO;GAAE,OAAO,QAAQ,OAAO;GAAG,eAAe,OAAO,OAAO,kBAAkB,WAAW,OAAO,gBAAgB,KAAA;EAAU;CAC/H,CAAC;AACH;;AAGA,eAAsB,kBAAkB,aAA8C;CAEpF,MAAM,SAAS,SAAS,MADF,cAAc,oBAAoB,aAAa,GAAG,kBAAkB,QAAQ,CACnE;CAC/B,OAAO;EAAE,OAAO,WAAW,OAAO,KAAK;EAAG,UAAU,WAAW,OAAO,QAAQ;CAAE;AAClF;;;AC3SA,IAAM,kBAAkB;;AAGxB,SAAgB,sBAAsB,eAAgC;CACpE,OAAO,UAAA,QAAK,KAAK,iBAAiB,kBAAA,iBAAiB,GAAG,QAAQ,YAAY,kBAAkB;AAC9F;AAUA,IAAM,cAAc;AAEpB,eAAe,UAAU,eAAoD;CAC3E,MAAM,SAAS,MAAM,eAAkC,sBAAsB,aAAa,CAAC;CAC3F,OAAO,QAAQ,SAAS,SAAS,EAAE,QAAQ,CAAC,EAAE;AAChD;AASA,IAAM,aAAyC,EAAE,MAAM,QAAQ,QAAQ,EAAE;AAEzE,eAAe,YAAY,QAAoE,eAAuC;CACpI,MAAM,MAAM,WAAW,KAAK,KAAK,YAAY;EAC3C,MAAM,QAAQ,MAAM,UAAU,aAAa;EAC3C,MAAM,wBAAwB,sBAAsB,aAAa,GAAG,EAAE,QAAQ,OAAO,MAAM,MAAM,EAAE,GAAG,eAAe;CACvH,CAAC;CAED,WAAW,OAAO,IAAI,YAAY,KAAA,CAAS;CAC3C,OAAO,MAAM;AACf;AAEA,eAAsB,sBAAsB,YAAqB,eAAgD;CAE/G,QAAO,MADa,UAAU,aAAa,EAAA,CAC9B,OAAO,YAAY,UAAU,MAAM;AAClD;AAEA,eAAsB,sBAAsB,YAAgC,WAAmB,eAAuC;CACpI,MAAM,SAAS,YAAY,UAAU;CACrC,MAAM,aAAa,YAAY;EAAE,GAAG;GAAS,SAAS;CAAU,IAAI,aAAa;AACnF;;;AAIA,eAAsB,uBAAuB,YAAqB,eAAuC;CACvG,MAAM,UAAU,YAAY,UAAU;CACtC,MAAM,aAAa,WAAW,OAAO,YAAY,OAAO,QAAQ,MAAM,CAAC,CAAC,QAAQ,CAAC,SAAS,QAAQ,OAAO,CAAC,GAAG,aAAa;AAC5H;;;;AClDA,IAAM,gBAAgB;AACtB,IAAM,eAAe;;AAErB,IAAM,iBAAiB;;AAEvB,IAAM,wBAAwB;;;;;AAM9B,SAAgB,qBAAqB,OAAyB;CAC5D,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,MAAM,UAAU,MAAM,KAAK;CAC3B,IAAI,aAAa,KAAK,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG;CACrD,IAAI,CAAC,QAAQ,SAAS,GAAG,GAAG,OAAO;CACnC,OAAO,QAAQ,QAAQ,gBAAgB,EAAE,CAAC,CAAC,QAAQ,uBAAuB,EAAE;AAC9E;;;ACRA,IAAa,+BAA+B;AAC5C,IAAM,2BAA2B,OAAU;AAC3C,IAAM,mBAAmB;AAiBzB,IAAM,sBAAsB;;;;AAK5B,SAAS,aAAa,QAA6C,OAAgD;CACjH,OAAO,OAAO,OAAO,QAAQ,KAAK,IAAI,OAAO,SAAS,KAAA;AACxD;;;;;AAMA,SAAS,aAAa,QAA6C,OAAe,OAAwB;CACxG,OAAO,aAAa,QAAQ,KAAK,CAAC,EAAE,SAAS,sBAAsB,qBAAqB,KAAK,IAAI;AACnG;;;;AAKA,SAAgB,mBACd,OACA,KACA,YACA,QACgB;CAChB,MAAM,SAAS,OAAO,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,OAAO,aAAa,QAAQ,OAAO,MAAM,OAAO,CAAC,CAAC;CAC/G,OAAO;EAAE,GAAG,OAAO,YAAY,MAAM;GAAI,aAAa,MAAM;CAAG;AACjE;AAiBA,SAAgB,cAAc,SAAiB,MAA6C;CAC1F,IAAI,SAAS,MAAM,OAAO,EAAE,MAAM,UAAU;CAC5C,IAAI,SAAS,cAAc,OAAO;EAAE,MAAM;EAAc,SAAS,SAAS,QAAQ;CAAc;CAChG,OAAO;EAAE,MAAM;EAAS,SAAS,SAAS,QAAQ,IAAI;CAAO;AAC/D;AAEA,SAAgB,eAAe,SAAiB,MAA8C;CAC5F,IAAI,SAAS,MAAM,OAAO,EAAE,MAAM,UAAU;CAE5C,IAAI,SAAS,aAAa,OAAO,EAAE,MAAM,UAAU;CACnD,IAAI,SAAS,cAAc,OAAO;EAAE,MAAM;EAAc,SAAS,UAAU,QAAQ;CAAc;CACjG,OAAO;EAAE,MAAM;EAAS,SAAS,UAAU,QAAQ,IAAI;CAAO;AAChE;AAEA,eAAe,WAAW,YAA8B,OAA6B,eAA8C;CACjI,MAAM,EAAE,WAAW;CACnB,IAAI;EAIF,MAAM,QAAQ,kBAAA,SAAS,YAAY,EAAE,cAAc,CAAC;EACpD,IAAI,CAAC,MAAM,SAAS,CAAC,MAAM,QAAQ,OAAO;GAAE,MAAM;GAAc,SAAS,eAAe,WAAW,KAAK;EAAgB;EACxH,IAAI,MAAM,WAAW,kBAAkB;GACrC,MAAM,UAAU,MAAM,MAAM,OAAO,MAAM,EAAE;GAC3C,OAAO,eAAe,MAAM,IAAI,QAAQ,IAAI;EAC9C;EACA,MAAM,SAAS,mBAAmB,OAAO,OAAO,gBAAgB,OAAO,CAAC,GAAG,OAAO,YAAY,OAAO,MAAM;EAC3G,MAAM,UAAU,MAAM,MAAM,MAAM,MAAM,IAAI,MAAM;EAClD,OAAO,cAAc,MAAM,IAAI,QAAQ,IAAI;CAC7C,SAAS,OAAO;EAId,OAAO;GAAE,MAAM;GAAS,SAAS,SAAS,MAAM,GAAG,IAAI,OAAO,KAAK;EAAI;CACzE;AACF;AAEA,eAAe,gBAAgB,aAAqB,YAAgC,eAAuB;CACzG,MAAM,uBAAuB,YAAY,aAAa;CACtD,OAAO,MAAM,mBAAmB,aAAa,EAAE,WAAW,CAAC;AAC7D;;;;;;;AAQA,eAAsB,cAAiB,OAAsC,KAAa,KAAmC;CAE3H,MAAM,UADW,MAAM,IAAI,GAAG,KAAK,QAAQ,QAAQ,EAAA,CAC3B,KAAK,KAAK,GAAG;CACrC,MAAM,OAAO,OAAO,WACZ,KAAA,SACA,KAAA,CACR;CACA,MAAM,IAAI,KAAK,IAAI;CACnB,IAAI;EACF,OAAO,MAAM;CACf,UAAU;EACR,IAAI,MAAM,IAAI,GAAG,MAAM,MAAM,MAAM,OAAO,GAAG;CAC/C;AACF;;;;AAKA,IAAM,gCAAgB,IAAI,IAA8B;;;;;;;;;;;;;;;;AAiBxD,eAAsB,kBACpB,YACA,aACA,eACyC;CACzC,OAAO,MAAM,cAAc,eAAe,oBAAoB,UAAU,SAAS,qBAAqB,YAAY,aAAa,aAAa,CAAC;AAC/I;AAEA,eAAe,qBACb,YACA,aACA,eACyC;CACzC,MAAM,cAAc,MAAM,qBAAqB;CAE/C,MAAM,QAAQ,MAAM,mBAAmB,aAAa;EAAE;EAAY,WAAW,MADnD,sBAAsB,YAAY,aAAa,KACmB,KAAA;CAAU,CAAC;CACvG,MAAM,SAAS,MAAM,qBAAqB,MAAM,gBAAgB,aAAa,YAAY,aAAa,IAAI;CAE1G,MAAM,UAA0C,CAAC;CACjD,KAAK,MAAM,cAAc,aACvB,QAAQ,KAAK,MAAM,wBAAwB,YAAY,OAAO,QAAQ,aAAa,CAAC;CAMtF,MAAM,aAAa,QAAQ,SAAS,UAAU,MAAM,UAAU;CAC9D,IAAI,WAAW,SAAS,GAGtB,IAAI,KAAK,UAAU,qDAAqD;EAAE;EAAY;CAAW,CAAC;CAEpG,MAAM,SAAS,QAAQ,SAAS,UAAU,MAAM,MAAM;CACtD,IAAI,OAAO,SAAS,GAAG;EACrB,IAAI,KAAK,UAAU,wDAAwD;GAAE;GAAY,QAAQ,OAAO;EAAO,CAAC;EAChH,OAAO;CACT;CACA,IAAI,OAAO,eAAe,MAAM,aAAa,YAAY,OAAO,eAAe,aAAa,aAAa;CACzG,OAAO;AACT;;;;;;;;;;;AAYA,eAAe,aACb,YACA,eACA,aACA,eACe;CACf,IAAI,CAAE,MAAM,4BAA4B,WAAW,GAAI;EACrD,IAAI,KAAK,UAAU,yFAAyF,EAAE,WAAW,CAAC;EAC1H;CACF;CACA,MAAM,sBAAsB,YAAY,eAAe,aAAa;AACtE;AAEA,eAAe,WAAW,SAAmC;CAC3D,IAAI;EACF,OAAA,GAAA,iBAAA,KAAA,CAAW,OAAO;EAClB,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;AAKA,eAAsB,4BACpB,aACA,SAAgD,YAC9B;CAElB,QAAO,MADa,QAAQ,IAAI,YAAY,KAAK,eAAe,OAAO,WAAW,QAAQ,CAAC,CAAC,EAAA,CAC/E,KAAK,OAAO;AAC3B;AAEA,eAAe,wBACb,YACA,QACA,eACuC;CACvC,MAAM,WAA2B,CAAC;CAClC,KAAK,MAAM,SAAS,QAClB,SAAS,KAAK,MAAM,WAAW,YAAY,OAAO,aAAa,CAAC;CAElE,OAAO;EACL,MAAM,WAAW;EACjB,SAAS,SAAS,QAAQ,YAAY,QAAQ,SAAS,SAAS,CAAC,CAAC;EAClE,SAAS,SAAS,QAAQ,YAAY,QAAQ,SAAS,SAAS,CAAC,CAAC;EAClE,YAAY,SAAS,SAAS,YAAa,QAAQ,SAAS,eAAe,CAAC,QAAQ,OAAO,IAAI,CAAC,CAAE;EAClG,QAAQ,SAAS,SAAS,YAAa,QAAQ,SAAS,UAAU,CAAC,QAAQ,OAAO,IAAI,CAAC,CAAE;CAC3F;AACF;;;AAIA,eAAe,iBAAmC;CAChD,OAAO,SAAS,MAAM,iBAAiB,EAAA,EAAI,aAAa;AAC1D;;;;;;;;;AAUA,SAAgB,gBAAgB,aAA2E;CACzG,MAAM,yBAAS,IAAI,IAAgC;CACnD,KAAK,MAAM,cAAc,aAAa;EACpC,MAAM,MAAM,oBAAoB,WAAW,OAAO,gBAAgB,UAAU;EAC5E,OAAO,IAAI,KAAK,CAAC,GAAI,OAAO,IAAI,GAAG,KAAK,CAAC,GAAI,UAAU,CAAC;CAC1D;CACA,OAAO;AACT;;;;;;;;;;;;AAmBA,SAAgB,mBAAmB,SAA4B,WAAwD;CACrH,IAAI,CAAC,QAAQ,gBAAgB,OAAO;CACpC,MAAM,MAAM,oBAAoB,QAAQ,eAAe,UAAU;CAEjE,OADkB,UAAU,MAAM,UAAU,MAAM,kBAAkB,oBAAoB,MAAM,eAAe,UAAU,MAAM,GACtH,IAAY,OAAO;AAC5B;;;;;;;AAQA,eAAsB,6BAA6B,SAA4B,eAA+C;CAC5H,IAAI;EACF,IAAI,CAAC,QAAQ,gBAAgB,OAAO;EAEpC,MAAM,WAAW,mBACf,UACA,MAHsB,kBAAA,oBAAoB,EAAE,cAAc,CAAC,EAAA,CAGjD,KAAK,eAAe,WAAW,MAAM,CACjD;EACA,IAAI,aAAa,MAAM,OAAO;EAC9B,MAAM,uBAAuB,UAAU,aAAa;EACpD,IAAI,KAAK,UAAU,qEAAqE,EAAE,YAAY,SAAS,CAAC;EAChH,OAAO;CACT,SAAS,OAAO;EACd,IAAI,KAAK,UAAU,kEAAkE,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;EAC7G,OAAO;CACT;AACF;;AAGA,eAAe,gBAAgB,eAAiE;CAE9F,OAAO,iBAAgB,MADL,kBAAA,oBAAoB,EAAE,cAAc,CAAC,EAAA,CAC5B,QAAQ,eAAe,WAAW,OAAO,cAAc,CAAC;AACrF;;;;;;;AAQA,eAAe,sBAAsB,QAA2D;CAC9F,IAAI,OAAO,SAAS,GAAG,OAAO;CAC9B,IAAI,MAAM,eAAe,GAAG,OAAO;CACnC,IAAI,KAAK,UAAU,kEAAkE,EAAE,WAAW,OAAO,KAAK,CAAC;CAC/G,OAAO;AACT;;;AAIA,eAAe,kBAAkB,QAAyC,eAAgE;CACxI,MAAM,UAA0C,CAAC;CACjD,KAAK,MAAM,CAAC,YAAY,gBAAgB,QACtC,IAAI;EACF,QAAQ,KAAK,GAAI,MAAM,kBAAkB,YAAY,aAAa,aAAa,CAAE;CACnF,SAAS,OAAO;EACd,IAAI,KAAK,UAAU,wBAAwB;GAAE;GAAY,OAAO,OAAO,KAAK;EAAE,CAAC;EAC/E,QAAQ,KAAK,GAAG,YAAY,KAAK,gBAAgB;GAAE,MAAM,WAAW;GAAM,SAAS;GAAG,SAAS;GAAG,YAAY,CAAC;GAAG,QAAQ,CAAC,OAAO,KAAK,CAAC;EAAE,EAAE,CAAC;CAC/I;CAEF,OAAO;AACT;;AAGA,eAAsB,2BAA2B,eAAgE;CAC/G,MAAM,SAAS,MAAM,gBAAgB,aAAa;CAClD,OAAQ,MAAM,sBAAsB,MAAM,IAAK,MAAM,kBAAkB,QAAQ,aAAa,IAAI,CAAC;AACnG;;;;;;;;AASA,eAAsB,eAAkB,QAAwB,WAAoF;CAClJ,MAAM,UAAU,MAAM,QAAQ,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,OAAO,UAAY,MAAM,UAAU,MAAM,EAAE,MAAO,OAAO,QAAQ,IAAK,CAAC;CACzH,OAAO,IAAI,IAAI,QAAQ,QAAQ,UAAgC,UAAU,IAAI,CAAC;AAChF;;;;AAKA,eAAsB,2BAA2B,eAAgE;CAE/G,MAAM,UAAU,MAAM,eAAe,MADhB,gBAAgB,aAAa,IACJ,eAAe,sBAAsB,YAAY,aAAa,CAAC;CAC7G,IAAI,CAAE,MAAM,sBAAsB,OAAO,GAAI,OAAO,CAAC;CACrD,IAAI,KAAK,UAAU,uDAAuD,EAAE,WAAW,CAAC,GAAG,QAAQ,KAAK,CAAC,EAAE,CAAC;CAC5G,OAAO,MAAM,kBAAkB,SAAS,aAAa;AACvD;AAgBA,IAAM,qBAA6C;CAAE,YAAY;CAAiB,UAAU;CAAgB,WAAW;AAAkB;;;;;;;;;;;AAYzI,eAAsB,0BACpB,MACA,eACA,OAA+B,oBACK;CAEpC,MAAM,SAAS,CAAC,GAAG,MADE,KAAK,WAAW,aAAa,CACzB,CAAC,CAAC,QAAQ,GAAG,iBAAiB,YAAY,MAAM,eAAe,WAAW,SAAS,IAAI,CAAC;CACjH,IAAI,OAAO,WAAW,GAAG,OAAO,EAAE,MAAM,iBAAiB;CACzD,IAAI,CAAE,MAAM,KAAK,SAAS,GAAI,OAAO,EAAE,MAAM,aAAa;CAC1D,OAAO;EAAE,MAAM;EAAU,SAAS,MAAM,KAAK,UAAU,IAAI,IAAI,MAAM,GAAG,aAAa;CAAE;AACzF;;;AAIA,SAAgB,0BAA0B,MAAuE;CAC/G,OAAO;EACL,IAAI;EACJ,MAAM;EACN,aAAa;EACb,UAAU;GAAE,MAAM,0BAAA,eAAe;GAAU,YAAY,MAAM,cAAc;EAAyB;EACpG,iBAAiB,0BAAA,oBAAoB;EACrC,WAAW,2BAA2B,MAAM,iBAAiB,kBAAA,iBAAiB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;CAChG;AACF;;;ACvcA,IAAM,iBAAiB;AACvB,IAAM,kBAAkB;AACxB,IAAM,uBAAuB;AAC7B,IAAM,wBAAwB;AAC9B,IAAM,iBAAiB;AA8CvB,IAAa,qBAAqB,UAAoC;CACpE,MAAM,SAAS,SAAS,KAAK;CAC7B,OAAO;EAAE,IAAI,YAAY,QAAQ,IAAI;EAAG,OAAO,YAAY,QAAQ,OAAO;CAAE;AAC9E;AAEA,IAAa,iBAAiB,UAAgC;CAC5D,MAAM,SAAS,SAAS,KAAK;CAC7B,OAAO;EACL,IAAI,YAAY,QAAQ,IAAI;EAC5B,OAAO,YAAY,QAAQ,OAAO;EAClC,QAAQ,YAAY,QAAQ,QAAQ;EACpC,KAAK,YAAY,QAAQ,KAAK;EAC9B,OAAO,YAAY,QAAQ,OAAO;CACpC;AACF;;;;;AAMA,IAAa,uBAAuB,eAA2C,YAAY,KAAK,KAAK;AAErG,IAAM,YAAY,YAAgC,SAAS,OACzD,GAAG,eAAe,SAAS,mBAAmB,oBAAoB,UAAU,CAAC,EAAE,QAAQ;AAEzF,eAAsB,cAAc,aAAiD;CAEnF,OAAO,QAAQ,MADM,cAAc,iBAAiB,aAAa,GAAG,eAAe,8BAA8B,gBAAgB,CAC5G,CAAC,CAAC,IAAI,iBAAiB;AAC9C;AAEA,eAAsB,UAAU,aAAqB,QAAwB,CAAC,GAA2B;CACvG,MAAM,SAAS,IAAI,gBAAgB;EACjC,YAAY,OAAO,MAAM,cAAA,EAAsC;EAC/D,eAAe,OAAO,MAAM,iBAAiB,KAAK;CACpD,CAAC;CAED,OAAO,QAAQ,MADM,cAAc,iBAAiB,aAAa,GAAG,SAAS,MAAM,UAAU,EAAE,GAAG,OAAO,SAAS,GAAG,CAChG,CAAC,CAAC,IAAI,aAAa;AAC1C;AAEA,eAAsB,WAAW,aAAqB,OAA8C;CAClG,MAAM,OAAO;EAAE,OAAO,MAAM;EAAO,OAAO,MAAM;EAAO,KAAK,MAAM;CAAI;CAEtE,OAAO,cAAc,MADC,cAAc,iBAAiB,aAAa,SAAS,MAAM,UAAU,GAAG;EAAE,QAAQ;EAAQ,MAAM,KAAK,UAAU,IAAI;CAAE,CAAC,CAChH;AAC9B;;;;;AAMA,IAAa,kBAAkB,WAAqD;CAClF,GAAI,MAAM,UAAU,KAAA,IAAY,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;CAC1D,GAAI,MAAM,UAAU,KAAA,IAAY,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;CAC1D,GAAI,MAAM,QAAQ,KAAA,IAAY,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC;AACtD;AAEA,eAAsB,WAAW,aAAqB,OAA8C;CAGlG,OAAO,cAAc,MADC,cAAc,iBAAiB,aADzC,SAAS,MAAM,YAAY,IAAI,mBAAmB,MAAM,MAAM,GACR,GAAK;EAAE,QAAQ;EAAS,MAAM,KAAK,UAAU,eAAe,KAAK,CAAC;CAAE,CAAC,CAC3G;AAC9B;AAEA,eAAsB,aAAa,aAAqB,OAAgD;CAQtG,OAAO,cAAc,MAJC,cAAc,iBAAiB,aADzC,SAAS,MAAM,YAAY,IAAI,mBAAmB,MAAM,MAAM,GACR,GAAK;EACrE,QAAQ;EACR,MAAM,KAAK,UAAU,EAAE,QAAQ,sBAAsB,CAAC;CACxD,CAAC,CAC2B;AAC9B;AAEA,eAAsB,WAAW,aAAqB,OAAyC;CAE7F,MAAM,cAAc,iBAAiB,aADzB,SAAS,MAAM,YAAY,IAAI,mBAAmB,MAAM,MAAM,GACxB,GAAK,EAAE,QAAQ,SAAS,CAAC;AAC7E;;;AC1HA,IAAM,kBAAkB;AACxB,IAAM,mBAAmB;AACzB,IAAM,kBAAkB;AACxB,IAAM,oBAAoB;AAC1B,IAAM,cAAc;AAGpB,IAAM,iBAAiB;AACvB,IAAM,qBAAqB;CAAC;CAAS;CAAoB;CAAmB;AAAwB;AAwBpG,IAAa,sBAAsB,UAAqC;CACtE,MAAM,SAAS,SAAS,KAAK;CAC7B,OAAO;EACL,IAAI,YAAY,QAAQ,IAAI;EAC5B,MAAM,YAAY,QAAQ,MAAM;EAChC,UAAU,YAAY,QAAQ,UAAU;EACxC,aAAa,YAAY,QAAQ,aAAa;EAC9C,cAAc,YAAY,QAAQ,cAAc;CAClD;AACF;AAEA,IAAa,kBAAkB,aAA8B,mBAAmB,MAAM,WAAW,SAAS,WAAW,MAAM,CAAC;AAE5H,eAAsB,eAAe,aAAqB,QAA6B,CAAC,GAAgC;CAOtH,MAAM,SAAS,SAAS,MADH,cAAc,iBAAiB,aAAa,GAAG,gBAAgB,GAAG,IALpE,gBAAgB;EACjC,UAAU,OAAO,MAAM,cAAA,EAAsC;EAC7D,QAAQ,SAAS,YAAY;EAC7B,SAAS;CACX,CACuF,CAAA,CAAO,SAAS,GAAG,CAC5E;CAE9B,QADc,MAAM,QAAQ,OAAO,KAAK,IAAI,OAAO,QAAQ,CAAC,EAAA,CAC/C,IAAI,kBAAkB;AACrC;AAKA,IAAM,iBAAiB;;;;;;AAOvB,IAAM,6BAAqC,gBAAA,GAAA,YAAA,YAAA,CAA2B,cAAc,CAAC,CAAC,SAAS,KAAK;AAKpG,IAAM,eAAe;AAErB,IAAa,sBAAsB,aAA6B;CAC9D,IAAI,CAAC,aAAa,KAAK,QAAQ,GAAG,MAAM,IAAI,MAAM,uCAAuC,SAAS,qDAAqD;CACvJ,OAAO;AACT;AAEA,IAAa,sBAAsB,UAAkC,SAAiB,UAAkB,aACtG;CACE,KAAK;CACL;CACA;CACA,KAAK,UAAU,QAAQ;CACvB,KAAK;CACL,iBAAiB;CACjB;CACA;CACA,KAAK,SAAS;CACd;AACF,CAAC,CAAC,KAAK,MAAM;;AAGf,IAAa,gBAAgB,OAAiB,WAAyB,yBAAiC;CACtG,MAAM,YAAY,cAA+B,MAAM,MAAM,SAAS,KAAK,SAAS,SAAS,CAAC;CAC9F,IAAI,WAAW,SAAS;CACxB,OAAO,SAAS,QAAQ,GAAG,WAAW,SAAS;CAC/C,OAAO;AACT;AAEA,eAAsB,gBAAgB,aAAqB,OAAwD;CACjH,MAAM,WAAW,mBAAmB,MAAM,YAAY,iBAAiB;CACvE,MAAM,WAAW;EAAE,MAAM,MAAM;EAAM;CAAS;CAC9C,MAAM,WAAW,aAAa,CAAC,MAAM,SAAS,KAAK,UAAU,QAAQ,CAAC,CAAC;CAOvE,OAAO,mBAAmB,MALJ,cAAc,iBAAiB,aAAa,GAAG,iBAAiB,GAAG,IADtE,gBAAgB;EAAE,YAAY;EAAa,QAAQ;CAAY,CACO,CAAA,CAAO,SAAS,KAAK;EAC5G,QAAQ;EACR,aAAa,+BAA+B;EAC5C,MAAM,mBAAmB,UAAU,MAAM,SAAS,UAAU,QAAQ;CACtE,CAAC,CACgC;AACnC;AAEA,eAAsB,cAAc,aAAqB,OAAiF;CACxI,MAAM,SAAS,mBAAmB,MAAM,MAAM;CAE9C,MAAM,OAAO,mBAAmB,MADT,cAAc,iBAAiB,aAAa,GAAG,gBAAgB,GAAG,OAAO,UAAU,aAAa,CAC/E;CACxC,IAAI,CAAC,eAAe,KAAK,QAAQ,GAC/B,MAAM,IAAI,MAAM,sBAAsB,KAAK,KAAK,OAAO,KAAK,YAAY,gBAAgB,0CAA0C;CAEpI,MAAM,MAAM,MAAM,cAAc,iBAAiB,aAAa,GAAG,gBAAgB,GAAG,OAAO,aAAa,EAAE,YAAY,KAAK,CAAC;CAE5H,OAAO;EAAE;EAAM,SADC,OAAO,QAAQ,WAAW,IAAI,MAAM,GAAG,cAAc,IAAI;CAClD;AACzB;AAEA,eAAsB,gBAAgB,aAAqB,OAA0C;CACnG,MAAM,cAAc,iBAAiB,aAAa,GAAG,gBAAgB,GAAG,mBAAmB,MAAM,MAAM,KAAK,EAAE,QAAQ,SAAS,CAAC;AAClI"}
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/calendarSyncStore.ts","../../src/google/collectionDateTime.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\";\n\nexport function googleConfigDir(home?: string): string {\n return join(home ?? homedir(), \".config\", \"mulmo\");\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 });\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;\nconst 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}\n\nexport async function googleRequest(apiLabel: string, accessToken: string, url: string, init: GoogleRequestInit = {}): Promise<unknown> {\n const { contentType = \"application/json\", expectText = false, ...rest } = init;\n const response = await fetchWithTimeout(url, {\n ...rest,\n timeoutMs: GOOGLE_API_TIMEOUT_MS,\n headers: { 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// An expired/invalidated calendar syncToken.\nconst HTTP_GONE = 410;\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\nexport interface CalendarEventInput {\n summary: string;\n startDateTime: string;\n endDateTime: string;\n description?: string;\n /** Calendar to create the event on; defaults to the user's primary. */\n calendarId?: string;\n /** Event colour (Google event palette id \"1\"..\"11\"); omit to inherit the calendar's colour. */\n colorId?: string;\n}\n\nexport interface UpdateCalendarEventInput {\n eventId: string;\n summary?: string;\n startDateTime?: string;\n endDateTime?: string;\n /** `\"\"` clears the description; omit to leave it untouched. */\n description?: string;\n calendarId?: string;\n colorId?: string;\n}\n\nexport interface DeleteCalendarEventInput {\n eventId: string;\n calendarId?: string;\n}\n\nexport interface ListEventsInput {\n timeMin?: string;\n maxResults?: number;\n /** Calendar to read; defaults to the user's primary. */\n calendarId?: string;\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}\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}\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 };\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 };\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\nexport async function createCalendarEvent(accessToken: string, input: CalendarEventInput): Promise<CalendarEventSummary> {\n const body = {\n summary: input.summary,\n description: input.description,\n start: { dateTime: input.startDateTime },\n end: { dateTime: input.endDateTime },\n ...(input.colorId ? { colorId: input.colorId } : {}),\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. */\nexport const buildEventPatch = (input: UpdateCalendarEventInput): Record<string, unknown> => ({\n ...(input.summary !== undefined ? { summary: input.summary } : {}),\n ...(input.description !== undefined ? { description: input.description } : {}),\n ...(input.startDateTime !== undefined ? { start: { dateTime: input.startDateTime } } : {}),\n ...(input.endDateTime !== undefined ? { end: { dateTime: 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, { method: \"PATCH\", body: JSON.stringify(buildEventPatch(input)) });\n return toEventSummary(updated);\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;\n /** Token from the previous sync. Omit for a full sync. */\n syncToken?: string;\n /** Page size for the underlying list calls. */\n maxResults?: number;\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;\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}\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. */\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 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\n const payload = await fetchSyncPage(accessToken, input.calendarId, params);\n if (payload === GONE) return { events: [], fullResyncRequired: true };\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 }\n\n return { events, nextSyncToken, fullResyncRequired: false };\n}\n\nexport interface CalendarListPage {\n items: unknown[];\n nextPageToken?: string;\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/** 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","// Persistence for the Google Calendar incremental-sync tokens (#2095).\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\";\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}\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\nasync function readState(workspaceRoot?: string): Promise<CalendarSyncState> {\n const stored = await readJsonOrNull<CalendarSyncState>(calendarSyncStatePath(workspaceRoot));\n return stored?.tokens ? stored : { tokens: {} };\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\nasync function updateState(mutate: (tokens: Record<string, string>) => Record<string, string>, workspaceRoot?: string): Promise<void> {\n const run = writeQueue.tail.then(async () => {\n const state = await readState(workspaceRoot);\n await writeJsonAtomicWithMode(calendarSyncStatePath(workspaceRoot), { tokens: mutate(state.tokens) }, SYNC_STATE_MODE);\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 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((tokens) => ({ ...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. */\nexport async function clearCalendarSyncToken(calendarId?: string, workspaceRoot?: string): Promise<void> {\n const dropped = calendarKey(calendarId);\n await updateState((tokens) => Object.fromEntries(Object.entries(tokens).filter(([key]) => key !== dropped)), workspaceRoot);\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","// 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 { 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 type { CollectionFieldSpec, CollectionItem } from \"../collection/core/schema.js\";\nimport type { GOOGLE_CALENDAR_SOURCE_FIELDS } from \"../collection/core/schemaZ.js\";\nimport { getGoogleAccessToken } from \"./auth.js\";\nimport { canonicalCalendarId, syncCalendarEvents, type CalendarEventSummary } from \"./calendar.js\";\nimport { toCollectionDateTime } from \"./collectionDateTime.js\";\nimport { clearCalendarSyncToken, loadCalendarSyncToken, saveCalendarSyncToken } from \"./calendarSyncStore.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;\nconst CANCELLED_STATUS = \"cancelled\";\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 /** Retryable failures. Any of these hold the sync token back. */\n errors: string[];\n}\n\n/** The event fields a schema may map from — narrowed to real keys of\n * `CalendarEventSummary` so the projection below needs no cast. */\ntype 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]) => [field, projectValue(fields, field, event[source])]);\n return { ...Object.fromEntries(mapped), [primaryKey]: event.id };\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. */\ntype ApplyOutcome =\n { kind: \"written\" } | { kind: \"removed\" } | { kind: \"skipped\" } | { kind: \"unwritable\"; message: string } | { 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\nasync function applyEvent(collection: LoadedCollection, event: CalendarEventSummary, workspaceRoot: string): 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 if (event.status === CANCELLED_STATUS) {\n const deleted = await store.delete(event.id);\n return classifyDelete(event.id, deleted.kind);\n }\n const record = toCollectionRecord(event, schema.googleCalendar?.map ?? {}, schema.primaryKey, schema.fields);\n const written = await store.write(event.id, record);\n return classifyWrite(event.id, written.kind);\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\nasync function restartFullSync(accessToken: string, calendarId: string | undefined, workspaceRoot: string) {\n await clearCalendarSyncToken(calendarId, workspaceRoot);\n return await syncCalendarEvents(accessToken, { calendarId });\n}\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 sync 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/** 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. */\nexport async function syncCalendarGroup(\n calendarId: string | undefined,\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n): Promise<CalendarCollectionSyncResult[]> {\n return await withKeyedLock(calendarLocks, canonicalCalendarId(calendarId), () => syncCalendarGroupNow(calendarId, collections, workspaceRoot));\n}\n\nasync function syncCalendarGroupNow(\n calendarId: string | undefined,\n collections: readonly LoadedCollection[],\n workspaceRoot: string,\n): Promise<CalendarCollectionSyncResult[]> {\n const accessToken = await getGoogleAccessToken();\n const storedToken = await loadCalendarSyncToken(calendarId, workspaceRoot);\n const first = await syncCalendarEvents(accessToken, { calendarId, syncToken: storedToken ?? undefined });\n const result = first.fullResyncRequired ? await restartFullSync(accessToken, calendarId, workspaceRoot) : first;\n\n const results: CalendarCollectionSyncResult[] = [];\n for (const collection of collections) {\n results.push(await applyEventsToCollection(collection, result.events, workspaceRoot));\n }\n // Advance the token only after every collection in the group consumed the\n // window AND every record actually landed. Google never resends a window, so\n // advancing past a failed write would lose those events for good; holding the\n // token back just replays them next run (writes are idempotent).\n const unwritable = results.flatMap((entry) => entry.unwritable);\n if (unwritable.length > 0) {\n // Never retryable, so the token still advances — but say so loudly, since\n // these events will silently never appear in the collection.\n log.warn(\"google\", \"skipping calendar events that can never be stored\", { calendarId, unwritable });\n }\n const failed = results.flatMap((entry) => entry.errors);\n if (failed.length > 0) {\n log.warn(\"google\", \"holding back calendar sync token after failed writes\", { calendarId, failed: failed.length });\n return results;\n }\n if (result.nextSyncToken) await advanceToken(calendarId, result.nextSyncToken, collections, workspaceRoot);\n return results;\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): Promise<CalendarCollectionSyncResult> {\n const outcomes: ApplyOutcome[] = [];\n for (const event of events) {\n outcomes.push(await applyEvent(collection, event, workspaceRoot));\n }\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 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 };\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 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. */\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 on this host\", { 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. */\nasync function runCalendarGroups(groups: Map<string, LoadedCollection[]>, workspaceRoot: string): Promise<CalendarCollectionSyncResult[]> {\n const results: CalendarCollectionSyncResult[] = [];\n for (const [calendarId, collections] of groups) {\n try {\n results.push(...(await syncCalendarGroup(calendarId, collections, workspaceRoot)));\n } catch (error) {\n log.warn(\"google\", \"calendar sync failed\", { calendarId, error: String(error) });\n results.push(...collections.map((collection) => ({ slug: collection.slug, written: 0, removed: 0, unwritable: [], errors: [String(error)] })));\n }\n }\n return results;\n}\n\n/** Sync every collection that declares `googleCalendar`. */\nexport async function syncDueCalendarCollections(workspaceRoot: string): Promise<CalendarCollectionSyncResult[]> {\n const groups = await declaringGroups(workspaceRoot);\n return (await backgroundSyncAllowed(groups)) ? await runCalendarGroups(groups, workspaceRoot) : [];\n}\n\n/** The groups whose calendar has never synced. A missing token IS the \"created\n * since the last sync\" signal — nothing else distinguishes a new collection\n * from an edited one on the write path this feeds (#2427).\n *\n * Self-silencing by construction: the first sync stores a token, so a calendar\n * matches at most once. `loadToken` is injected so the rule is testable without\n * a workspace on disk. */\nexport async function unsyncedGroups<T>(groups: Map<string, T>, loadToken: (calendarId: string) => Promise<string | null>): Promise<Map<string, T>> {\n const checked = await Promise.all([...groups].map(async (entry) => ((await loadToken(entry[0])) === null ? entry : null)));\n return new Map(checked.filter((entry): entry is [string, T] => entry !== null));\n}\n\n/** Sync only the calendars that have never synced — the first sync for a\n * just-created collection, which otherwise stays empty until the hourly\n * 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 unsyncedGroups(groups, (calendarId) => loadCalendarSyncToken(calendarId, workspaceRoot));\n if (!(await backgroundSyncAllowed(pending))) return [];\n log.info(\"google\", \"running the first sync for newly declared calendars\", { 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/** Scheduler registration, shaped like `feedRefreshTaskDef` so hosts wire it\n * with a single line. */\nexport function googleCalendarSyncTaskDef(opts?: { workspaceRoot?: string; intervalMs?: number }): SystemTaskDef {\n return {\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()).then(() => {}),\n };\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;\n maxResults?: number;\n showCompleted?: boolean;\n}\n\nexport interface CreateTaskInput {\n title: string;\n notes?: string;\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;\n taskListId?: string;\n}\n\nexport interface CompleteTaskInput {\n taskId: string;\n taskListId?: string;\n}\n\nexport interface UpdateTaskInput {\n taskId: string;\n title?: string;\n /** `\"\"` clears the notes; omit to leave them untouched. */\n notes?: string;\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;\n taskListId?: string;\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\nexport async function completeTask(accessToken: string, input: CompleteTaskInput): Promise<TaskSummary> {\n // PATCH keeps the rest of the task intact — a PUT would need the full body\n // and would silently drop fields the caller never read.\n const url = tasksUrl(input.taskListId, `/${encodeURIComponent(input.taskId)}`);\n const updated = await googleRequest(TASKS_API_LABEL, accessToken, url, {\n method: \"PATCH\",\n body: JSON.stringify({ status: TASK_STATUS_COMPLETED }),\n });\n return toTaskSummary(updated);\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 const url = tasksUrl(input.taskListId, `/${encodeURIComponent(input.taskId)}`);\n const updated = await googleRequest(TASKS_API_LABEL, accessToken, url, {\n method: \"PATCH\",\n body: JSON.stringify({ status: TASK_STATUS_NEEDS_ACTION }),\n });\n return toTaskSummary(updated);\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;\n}\n\nexport interface CreateDriveFileInput {\n name: string;\n content: string;\n mimeType?: string;\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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"}