@mulmoclaude/core 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/helps/error-recovery.md +55 -0
- package/dist/collection/core/errorMessage.d.ts +1 -4
- package/dist/collection/core/ontologyGraph.d.ts +58 -0
- package/dist/collection/core/uiTypes.d.ts +7 -0
- package/dist/collection/index.cjs +81 -10
- package/dist/collection/index.cjs.map +1 -1
- package/dist/collection/index.d.ts +1 -0
- package/dist/collection/index.js +74 -4
- package/dist/collection/index.js.map +1 -1
- package/dist/collection/registry/server/index.cjs +8 -7
- package/dist/collection/registry/server/index.cjs.map +1 -1
- package/dist/collection/registry/server/index.js +3 -2
- package/dist/collection/registry/server/index.js.map +1 -1
- package/dist/collection/server/index.cjs +3 -3
- package/dist/collection/server/index.js +3 -3
- package/dist/collection/server/ontology.d.ts +2 -24
- package/dist/collection/server/util.d.ts +1 -2
- package/dist/collection-watchers/index.cjs +5 -4
- package/dist/collection-watchers/index.cjs.map +1 -1
- package/dist/collection-watchers/index.js +4 -3
- package/dist/collection-watchers/index.js.map +1 -1
- package/dist/{discovery-QUi_7aNG.js → discovery-B9Vfojyy.js} +2 -2
- package/dist/{discovery-QUi_7aNG.js.map → discovery-B9Vfojyy.js.map} +1 -1
- package/dist/{discovery-cwtfyDxl.cjs → discovery-BxbJy2VN.cjs} +2 -2
- package/dist/{discovery-cwtfyDxl.cjs.map → discovery-BxbJy2VN.cjs.map} +1 -1
- package/dist/errors-7P5eMOSX.cjs +30 -0
- package/dist/errors-7P5eMOSX.cjs.map +1 -0
- package/dist/errors-eid6Mes3.js +19 -0
- package/dist/errors-eid6Mes3.js.map +1 -0
- package/dist/feeds/index.cjs +2 -2
- package/dist/feeds/index.js +2 -2
- package/dist/feeds/server/index.cjs +4 -4
- package/dist/feeds/server/index.js +4 -4
- package/dist/google/index.cjs +8 -18
- package/dist/google/index.cjs.map +1 -1
- package/dist/google/index.js +3 -13
- package/dist/google/index.js.map +1 -1
- package/dist/google/util.d.ts +2 -4
- package/dist/{ids-P2T89Gou.js → ids-D1M1T6KJ.js} +26 -3
- package/dist/ids-D1M1T6KJ.js.map +1 -0
- package/dist/{ids-Dl9Ij63K.cjs → ids-DWmHjm17.cjs} +37 -2
- package/dist/ids-DWmHjm17.cjs.map +1 -0
- package/dist/{ingestTypes-C-IQB4nw.js → ingestTypes-Ci4eOgv-.js} +2 -2
- package/dist/{ingestTypes-C-IQB4nw.js.map → ingestTypes-Ci4eOgv-.js.map} +1 -1
- package/dist/{ingestTypes-DZpAC3N_.cjs → ingestTypes-DbuQNuK6.cjs} +2 -2
- package/dist/{ingestTypes-DZpAC3N_.cjs.map → ingestTypes-DbuQNuK6.cjs.map} +1 -1
- package/dist/{promptSafety-BOc1B3im.cjs → promptSafety-0ZKHX-6J.cjs} +5 -39
- package/dist/promptSafety-0ZKHX-6J.cjs.map +1 -0
- package/dist/{promptSafety-B5KPkaNY.js → promptSafety-RE1SPxBN.js} +3 -25
- package/dist/promptSafety-RE1SPxBN.js.map +1 -0
- package/dist/remote-host/server/index.cjs +4 -4
- package/dist/remote-host/server/index.cjs.map +1 -1
- package/dist/remote-host/server/index.js +1 -1
- package/dist/scheduler/index.cjs +2 -4
- package/dist/scheduler/index.cjs.map +1 -1
- package/dist/scheduler/index.js +1 -3
- package/dist/scheduler/index.js.map +1 -1
- package/dist/{server-B7mtx4Cd.cjs → server-DOGKfUuD.cjs} +9 -17
- package/dist/server-DOGKfUuD.cjs.map +1 -0
- package/dist/{server-CNLZZONa.js → server-sGNe-msA.js} +9 -11
- package/dist/server-sGNe-msA.js.map +1 -0
- package/dist/text-CaAh9ZSO.cjs +27 -0
- package/dist/text-CaAh9ZSO.cjs.map +1 -0
- package/dist/text-CjDbP2O1.js +22 -0
- package/dist/text-CjDbP2O1.js.map +1 -0
- package/dist/utils/errors.d.ts +2 -0
- package/dist/utils/index.cjs +6 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/text.d.ts +12 -0
- package/dist/whisper/index.cjs +7 -9
- package/dist/whisper/index.cjs.map +1 -1
- package/dist/whisper/index.js +1 -3
- package/dist/whisper/index.js.map +1 -1
- package/dist/whisper/internal.d.ts +1 -1
- package/dist/workspace-setup/index.js +12 -3
- package/dist/workspace-setup/index.js.map +1 -1
- package/package.json +7 -1
- package/dist/errorMessage--LvXe_Jx.cjs +0 -16
- package/dist/errorMessage--LvXe_Jx.cjs.map +0 -1
- package/dist/errorMessage-DIdhheHk.js +0 -11
- package/dist/errorMessage-DIdhheHk.js.map +0 -1
- package/dist/ids-Dl9Ij63K.cjs.map +0 -1
- package/dist/ids-P2T89Gou.js.map +0 -1
- package/dist/promptSafety-B5KPkaNY.js.map +0 -1
- package/dist/promptSafety-BOc1B3im.cjs.map +0 -1
- package/dist/server-B7mtx4Cd.cjs.map +0 -1
- package/dist/server-CNLZZONa.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/google/host.ts","../../src/google/datetime.ts","../../src/google/paths.ts","../../src/google/util.ts","../../src/google/clientSecret.ts","../../src/google/fsJson.ts","../../src/google/tokenStore.ts","../../src/google/fetch.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/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\nexport interface GoogleLogger {\n error: (prefix: string, message: string, data?: Record<string, unknown>) => void;\n warn: (prefix: string, message: string, data?: Record<string, unknown>) => void;\n info: (prefix: string, message: string, data?: Record<string, unknown>) => void;\n debug: (prefix: string, message: string, data?: Record<string, unknown>) => void;\n}\n\nconst silentLogger: GoogleLogger = {\n error: () => undefined,\n warn: () => undefined,\n info: () => undefined,\n debug: () => undefined,\n};\n\nlet hostLog: GoogleLogger = silentLogger;\n\nexport function configureGoogleHost(binding: { log: GoogleLogger }): void {\n hostLog = binding.log;\n}\n\nexport const log: GoogleLogger = {\n error: (prefix, message, data) => hostLog.error(prefix, message, data),\n warn: (prefix, message, data) => hostLog.warn(prefix, message, data),\n info: (prefix, message, data) => hostLog.info(prefix, message, data),\n debug: (prefix, message, data) => hostLog.debug(prefix, message, data),\n};\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","// Small helpers ported from the host (`server/utils/{errors,text,time,types}.ts`)\n// so the Google engine carries no dependency on host utils — same convention\n// as `collection/registry/server/fetch.ts`.\n\nexport const ONE_SECOND_MS = 1_000;\nexport const ONE_MINUTE_MS = 60_000;\n\nexport function errorMessage(err: unknown, fallback = \"unknown error\"): string {\n if (err instanceof Error) return err.message || fallback;\n const text = String(err);\n return text === \"\" || text === \"[object Object]\" ? fallback : text;\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Clip a string to at most `max` chars; the ellipsis is included in the\n * budget so output never exceeds `max`. */\nexport function truncate(text: string, max: number, ellipsis = \"…\"): string {\n if (max <= 0) return \"\";\n if (text.length <= max) return text;\n return `${text.slice(0, Math.max(0, max - ellipsis.length))}${ellipsis}`;\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. Unlike the host's\n// `writeJsonAtomic` (and core's collection `atomic.ts`), this one supports a\n// file mode — the token file must be 0600.\nimport { promises as fsp } from \"node:fs\";\nimport path from \"node:path\";\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\nconst IS_WINDOWS = process.platform === \"win32\";\nconst RENAME_RETRY_DELAYS_MS = [30, 100, 300];\n\nconst hasErrnoCode = (err: unknown): err is { code: string } =>\n typeof err === \"object\" && err !== null && \"code\" in err && typeof (err as { code: unknown }).code === \"string\";\n\n// On Windows, AV / Search Indexer / Defender briefly hold handles and rename\n// trips EPERM/EBUSY/EACCES. The retry is gated to Windows because POSIX EPERM\n// means a real permission problem — retrying would just delay the throw.\n// Ported from the host's server/utils/files/atomic.ts.\nconst isTransientRenameError = (err: unknown): boolean =>\n IS_WINDOWS && hasErrnoCode(err) && (err.code === \"EPERM\" || err.code === \"EBUSY\" || err.code === \"EACCES\");\n\nasync function renameWithWindowsRetry(fromPath: string, toPath: string): Promise<void> {\n for (const delayMs of RENAME_RETRY_DELAYS_MS) {\n try {\n await fsp.rename(fromPath, toPath);\n return;\n } catch (err) {\n if (!isTransientRenameError(err)) throw err;\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n }\n await fsp.rename(fromPath, toPath);\n}\n\n/** tmp-write + rename so readers never see a half-written file; `mode`\n * applies to the tmp file and survives the rename. */\nexport async function writeJsonAtomicWithMode(filePath: string, data: unknown, mode: number): Promise<void> {\n const tmp = `${filePath}.tmp`;\n await fsp.mkdir(path.dirname(filePath), { recursive: true });\n try {\n await fsp.writeFile(tmp, JSON.stringify(data, null, 2), { encoding: \"utf-8\", mode });\n await renameWithWindowsRetry(tmp, filePath);\n } catch (err) {\n await fsp.unlink(tmp).catch(() => undefined);\n throw err;\n }\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","// `fetch` with a finite timeout, ported from the host (`server/utils/fetch.ts`)\n// so the Google engine carries no dependency on host utils (same convention as\n// `collection/registry/server/fetch.ts`).\n\nimport { ONE_SECOND_MS } from \"./util.js\";\n\nexport const DEFAULT_FETCH_TIMEOUT_MS = 10 * ONE_SECOND_MS;\n\n// `Parameters<typeof fetch>[1]` avoids referencing the ambient `RequestInit`\n// type, which ESLint's `no-undef` rule trips over in the server config.\nexport type FetchWithTimeoutInit = Parameters<typeof fetch>[1] & { timeoutMs?: number };\n\n/** `fetch` with a finite timeout. Rejects with a `TimeoutError` once\n * `timeoutMs` elapses. Composes with a caller-supplied `signal` so external\n * cancellation still works. */\nexport async function fetchWithTimeout(url: string | URL, init: FetchWithTimeoutInit = {}): Promise<Response> {\n const { timeoutMs = DEFAULT_FETCH_TIMEOUT_MS, signal: callerSignal, ...rest } = init;\n\n if (callerSignal?.aborted) {\n throw callerSignal.reason ?? new DOMException(\"Aborted\", \"AbortError\");\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => {\n controller.abort(new DOMException(`fetch timed out after ${timeoutMs}ms`, \"TimeoutError\"));\n }, timeoutMs);\n\n const unsubscribeCaller = bridgeExternalSignal(callerSignal, controller);\n\n try {\n return await fetch(url, { ...rest, signal: controller.signal });\n } finally {\n clearTimeout(timer);\n unsubscribeCaller?.();\n }\n}\n\nfunction bridgeExternalSignal(external: AbortSignal | null | undefined, controller: AbortController): (() => void) | null {\n if (!external) return null;\n const onAbort = () => controller.abort(external.reason);\n external.addEventListener(\"abort\", onAbort, { once: true });\n return () => external.removeEventListener(\"abort\", onAbort);\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 \"./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 \"./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 \"./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 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\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","// 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 { 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 { 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 { 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\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(event: CalendarEventSummary, map: Record<string, GoogleCalendarSourceField>, primaryKey: string): CollectionItem {\n const mapped = Object.entries(map).map(([field, source]) => [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);\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/** 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.) */\nexport async function syncCalendarGroup(\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 (result.nextSyncToken && failed.length === 0) {\n await saveCalendarSyncToken(calendarId, result.nextSyncToken, workspaceRoot);\n } else if (failed.length > 0) {\n log.warn(\"google\", \"holding back calendar sync token after failed writes\", { calendarId, failed: failed.length });\n }\n return results;\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/** Sync every collection that declares `googleCalendar`. Failures are isolated\n * per calendar — one unreachable calendar (or a revoked grant) must not stop\n * the others. */\nexport async function syncDueCalendarCollections(workspaceRoot: string): Promise<CalendarCollectionSyncResult[]> {\n const all = await discoverCollections({ workspaceRoot });\n const declaring = all.filter((collection) => collection.schema.googleCalendar);\n if (declaring.length === 0) return [];\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 if (!(await isGoogleLinked())) {\n log.info(\"google\", \"skipping calendar sync — no Google account linked on this host\", { collections: declaring.length });\n return [];\n }\n const results: CalendarCollectionSyncResult[] = [];\n for (const [calendarId, collections] of groupByCalendar(declaring)) {\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/** Scheduler registration, shaped like `feedRefreshTaskDef` so hosts wire it\n * with a single line. */\nexport function googleCalendarSyncTaskDef(opts?: { workspaceRoot?: string; intervalMs?: number }): SystemTaskDef {\n return {\n id: GOOGLE_CALENDAR_SYNC_TASK_ID,\n name: \"Google Calendar sync\",\n description: \"Pulls changed Google Calendar events into any collection declaring `googleCalendar`, without invoking the LLM.\",\n schedule: { type: SCHEDULE_TYPES.interval, intervalMs: opts?.intervalMs ?? DEFAULT_SYNC_INTERVAL_MS },\n missedRunPolicy: MISSED_RUN_POLICIES.runOnce,\n run: () => syncDueCalendarCollections(opts?.workspaceRoot ?? getWorkspaceRoot()).then(() => {}),\n };\n}\n","// Google Tasks v1 REST calls. `@default` is Google's alias for the user's\n// default task list, so callers can stay list-agnostic.\nimport { asRecord, googleRequest, itemsOf, stringField, DEFAULT_LIST_MAX_RESULTS } from \"./apiClient.js\";\n\nconst TASKS_BASE_URL = \"https://tasks.googleapis.com/tasks/v1\";\nconst TASKS_API_LABEL = \"Google Tasks API\";\nconst DEFAULT_TASK_LIST_ID = \"@default\";\nconst TASK_STATUS_COMPLETED = \"completed\";\nconst MAX_TASK_LISTS = 50;\n\nexport interface TaskListSummary {\n id: string;\n title: string;\n}\n\nexport interface TaskSummary {\n id: string;\n title: string;\n status: string;\n due: string;\n notes: string;\n}\n\nexport interface ListTasksInput {\n taskListId?: string;\n maxResults?: number;\n showCompleted?: boolean;\n}\n\nexport interface CreateTaskInput {\n title: string;\n notes?: string;\n /** RFC3339. Google stores a DATE only — the time part is recorded but\n * ignored by the UI, so callers should not promise time-of-day fidelity. */\n due?: string;\n taskListId?: string;\n}\n\nexport interface CompleteTaskInput {\n taskId: string;\n taskListId?: string;\n}\n\nexport 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\nconst tasksUrl = (taskListId: string | undefined, suffix = \"\"): string =>\n `${TASKS_BASE_URL}/lists/${encodeURIComponent(taskListId ?? DEFAULT_TASK_LIST_ID)}/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\nexport async function completeTask(accessToken: string, input: CompleteTaskInput): Promise<TaskSummary> {\n // PATCH keeps the rest of the task intact — a PUT would need the full body\n // and would silently drop fields the caller never read.\n const url = tasksUrl(input.taskListId, `/${encodeURIComponent(input.taskId)}`);\n const updated = await googleRequest(TASKS_API_LABEL, accessToken, url, {\n method: \"PATCH\",\n body: JSON.stringify({ status: TASK_STATUS_COMPLETED }),\n });\n return toTaskSummary(updated);\n}\n\nexport async function deleteTask(accessToken: string, input: CompleteTaskInput): Promise<void> {\n const url = tasksUrl(input.taskListId, `/${encodeURIComponent(input.taskId)}`);\n await googleRequest(TASKS_API_LABEL, accessToken, url, { method: \"DELETE\" });\n}\n","// Google Drive v3 REST calls under the `drive.file` scope — the app only\n// ever sees files IT created, never the user's wider Drive. That narrow\n// scope is why this is non-sensitive and needs no Google verification\n// review; keep every call inside it.\nimport { randomBytes } from \"node:crypto\";\nimport { asRecord, googleRequest, stringField, DEFAULT_LIST_MAX_RESULTS } from \"./apiClient.js\";\n\nconst DRIVE_FILES_URL = \"https://www.googleapis.com/drive/v3/files\";\nconst DRIVE_UPLOAD_URL = \"https://www.googleapis.com/upload/drive/v3/files\";\nconst DRIVE_API_LABEL = \"Google Drive API\";\nconst DEFAULT_MIME_TYPE = \"text/plain\";\nconst FILE_FIELDS = \"id,name,mimeType,webViewLink,modifiedTime\";\n// Reading a huge blob into a tool result would blow the context window; the\n// tool surface is for app-created text documents, not media.\nconst MAX_READ_CHARS = 100_000;\nconst TEXT_MIME_PREFIXES = [\"text/\", \"application/json\", \"application/xml\", \"application/javascript\"];\n\nexport interface DriveFileSummary {\n id: string;\n name: string;\n mimeType: string;\n webViewLink: string;\n modifiedTime: string;\n}\n\nexport interface ListDriveFilesInput {\n maxResults?: number;\n}\n\nexport interface CreateDriveFileInput {\n name: string;\n content: string;\n mimeType?: string;\n}\n\nexport interface ReadDriveFileInput {\n fileId: string;\n}\n\nexport const toDriveFileSummary = (value: unknown): DriveFileSummary => {\n const record = asRecord(value);\n return {\n id: stringField(record, \"id\"),\n name: stringField(record, \"name\"),\n mimeType: stringField(record, \"mimeType\"),\n webViewLink: stringField(record, \"webViewLink\"),\n modifiedTime: stringField(record, \"modifiedTime\"),\n };\n};\n\nexport const isTextMimeType = (mimeType: string): boolean => TEXT_MIME_PREFIXES.some((prefix) => mimeType.startsWith(prefix));\n\nexport async function listDriveFiles(accessToken: string, input: ListDriveFilesInput = {}): Promise<DriveFileSummary[]> {\n const params = new URLSearchParams({\n pageSize: String(input.maxResults ?? DEFAULT_LIST_MAX_RESULTS),\n fields: `files(${FILE_FIELDS})`,\n orderBy: \"modifiedTime desc\",\n });\n const listed = await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}?${params.toString()}`);\n const record = asRecord(listed);\n const files = Array.isArray(record.files) ? record.files : [];\n return files.map(toDriveFileSummary);\n}\n\n// Multipart upload: metadata part + media part in one request. Built by hand\n// because the `googleapis` SDK is the only alternative and this is the sole\n// multipart call in the engine.\nconst BOUNDARY_BYTES = 16;\n\n/** Fresh per request: a fixed boundary appearing inside file content would\n * split the payload at the wrong place, so Drive would store a truncated or\n * mangled file. Random 128 bits makes an accidental — or crafted — collision\n * infeasible; `newMultipartBoundary` is re-derived until it is absent from\n * the body it must delimit. */\nconst newMultipartBoundary = (): string => `mulmo-drive-${randomBytes(BOUNDARY_BYTES).toString(\"hex\")}`;\n\n// RFC 2045 token + parameters: no CR/LF (header injection into the part\n// headers) and no quotes/semicolons that would let a crafted value forge\n// extra headers or parameters.\nconst MIME_TYPE_RE = /^[A-Za-z0-9!#$&^_.+-]+\\/[A-Za-z0-9!#$&^_.+-]+$/;\n\nexport const assertSafeMimeType = (mimeType: string): string => {\n if (!MIME_TYPE_RE.test(mimeType)) throw new Error(`Google Drive API: invalid mimeType '${mimeType}' — expected a plain type/subtype such as text/plain`);\n return mimeType;\n};\n\nexport const buildMultipartBody = (metadata: Record<string, string>, content: string, mimeType: string, boundary: string): string =>\n [\n `--${boundary}`,\n \"Content-Type: application/json; charset=UTF-8\",\n \"\",\n JSON.stringify(metadata),\n `--${boundary}`,\n `Content-Type: ${mimeType}`,\n \"\",\n content,\n `--${boundary}--`,\n \"\",\n ].join(\"\\r\\n\");\n\n/** A boundary that appears nowhere in the parts it delimits. */\nexport const pickBoundary = (parts: string[], generate: () => string = newMultipartBoundary): string => {\n const collides = (candidate: string): boolean => parts.some((part) => part.includes(candidate));\n let boundary = generate();\n while (collides(boundary)) boundary = generate();\n return boundary;\n};\n\nexport async function createDriveFile(accessToken: string, input: CreateDriveFileInput): Promise<DriveFileSummary> {\n const mimeType = assertSafeMimeType(input.mimeType ?? DEFAULT_MIME_TYPE);\n const metadata = { name: input.name, mimeType };\n const boundary = pickBoundary([input.content, JSON.stringify(metadata)]);\n const params = new URLSearchParams({ uploadType: \"multipart\", fields: FILE_FIELDS });\n const created = await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_UPLOAD_URL}?${params.toString()}`, {\n method: \"POST\",\n contentType: `multipart/related; boundary=${boundary}`,\n body: buildMultipartBody(metadata, input.content, mimeType, boundary),\n });\n return toDriveFileSummary(created);\n}\n\nexport async function readDriveFile(accessToken: string, input: ReadDriveFileInput): Promise<{ file: DriveFileSummary; content: string }> {\n const fileId = encodeURIComponent(input.fileId);\n const metadata = await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}/${fileId}?fields=${FILE_FIELDS}`);\n const file = toDriveFileSummary(metadata);\n if (!isTextMimeType(file.mimeType)) {\n throw new Error(`Google Drive API: '${file.name}' is ${file.mimeType || \"a binary file\"} — only text files can be read as content`);\n }\n const raw = await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}/${fileId}?alt=media`, { expectText: true });\n const content = typeof raw === \"string\" ? raw.slice(0, MAX_READ_CHARS) : \"\";\n return { file, content };\n}\n\nexport async function deleteDriveFile(accessToken: string, input: ReadDriveFileInput): Promise<void> {\n await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}/${encodeURIComponent(input.fileId)}`, { method: \"DELETE\" });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/google/host.ts","../../src/google/datetime.ts","../../src/google/paths.ts","../../src/google/util.ts","../../src/google/clientSecret.ts","../../src/google/fsJson.ts","../../src/google/tokenStore.ts","../../src/google/fetch.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/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\nexport interface GoogleLogger {\n error: (prefix: string, message: string, data?: Record<string, unknown>) => void;\n warn: (prefix: string, message: string, data?: Record<string, unknown>) => void;\n info: (prefix: string, message: string, data?: Record<string, unknown>) => void;\n debug: (prefix: string, message: string, data?: Record<string, unknown>) => void;\n}\n\nconst silentLogger: GoogleLogger = {\n error: () => undefined,\n warn: () => undefined,\n info: () => undefined,\n debug: () => undefined,\n};\n\nlet hostLog: GoogleLogger = silentLogger;\n\nexport function configureGoogleHost(binding: { log: GoogleLogger }): void {\n hostLog = binding.log;\n}\n\nexport const log: GoogleLogger = {\n error: (prefix, message, data) => hostLog.error(prefix, message, data),\n warn: (prefix, message, data) => hostLog.warn(prefix, message, data),\n info: (prefix, message, data) => hostLog.info(prefix, message, data),\n debug: (prefix, message, data) => hostLog.debug(prefix, message, data),\n};\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. Unlike the host's\n// `writeJsonAtomic` (and core's collection `atomic.ts`), this one supports a\n// file mode — the token file must be 0600.\nimport { promises as fsp } from \"node:fs\";\nimport path from \"node:path\";\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\nconst IS_WINDOWS = process.platform === \"win32\";\nconst RENAME_RETRY_DELAYS_MS = [30, 100, 300];\n\nconst hasErrnoCode = (err: unknown): err is { code: string } =>\n typeof err === \"object\" && err !== null && \"code\" in err && typeof (err as { code: unknown }).code === \"string\";\n\n// On Windows, AV / Search Indexer / Defender briefly hold handles and rename\n// trips EPERM/EBUSY/EACCES. The retry is gated to Windows because POSIX EPERM\n// means a real permission problem — retrying would just delay the throw.\n// Ported from the host's server/utils/files/atomic.ts.\nconst isTransientRenameError = (err: unknown): boolean =>\n IS_WINDOWS && hasErrnoCode(err) && (err.code === \"EPERM\" || err.code === \"EBUSY\" || err.code === \"EACCES\");\n\nasync function renameWithWindowsRetry(fromPath: string, toPath: string): Promise<void> {\n for (const delayMs of RENAME_RETRY_DELAYS_MS) {\n try {\n await fsp.rename(fromPath, toPath);\n return;\n } catch (err) {\n if (!isTransientRenameError(err)) throw err;\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n }\n await fsp.rename(fromPath, toPath);\n}\n\n/** tmp-write + rename so readers never see a half-written file; `mode`\n * applies to the tmp file and survives the rename. */\nexport async function writeJsonAtomicWithMode(filePath: string, data: unknown, mode: number): Promise<void> {\n const tmp = `${filePath}.tmp`;\n await fsp.mkdir(path.dirname(filePath), { recursive: true });\n try {\n await fsp.writeFile(tmp, JSON.stringify(data, null, 2), { encoding: \"utf-8\", mode });\n await renameWithWindowsRetry(tmp, filePath);\n } catch (err) {\n await fsp.unlink(tmp).catch(() => undefined);\n throw err;\n }\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","// `fetch` with a finite timeout, ported from the host (`server/utils/fetch.ts`)\n// so the Google engine carries no dependency on host utils (same convention as\n// `collection/registry/server/fetch.ts`).\n\nimport { ONE_SECOND_MS } from \"./util.js\";\n\nexport const DEFAULT_FETCH_TIMEOUT_MS = 10 * ONE_SECOND_MS;\n\n// `Parameters<typeof fetch>[1]` avoids referencing the ambient `RequestInit`\n// type, which ESLint's `no-undef` rule trips over in the server config.\nexport type FetchWithTimeoutInit = Parameters<typeof fetch>[1] & { timeoutMs?: number };\n\n/** `fetch` with a finite timeout. Rejects with a `TimeoutError` once\n * `timeoutMs` elapses. Composes with a caller-supplied `signal` so external\n * cancellation still works. */\nexport async function fetchWithTimeout(url: string | URL, init: FetchWithTimeoutInit = {}): Promise<Response> {\n const { timeoutMs = DEFAULT_FETCH_TIMEOUT_MS, signal: callerSignal, ...rest } = init;\n\n if (callerSignal?.aborted) {\n throw callerSignal.reason ?? new DOMException(\"Aborted\", \"AbortError\");\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => {\n controller.abort(new DOMException(`fetch timed out after ${timeoutMs}ms`, \"TimeoutError\"));\n }, timeoutMs);\n\n const unsubscribeCaller = bridgeExternalSignal(callerSignal, controller);\n\n try {\n return await fetch(url, { ...rest, signal: controller.signal });\n } finally {\n clearTimeout(timer);\n unsubscribeCaller?.();\n }\n}\n\nfunction bridgeExternalSignal(external: AbortSignal | null | undefined, controller: AbortController): (() => void) | null {\n if (!external) return null;\n const onAbort = () => controller.abort(external.reason);\n external.addEventListener(\"abort\", onAbort, { once: true });\n return () => external.removeEventListener(\"abort\", onAbort);\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 \"./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 \"./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 \"./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 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\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","// 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 { 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 { 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 { 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\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(event: CalendarEventSummary, map: Record<string, GoogleCalendarSourceField>, primaryKey: string): CollectionItem {\n const mapped = Object.entries(map).map(([field, source]) => [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);\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/** 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.) */\nexport async function syncCalendarGroup(\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 (result.nextSyncToken && failed.length === 0) {\n await saveCalendarSyncToken(calendarId, result.nextSyncToken, workspaceRoot);\n } else if (failed.length > 0) {\n log.warn(\"google\", \"holding back calendar sync token after failed writes\", { calendarId, failed: failed.length });\n }\n return results;\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/** Sync every collection that declares `googleCalendar`. Failures are isolated\n * per calendar — one unreachable calendar (or a revoked grant) must not stop\n * the others. */\nexport async function syncDueCalendarCollections(workspaceRoot: string): Promise<CalendarCollectionSyncResult[]> {\n const all = await discoverCollections({ workspaceRoot });\n const declaring = all.filter((collection) => collection.schema.googleCalendar);\n if (declaring.length === 0) return [];\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 if (!(await isGoogleLinked())) {\n log.info(\"google\", \"skipping calendar sync — no Google account linked on this host\", { collections: declaring.length });\n return [];\n }\n const results: CalendarCollectionSyncResult[] = [];\n for (const [calendarId, collections] of groupByCalendar(declaring)) {\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/** Scheduler registration, shaped like `feedRefreshTaskDef` so hosts wire it\n * with a single line. */\nexport function googleCalendarSyncTaskDef(opts?: { workspaceRoot?: string; intervalMs?: number }): SystemTaskDef {\n return {\n id: GOOGLE_CALENDAR_SYNC_TASK_ID,\n name: \"Google Calendar sync\",\n description: \"Pulls changed Google Calendar events into any collection declaring `googleCalendar`, without invoking the LLM.\",\n schedule: { type: SCHEDULE_TYPES.interval, intervalMs: opts?.intervalMs ?? DEFAULT_SYNC_INTERVAL_MS },\n missedRunPolicy: MISSED_RUN_POLICIES.runOnce,\n run: () => syncDueCalendarCollections(opts?.workspaceRoot ?? getWorkspaceRoot()).then(() => {}),\n };\n}\n","// Google Tasks v1 REST calls. `@default` is Google's alias for the user's\n// default task list, so callers can stay list-agnostic.\nimport { asRecord, googleRequest, itemsOf, stringField, DEFAULT_LIST_MAX_RESULTS } from \"./apiClient.js\";\n\nconst TASKS_BASE_URL = \"https://tasks.googleapis.com/tasks/v1\";\nconst TASKS_API_LABEL = \"Google Tasks API\";\nconst DEFAULT_TASK_LIST_ID = \"@default\";\nconst TASK_STATUS_COMPLETED = \"completed\";\nconst MAX_TASK_LISTS = 50;\n\nexport interface TaskListSummary {\n id: string;\n title: string;\n}\n\nexport interface TaskSummary {\n id: string;\n title: string;\n status: string;\n due: string;\n notes: string;\n}\n\nexport interface ListTasksInput {\n taskListId?: string;\n maxResults?: number;\n showCompleted?: boolean;\n}\n\nexport interface CreateTaskInput {\n title: string;\n notes?: string;\n /** RFC3339. Google stores a DATE only — the time part is recorded but\n * ignored by the UI, so callers should not promise time-of-day fidelity. */\n due?: string;\n taskListId?: string;\n}\n\nexport interface CompleteTaskInput {\n taskId: string;\n taskListId?: string;\n}\n\nexport 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\nconst tasksUrl = (taskListId: string | undefined, suffix = \"\"): string =>\n `${TASKS_BASE_URL}/lists/${encodeURIComponent(taskListId ?? DEFAULT_TASK_LIST_ID)}/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\nexport async function completeTask(accessToken: string, input: CompleteTaskInput): Promise<TaskSummary> {\n // PATCH keeps the rest of the task intact — a PUT would need the full body\n // and would silently drop fields the caller never read.\n const url = tasksUrl(input.taskListId, `/${encodeURIComponent(input.taskId)}`);\n const updated = await googleRequest(TASKS_API_LABEL, accessToken, url, {\n method: \"PATCH\",\n body: JSON.stringify({ status: TASK_STATUS_COMPLETED }),\n });\n return toTaskSummary(updated);\n}\n\nexport async function deleteTask(accessToken: string, input: CompleteTaskInput): Promise<void> {\n const url = tasksUrl(input.taskListId, `/${encodeURIComponent(input.taskId)}`);\n await googleRequest(TASKS_API_LABEL, accessToken, url, { method: \"DELETE\" });\n}\n","// Google Drive v3 REST calls under the `drive.file` scope — the app only\n// ever sees files IT created, never the user's wider Drive. That narrow\n// scope is why this is non-sensitive and needs no Google verification\n// review; keep every call inside it.\nimport { randomBytes } from \"node:crypto\";\nimport { asRecord, googleRequest, stringField, DEFAULT_LIST_MAX_RESULTS } from \"./apiClient.js\";\n\nconst DRIVE_FILES_URL = \"https://www.googleapis.com/drive/v3/files\";\nconst DRIVE_UPLOAD_URL = \"https://www.googleapis.com/upload/drive/v3/files\";\nconst DRIVE_API_LABEL = \"Google Drive API\";\nconst DEFAULT_MIME_TYPE = \"text/plain\";\nconst FILE_FIELDS = \"id,name,mimeType,webViewLink,modifiedTime\";\n// Reading a huge blob into a tool result would blow the context window; the\n// tool surface is for app-created text documents, not media.\nconst MAX_READ_CHARS = 100_000;\nconst TEXT_MIME_PREFIXES = [\"text/\", \"application/json\", \"application/xml\", \"application/javascript\"];\n\nexport interface DriveFileSummary {\n id: string;\n name: string;\n mimeType: string;\n webViewLink: string;\n modifiedTime: string;\n}\n\nexport interface ListDriveFilesInput {\n maxResults?: number;\n}\n\nexport interface CreateDriveFileInput {\n name: string;\n content: string;\n mimeType?: string;\n}\n\nexport interface ReadDriveFileInput {\n fileId: string;\n}\n\nexport const toDriveFileSummary = (value: unknown): DriveFileSummary => {\n const record = asRecord(value);\n return {\n id: stringField(record, \"id\"),\n name: stringField(record, \"name\"),\n mimeType: stringField(record, \"mimeType\"),\n webViewLink: stringField(record, \"webViewLink\"),\n modifiedTime: stringField(record, \"modifiedTime\"),\n };\n};\n\nexport const isTextMimeType = (mimeType: string): boolean => TEXT_MIME_PREFIXES.some((prefix) => mimeType.startsWith(prefix));\n\nexport async function listDriveFiles(accessToken: string, input: ListDriveFilesInput = {}): Promise<DriveFileSummary[]> {\n const params = new URLSearchParams({\n pageSize: String(input.maxResults ?? DEFAULT_LIST_MAX_RESULTS),\n fields: `files(${FILE_FIELDS})`,\n orderBy: \"modifiedTime desc\",\n });\n const listed = await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}?${params.toString()}`);\n const record = asRecord(listed);\n const files = Array.isArray(record.files) ? record.files : [];\n return files.map(toDriveFileSummary);\n}\n\n// Multipart upload: metadata part + media part in one request. Built by hand\n// because the `googleapis` SDK is the only alternative and this is the sole\n// multipart call in the engine.\nconst BOUNDARY_BYTES = 16;\n\n/** Fresh per request: a fixed boundary appearing inside file content would\n * split the payload at the wrong place, so Drive would store a truncated or\n * mangled file. Random 128 bits makes an accidental — or crafted — collision\n * infeasible; `newMultipartBoundary` is re-derived until it is absent from\n * the body it must delimit. */\nconst newMultipartBoundary = (): string => `mulmo-drive-${randomBytes(BOUNDARY_BYTES).toString(\"hex\")}`;\n\n// RFC 2045 token + parameters: no CR/LF (header injection into the part\n// headers) and no quotes/semicolons that would let a crafted value forge\n// extra headers or parameters.\nconst MIME_TYPE_RE = /^[A-Za-z0-9!#$&^_.+-]+\\/[A-Za-z0-9!#$&^_.+-]+$/;\n\nexport const assertSafeMimeType = (mimeType: string): string => {\n if (!MIME_TYPE_RE.test(mimeType)) throw new Error(`Google Drive API: invalid mimeType '${mimeType}' — expected a plain type/subtype such as text/plain`);\n return mimeType;\n};\n\nexport const buildMultipartBody = (metadata: Record<string, string>, content: string, mimeType: string, boundary: string): string =>\n [\n `--${boundary}`,\n \"Content-Type: application/json; charset=UTF-8\",\n \"\",\n JSON.stringify(metadata),\n `--${boundary}`,\n `Content-Type: ${mimeType}`,\n \"\",\n content,\n `--${boundary}--`,\n \"\",\n ].join(\"\\r\\n\");\n\n/** A boundary that appears nowhere in the parts it delimits. */\nexport const pickBoundary = (parts: string[], generate: () => string = newMultipartBoundary): string => {\n const collides = (candidate: string): boolean => parts.some((part) => part.includes(candidate));\n let boundary = generate();\n while (collides(boundary)) boundary = generate();\n return boundary;\n};\n\nexport async function createDriveFile(accessToken: string, input: CreateDriveFileInput): Promise<DriveFileSummary> {\n const mimeType = assertSafeMimeType(input.mimeType ?? DEFAULT_MIME_TYPE);\n const metadata = { name: input.name, mimeType };\n const boundary = pickBoundary([input.content, JSON.stringify(metadata)]);\n const params = new URLSearchParams({ uploadType: \"multipart\", fields: FILE_FIELDS });\n const created = await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_UPLOAD_URL}?${params.toString()}`, {\n method: \"POST\",\n contentType: `multipart/related; boundary=${boundary}`,\n body: buildMultipartBody(metadata, input.content, mimeType, boundary),\n });\n return toDriveFileSummary(created);\n}\n\nexport async function readDriveFile(accessToken: string, input: ReadDriveFileInput): Promise<{ file: DriveFileSummary; content: string }> {\n const fileId = encodeURIComponent(input.fileId);\n const metadata = await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}/${fileId}?fields=${FILE_FIELDS}`);\n const file = toDriveFileSummary(metadata);\n if (!isTextMimeType(file.mimeType)) {\n throw new Error(`Google Drive API: '${file.name}' is ${file.mimeType || \"a binary file\"} — only text files can be read as content`);\n }\n const raw = await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}/${fileId}?alt=media`, { expectText: true });\n const content = typeof raw === \"string\" ? raw.slice(0, MAX_READ_CHARS) : \"\";\n return { file, content };\n}\n\nexport async function deleteDriveFile(accessToken: string, input: ReadDriveFileInput): Promise<void> {\n await googleRequest(DRIVE_API_LABEL, accessToken, `${DRIVE_FILES_URL}/${encodeURIComponent(input.fileId)}`, { method: \"DELETE\" });\n}\n"],"mappings":"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"}
|
package/dist/google/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as errorMessage } from "../errors-eid6Mes3.js";
|
|
2
|
+
import { t as truncate } from "../text-CjDbP2O1.js";
|
|
3
|
+
import { Q as getWorkspaceRoot, n as discoverCollections, u as storeFor } from "../discovery-B9Vfojyy.js";
|
|
2
4
|
import path, { join } from "node:path";
|
|
3
5
|
import { promises } from "node:fs";
|
|
4
6
|
import { constants, copyFile, mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
|
|
@@ -61,21 +63,9 @@ function googleSecretsDir(home) {
|
|
|
61
63
|
//#region src/google/util.ts
|
|
62
64
|
var ONE_SECOND_MS = 1e3;
|
|
63
65
|
var ONE_MINUTE_MS = 6e4;
|
|
64
|
-
function errorMessage(err, fallback = "unknown error") {
|
|
65
|
-
if (err instanceof Error) return err.message || fallback;
|
|
66
|
-
const text = String(err);
|
|
67
|
-
return text === "" || text === "[object Object]" ? fallback : text;
|
|
68
|
-
}
|
|
69
66
|
function isRecord(value) {
|
|
70
67
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
71
68
|
}
|
|
72
|
-
/** Clip a string to at most `max` chars; the ellipsis is included in the
|
|
73
|
-
* budget so output never exceeds `max`. */
|
|
74
|
-
function truncate(text, max, ellipsis = "…") {
|
|
75
|
-
if (max <= 0) return "";
|
|
76
|
-
if (text.length <= max) return text;
|
|
77
|
-
return `${text.slice(0, Math.max(0, max - ellipsis.length))}${ellipsis}`;
|
|
78
|
-
}
|
|
79
69
|
//#endregion
|
|
80
70
|
//#region src/google/clientSecret.ts
|
|
81
71
|
var isNonEmptyString = (value) => typeof value === "string" && value !== "";
|