{"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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"}