@mulmoclaude/core 1.13.0 → 1.14.1
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 +12 -1
- package/dist/atomic-C_7YpMiM.cjs.map +1 -1
- package/dist/atomic-DPpdrJzO.js.map +1 -1
- package/dist/{calendarGrid-CrGxeCOG.js → calendarGrid-Csy2rpjp.js} +65 -36
- package/dist/{calendarGrid-CrGxeCOG.js.map → calendarGrid-Csy2rpjp.js.map} +1 -1
- package/dist/{calendarGrid-CaS9er8i.cjs → calendarGrid-p8K-7cuB.cjs} +65 -36
- package/dist/{calendarGrid-CaS9er8i.cjs.map → calendarGrid-p8K-7cuB.cjs.map} +1 -1
- package/dist/collection/core/actionVisible.d.ts +3 -3
- package/dist/collection/core/completion.d.ts +5 -5
- package/dist/collection/core/schemaZ.d.ts +4 -4
- package/dist/collection/index.cjs +7 -10
- package/dist/collection/index.cjs.map +1 -1
- package/dist/collection/index.js +7 -10
- package/dist/collection/index.js.map +1 -1
- package/dist/collection/registry/registryIndex.d.ts +1 -1
- package/dist/collection/registry/server/index.cjs +15 -13
- package/dist/collection/registry/server/index.cjs.map +1 -1
- package/dist/collection/registry/server/index.js +15 -13
- package/dist/collection/registry/server/index.js.map +1 -1
- package/dist/collection/server/csvQuery.d.ts +7 -2
- package/dist/collection/server/csvStore.d.ts +2 -1
- package/dist/collection/server/discovery.d.ts +2 -2
- package/dist/collection/server/index.cjs +3 -3
- package/dist/collection/server/index.js +3 -3
- package/dist/collection/server/io.d.ts +3 -3
- package/dist/collection/server/manageTool.d.ts +3 -3
- package/dist/collection/server/store.d.ts +3 -3
- package/dist/collection/server/validate.d.ts +1 -1
- package/dist/collection-watchers/index.cjs +10 -25
- package/dist/collection-watchers/index.cjs.map +1 -1
- package/dist/collection-watchers/index.js +10 -25
- package/dist/collection-watchers/index.js.map +1 -1
- package/dist/{discovery-D-ZJ6a6z.js → discovery-BSedPW8A.js} +64 -36
- package/dist/discovery-BSedPW8A.js.map +1 -0
- package/dist/{discovery-DAoMnBYM.cjs → discovery-DSLgRQhN.cjs} +64 -36
- package/dist/discovery-DSLgRQhN.cjs.map +1 -0
- package/dist/{dist-21pMsI1a.cjs → dist-CsgSfWwR.cjs} +22 -1
- package/dist/{dist-21pMsI1a.cjs.map → dist-CsgSfWwR.cjs.map} +1 -1
- package/dist/{dist-DP1FTGnO.js → dist-D8zokgGo.js} +11 -2
- package/dist/{dist-DP1FTGnO.js.map → dist-D8zokgGo.js.map} +1 -1
- package/dist/feeds/index.cjs +2 -2
- package/dist/feeds/index.js +2 -2
- package/dist/feeds/server/host.d.ts +2 -2
- package/dist/feeds/server/index.cjs +35 -29
- package/dist/feeds/server/index.cjs.map +1 -1
- package/dist/feeds/server/index.js +35 -29
- package/dist/feeds/server/index.js.map +1 -1
- package/dist/feeds/server/refreshResult.d.ts +2 -2
- package/dist/files/index.cjs +3 -2
- package/dist/files/index.cjs.map +1 -1
- package/dist/files/index.js +3 -2
- package/dist/files/index.js.map +1 -1
- package/dist/google/calendar.d.ts +47 -33
- package/dist/google/collectionPush.d.ts +13 -8
- package/dist/google/collectionSync.d.ts +2 -2
- package/dist/google/index.cjs +63 -38
- package/dist/google/index.cjs.map +1 -1
- package/dist/google/index.d.ts +2 -2
- package/dist/google/index.js +61 -38
- package/dist/google/index.js.map +1 -1
- package/dist/{graph-DjE1Az1C.cjs → graph-BZJxiv1E.cjs} +10 -12
- package/dist/graph-BZJxiv1E.cjs.map +1 -0
- package/dist/{graph-BcwqiSpR.js → graph-Ce7xsDTt.js} +10 -12
- package/dist/graph-Ce7xsDTt.js.map +1 -0
- package/dist/{ingestTypes-D1GQdG8e.cjs → ingestTypes-CPR-h-OZ.cjs} +2 -2
- package/dist/{ingestTypes-D1GQdG8e.cjs.map → ingestTypes-CPR-h-OZ.cjs.map} +1 -1
- package/dist/{ingestTypes-XReG-li7.js → ingestTypes-F2IJhOLJ.js} +2 -2
- package/dist/{ingestTypes-XReG-li7.js.map → ingestTypes-F2IJhOLJ.js.map} +1 -1
- package/dist/notifier/index.cjs +1 -1
- package/dist/notifier/index.js +1 -1
- package/dist/notifier/types.d.ts +4 -4
- package/dist/{notifier-C3IhYxqh.cjs → notifier-CKFVVwDP.cjs} +36 -25
- package/dist/notifier-CKFVVwDP.cjs.map +1 -0
- package/dist/{notifier-CMFlw74b.js → notifier-CQD8NljP.js} +36 -25
- package/dist/notifier-CQD8NljP.js.map +1 -0
- package/dist/plugin-vue/index.cjs +4 -2
- package/dist/plugin-vue/index.cjs.map +1 -1
- package/dist/plugin-vue/index.js +4 -2
- package/dist/plugin-vue/index.js.map +1 -1
- package/dist/{project-B9NSyr6L.js → project-B0j_Vf_Q.js} +7 -2
- package/dist/project-B0j_Vf_Q.js.map +1 -0
- package/dist/{project-CCtT_fQr.cjs → project-C1ep9pvo.cjs} +7 -2
- package/dist/project-C1ep9pvo.cjs.map +1 -0
- package/dist/{promptSafety-FD8cQn3e.cjs → promptSafety-DUZ5BiW5.cjs} +106 -88
- package/dist/promptSafety-DUZ5BiW5.cjs.map +1 -0
- package/dist/{promptSafety-C--op_6a.js → promptSafety-DgHMF4Pz.js} +106 -88
- package/dist/promptSafety-DgHMF4Pz.js.map +1 -0
- package/dist/remote-host/index.cjs +2 -1
- package/dist/remote-host/index.d.ts +10 -0
- package/dist/remote-host/index.js +2 -2
- package/dist/remote-host/server/hostRunner.d.ts +7 -2
- package/dist/remote-host/server/index.cjs +14 -11
- package/dist/remote-host/server/index.cjs.map +1 -1
- package/dist/remote-host/server/index.js +13 -10
- package/dist/remote-host/server/index.js.map +1 -1
- package/dist/{remote-host-DkDVxNim.cjs → remote-host-CtQjagPt.cjs} +53 -1
- package/dist/remote-host-CtQjagPt.cjs.map +1 -0
- package/dist/remote-host-Dba4lF3Z.js +82 -0
- package/dist/remote-host-Dba4lF3Z.js.map +1 -0
- package/dist/remote-view/index.cjs +6 -4
- package/dist/remote-view/index.cjs.map +1 -1
- package/dist/remote-view/index.d.ts +1 -1
- package/dist/remote-view/index.js +6 -4
- package/dist/remote-view/index.js.map +1 -1
- package/dist/scheduler/adapter.d.ts +4 -4
- package/dist/scheduler/index.cjs +35 -3
- package/dist/scheduler/index.cjs.map +1 -1
- package/dist/scheduler/index.js +35 -3
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/task-manager.d.ts +2 -2
- package/dist/{server-B_b0bLSW.cjs → server-1c6gB372.cjs} +54 -31
- package/dist/server-1c6gB372.cjs.map +1 -0
- package/dist/{server-Ci_A3chZ.js → server-Dv_L7l0y.js} +54 -31
- package/dist/server-Dv_L7l0y.js.map +1 -0
- package/dist/skill-bridge/index.cjs +3 -4
- package/dist/skill-bridge/index.cjs.map +1 -1
- package/dist/skill-bridge/index.js +3 -4
- package/dist/skill-bridge/index.js.map +1 -1
- package/dist/types-BKVZrtyc.js.map +1 -1
- package/dist/types-CNqkLT4M.cjs.map +1 -1
- package/dist/utils/fetch.cjs.map +1 -1
- package/dist/utils/fetch.d.ts +1 -1
- package/dist/utils/fetch.js.map +1 -1
- package/dist/utils/index.cjs +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/whisper/client.cjs.map +1 -1
- package/dist/whisper/client.d.ts +3 -3
- package/dist/whisper/client.js.map +1 -1
- package/dist/whisper/index.cjs +2 -6
- package/dist/whisper/index.cjs.map +1 -1
- package/dist/whisper/index.js +2 -6
- package/dist/whisper/index.js.map +1 -1
- package/dist/wiki/index.cjs +4 -4
- package/dist/wiki/index.cjs.map +1 -1
- package/dist/wiki/index.js +4 -4
- package/dist/wiki/index.js.map +1 -1
- package/dist/wiki/server/index.cjs +15 -12
- package/dist/wiki/server/index.cjs.map +1 -1
- package/dist/wiki/server/index.js +15 -12
- package/dist/wiki/server/index.js.map +1 -1
- package/package.json +6 -6
- package/dist/discovery-D-ZJ6a6z.js.map +0 -1
- package/dist/discovery-DAoMnBYM.cjs.map +0 -1
- package/dist/graph-BcwqiSpR.js.map +0 -1
- package/dist/graph-DjE1Az1C.cjs.map +0 -1
- package/dist/notifier-C3IhYxqh.cjs.map +0 -1
- package/dist/notifier-CMFlw74b.js.map +0 -1
- package/dist/project-B9NSyr6L.js.map +0 -1
- package/dist/project-CCtT_fQr.cjs.map +0 -1
- package/dist/promptSafety-C--op_6a.js.map +0 -1
- package/dist/promptSafety-FD8cQn3e.cjs.map +0 -1
- package/dist/remote-host-D_BRFHcI.js +0 -36
- package/dist/remote-host-D_BRFHcI.js.map +0 -1
- package/dist/remote-host-DkDVxNim.cjs.map +0 -1
- package/dist/server-B_b0bLSW.cjs.map +0 -1
- package/dist/server-Ci_A3chZ.js.map +0 -1
package/dist/files/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/files/json.ts","../../src/files/safe.ts","../../src/files/byPath.ts","../../src/files/htmlFileRequest.ts"],"sourcesContent":["import { writeFileAtomic, type WriteAtomicOptions } from \"./atomic.js\";\n\nconst JSON_INDENT = 2;\n\n/** Atomic JSON write (2-space indent) — serialize then write through the atomic\n * tmp-file + rename seam so a reader never sees a half-written document. */\nexport async function writeJsonAtomic(filePath: string, data: unknown, opts: WriteAtomicOptions = {}): Promise<void> {\n await writeFileAtomic(filePath, JSON.stringify(data, null, JSON_INDENT), opts);\n}\n","import { realpathSync } from \"node:fs\";\nimport path from \"node:path\";\n\n/** True for a `not found` filesystem error (ENOENT) — lets callers treat a\n * missing file as an empty/default result instead of a thrown error. */\nexport function isEnoent(err: unknown): boolean {\n return typeof err === \"object\" && err !== null && \"code\" in err && err.code === \"ENOENT\";\n}\n\n/** Realpath-based read-time containment: resolve `relPath` against the root's\n * realpath and require the target's realpath to stay inside it. Returns null\n * on ENOENT or traversal (symlink escapes included). `rootReal` MUST already\n * be a realpath. The one implementation for host and plugins (#2461) — this\n * security-critical primitive must not drift per consumer. */\nexport function resolveWithinRoot(rootReal: string, relPath: string): string | null {\n const normalized = path.normalize(relPath || \"\");\n const resolved = path.resolve(rootReal, normalized);\n const resolvedReal = realpathOrNull(resolved);\n if (resolvedReal === null) return null;\n if (resolvedReal !== rootReal && !resolvedReal.startsWith(rootReal + path.sep)) {\n return null;\n }\n return resolvedReal;\n}\n\nfunction realpathOrNull(absPath: string): string | null {\n try {\n return realpathSync(absPath);\n } catch {\n return null;\n }\n}\n","// Read / overwrite files the TOOL CALL named, rather than files the app minted.\n// `presentDocument(path)` / `presentHtml(path)` open a document that already\n// exists — a repo's `README.md`, `docs/report.html`, an absolute path — and the\n// user's edits in the view overwrite it in place.\n//\n// Shared by both hosts (MulmoClaude's `server/utils/files/by-path.ts` and\n// MulmoTerminal's backend bind their own workspace root to it), because the\n// judgement of what a `path` argument may reach is exactly the thing that must\n// not drift between them: a host that accepts what the other refuses turns one\n// tool call into two different behaviours.\n//\n// The rules:\n// - `classifyFilePath` (also what the plugins' `path` gate calls) decides the\n// shape: right extension, no NUL, no `.` / `..` / empty segment. That\n// lexical guard is what stops a vetted path from being re-pointed later.\n// - relative paths resolve against the injected root; absolute paths are taken\n// as given. There is deliberately NO containment check — opening a file\n// outside the workspace is the documented purpose of the `path` form. The\n// agent can already read and write those files directly; what is new is that\n// the view can too.\n// - a value that is absolute only under ANOTHER platform's rules is refused:\n// `classifyFilePath` recognises `C:\\proj\\x.md` everywhere (the value may\n// come from a remote host), but on POSIX `path.resolve(\"C:/proj/x.md\")`\n// lands under the process cwd — a file nobody named.\n// - reads and writes require a REGULAR FILE, judged through `realpath` so a\n// symlink is assessed by what it points at and a directory named `x.md`\n// cannot masquerade as a document.\n// - `write` overwrites only. Neither tool creates a file at a caller-supplied\n// path, so a write to a path that does not exist means the view is stale or\n// the path was wrong — refusing keeps a typo from scattering files.\n// - `readDir` and `unlink` are refused outright: this capability exists to\n// read and re-save ONE named document, not to browse or delete.\n\nimport { readFile, realpath, stat as fsStat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { classifyFilePath } from \"../artifacts/paths.js\";\nimport { writeFileAtomic } from \"./atomic.js\";\n\nexport const MARKDOWN_EXTENSIONS = [\".md\"] as const;\nexport const HTML_EXTENSIONS = [\".html\", \".htm\"] as const;\n\n/** Minimal structural echo of gui-chat-protocol's `FileOps`. Declared here\n * rather than imported so core keeps no dependency on the protocol package;\n * a host assigns the result straight to a `FileOps`-typed capability. */\nexport interface ByPathFileOps {\n read: (rel: string) => Promise<string>;\n readBytes: (rel: string) => Promise<Uint8Array>;\n write: (rel: string, content: string | Uint8Array) => Promise<void>;\n readDir: (rel: string) => Promise<string[]>;\n stat: (rel: string) => Promise<{ mtimeMs: number; size: number }>;\n exists: (rel: string) => Promise<boolean>;\n unlink: (rel: string) => Promise<void>;\n}\n\n/** Absolute on-disk path for a caller-supplied path with one of `extensions`,\n * or null when the value is not usable on this platform. */\nexport function resolveByPath(root: string, value: string, extensions: readonly string[]): string | null {\n const kind = classifyFilePath(value, extensions);\n if (kind === null) return null;\n if (kind === \"relative\") return path.resolve(root, value);\n return path.isAbsolute(value) ? path.resolve(value) : null;\n}\n\n/** True when the path names an existing regular file. */\nexport async function existsAsFile(root: string, value: string, extensions: readonly string[]): Promise<boolean> {\n const absPath = resolveByPath(root, value, extensions);\n return absPath === null ? false : (await regularFileTarget(absPath)) !== null;\n}\n\nexport interface ByPathOptions {\n /** The root relative values resolve against, read PER CALL — hosts inject the\n * workspace after these ops are already bound into plugin closures. */\n rootFor: () => string;\n extensions: readonly string[];\n}\n\nfunction resolveOrThrow({ rootFor, extensions }: ByPathOptions, value: string): string {\n const absPath = resolveByPath(rootFor(), value, extensions);\n if (absPath === null) throw new Error(`invalid path: ${value}`);\n return absPath;\n}\n\n/** The canonical REGULAR FILE a path names, or null when it is missing, is a\n * directory, or is anything else (a FIFO would block a read forever).\n * Resolved through `realpath` so a symlink is judged — and later written — by\n * what it points at: `writeFileAtomic` renames a temp file into place, which\n * through a link would replace the link itself and leave the real document\n * untouched. */\nasync function regularFileTarget(absPath: string): Promise<string | null> {\n try {\n const target = await realpath(absPath);\n return (await fsStat(target)).isFile() ? target : null;\n } catch {\n return null;\n }\n}\n\n/** The file to read or overwrite. Throws rather than creating: neither tool\n * ever writes to a path that does not already hold a document. */\nasync function existingFileFor(options: ByPathOptions, rel: string): Promise<string> {\n const target = await regularFileTarget(resolveOrThrow(options, rel));\n if (target === null) throw new Error(`no file exists at ${rel}`);\n return target;\n}\n\n/** Browsing and deleting are NOT part of this capability: it exists so a view\n * can read and re-save the ONE document the tool call named, not to enumerate\n * a directory it was never pointed at or remove a file. */\nfunction unsupported(operation: string): () => Promise<never> {\n return () => Promise.reject(new Error(`byPath FileOps does not support ${operation}`));\n}\n\n/** FileOps over caller-supplied paths — what a host injects as `files.byPath`\n * for plugins whose `path` argument may leave their artifact directory. Every\n * method takes the same value the tool call carried, not a scope-relative one. */\nexport function createByPathFileOps(options: ByPathOptions): ByPathFileOps {\n return {\n read: async (rel) => readFile(await existingFileFor(options, rel), \"utf-8\"),\n readBytes: async (rel) => {\n const buf = await readFile(await existingFileFor(options, rel));\n return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);\n },\n write: async (rel, content) => writeFileAtomic(await existingFileFor(options, rel), content),\n readDir: unsupported(\"readDir\"),\n stat: async (rel) => {\n const { mtimeMs, size } = await fsStat(resolveOrThrow(options, rel));\n return { mtimeMs, size };\n },\n exists: (rel) => existsAsFile(options.rootFor(), rel, options.extensions),\n unlink: unsupported(\"unlink\"),\n };\n}\n","// Server half of the `/htmlfile` scheme (client half: `src/config/htmlFileUrl.ts`).\n// Turns a request path into the absolute file to serve, or null.\n//\n// Shared by both hosts so the URL the View asks for and the file a host resolves\n// cannot drift apart.\n//\n// This mount is deliberately NOT contained to a root: presentHtml's `path` form\n// may name any HTML page on disk, and serving it is the point. What still holds:\n//\n// - only `ws` (workspace-relative) and `abs` (absolute) scopes exist; anything\n// else 404s, so a bare `/htmlfile/etc/passwd` is not a path at all.\n// - no `.` / `..` / empty segment survives, so a vetted URL cannot be\n// re-pointed by traversal, and no dotfile segment is reachable (same policy\n// as `resolveArtifactRequestPath`, which the artifact mounts share).\n// - extension allowlist, realpath and regular-file checks stay with the host's\n// mount, which is also where the CSP header and rate limit are applied.\n//\n// The trust boundary is whatever the host applies to its `/artifacts/html`\n// mount — in MulmoClaude, the loopback-only listener plus `requireSameOrigin`\n// (bearer auth does not apply outside `/api`, and an iframe `src` request cannot\n// carry an Authorization header anyway).\n\nimport path from \"node:path\";\n// The scope constants are re-declared rather than imported: core must not\n// depend on a plugin (dependencies flow the other way). `html-plugin`'s\n// `htmlFileUrl` builds the URLs these parse, and the round-trip test in the host\n// is what keeps the two halves honest.\nconst HTML_FILE_SCOPE_WORKSPACE = \"ws\";\nconst HTML_FILE_SCOPE_ABSOLUTE = \"abs\";\n\nconst WINDOWS_DRIVE_ONLY_RE = /^[a-zA-Z]:$/;\n\nfunction decodeSegments(reqPath: string): string[] | null {\n let decoded: string[];\n try {\n decoded = reqPath\n .replace(/^\\//, \"\")\n .split(\"/\")\n .map((segment) => decodeURIComponent(segment));\n } catch {\n // Malformed escape (`%ZZ`) — fail closed rather than bubbling a URIError.\n return null;\n }\n // Segmentation happens on the ENCODED path, so a `%2F` (or `%5C`) becomes a\n // separator only after this decode — `a%2F..%2F..%2Ftmp%2Fx.html` would arrive\n // as one segment that the `..` check below never sees, and then split apart\n // inside `path.resolve`. A decoded segment that still contains a separator is\n // never legitimate here, so refuse it.\n return decoded.some((segment) => segment.includes(\"/\") || segment.includes(\"\\\\\")) ? null : decoded;\n}\n\n/** Absolute path for a `/htmlfile/<scope>/<segments…>` request, or null when\n * the URL is malformed, uses an unknown scope, or touches a `.` / `..` /\n * dotfile segment. `workspaceRoot` should already be a realpath. */\nexport function resolveHtmlFileRequestPath(workspaceRoot: string, reqPath: string): string | null {\n const decoded = decodeSegments(reqPath);\n if (decoded === null || decoded.length < 2) return null;\n const [scope, ...rest] = decoded;\n if (scope !== HTML_FILE_SCOPE_WORKSPACE && scope !== HTML_FILE_SCOPE_ABSOLUTE) return null;\n if (rest.length === 0) return null;\n if (rest.some((segment) => segment === \"\" || segment === \".\" || segment === \"..\" || segment.startsWith(\".\") || segment.includes(\"\\0\"))) {\n return null;\n }\n if (scope === HTML_FILE_SCOPE_WORKSPACE) return path.resolve(workspaceRoot, ...rest);\n // `abs/C:/proj/page.html` — the drive letter arrives as its own segment, so\n // rejoin it before resolving; everything else is rooted at `/`.\n const candidate = WINDOWS_DRIVE_ONLY_RE.test(rest[0]) ? rest.join(\"/\") : `/${rest.join(\"/\")}`;\n // Only THIS platform's `path` can say whether that is really absolute: on\n // POSIX, `path.resolve(\"C:/proj/page.html\")` would land under the process cwd\n // rather than at any drive, silently serving the wrong file. Refuse instead.\n return path.isAbsolute(candidate) ? path.resolve(candidate) : null;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/files/json.ts","../../src/files/safe.ts","../../src/files/byPath.ts","../../src/files/htmlFileRequest.ts"],"sourcesContent":["import { writeFileAtomic, type WriteAtomicOptions } from \"./atomic.js\";\n\nconst JSON_INDENT = 2;\n\n/** Atomic JSON write (2-space indent) — serialize then write through the atomic\n * tmp-file + rename seam so a reader never sees a half-written document. */\nexport async function writeJsonAtomic(filePath: string, data: unknown, opts: WriteAtomicOptions = {}): Promise<void> {\n await writeFileAtomic(filePath, JSON.stringify(data, null, JSON_INDENT), opts);\n}\n","import { realpathSync } from \"node:fs\";\nimport path from \"node:path\";\n\n/** True for a `not found` filesystem error (ENOENT) — lets callers treat a\n * missing file as an empty/default result instead of a thrown error. */\nexport function isEnoent(err: unknown): boolean {\n return typeof err === \"object\" && err !== null && \"code\" in err && err.code === \"ENOENT\";\n}\n\n/** Realpath-based read-time containment: resolve `relPath` against the root's\n * realpath and require the target's realpath to stay inside it. Returns null\n * on ENOENT or traversal (symlink escapes included). `rootReal` MUST already\n * be a realpath. The one implementation for host and plugins (#2461) — this\n * security-critical primitive must not drift per consumer. */\nexport function resolveWithinRoot(rootReal: string, relPath: string): string | null {\n const normalized = path.normalize(relPath || \"\");\n const resolved = path.resolve(rootReal, normalized);\n const resolvedReal = realpathOrNull(resolved);\n if (resolvedReal === null) return null;\n if (resolvedReal !== rootReal && !resolvedReal.startsWith(rootReal + path.sep)) {\n return null;\n }\n return resolvedReal;\n}\n\nfunction realpathOrNull(absPath: string): string | null {\n try {\n return realpathSync(absPath);\n } catch {\n return null;\n }\n}\n","// Read / overwrite files the TOOL CALL named, rather than files the app minted.\n// `presentDocument(path)` / `presentHtml(path)` open a document that already\n// exists — a repo's `README.md`, `docs/report.html`, an absolute path — and the\n// user's edits in the view overwrite it in place.\n//\n// Shared by both hosts (MulmoClaude's `server/utils/files/by-path.ts` and\n// MulmoTerminal's backend bind their own workspace root to it), because the\n// judgement of what a `path` argument may reach is exactly the thing that must\n// not drift between them: a host that accepts what the other refuses turns one\n// tool call into two different behaviours.\n//\n// The rules:\n// - `classifyFilePath` (also what the plugins' `path` gate calls) decides the\n// shape: right extension, no NUL, no `.` / `..` / empty segment. That\n// lexical guard is what stops a vetted path from being re-pointed later.\n// - relative paths resolve against the injected root; absolute paths are taken\n// as given. There is deliberately NO containment check — opening a file\n// outside the workspace is the documented purpose of the `path` form. The\n// agent can already read and write those files directly; what is new is that\n// the view can too.\n// - a value that is absolute only under ANOTHER platform's rules is refused:\n// `classifyFilePath` recognises `C:\\proj\\x.md` everywhere (the value may\n// come from a remote host), but on POSIX `path.resolve(\"C:/proj/x.md\")`\n// lands under the process cwd — a file nobody named.\n// - reads and writes require a REGULAR FILE, judged through `realpath` so a\n// symlink is assessed by what it points at and a directory named `x.md`\n// cannot masquerade as a document.\n// - `write` overwrites only. Neither tool creates a file at a caller-supplied\n// path, so a write to a path that does not exist means the view is stale or\n// the path was wrong — refusing keeps a typo from scattering files.\n// - `readDir` and `unlink` are refused outright: this capability exists to\n// read and re-save ONE named document, not to browse or delete.\n\nimport { readFile, realpath, stat as fsStat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { classifyFilePath } from \"../artifacts/paths.js\";\nimport { writeFileAtomic } from \"./atomic.js\";\n\nexport const MARKDOWN_EXTENSIONS = [\".md\"] as const;\nexport const HTML_EXTENSIONS = [\".html\", \".htm\"] as const;\n\n/** Minimal structural echo of gui-chat-protocol's `FileOps`. Declared here\n * rather than imported so core keeps no dependency on the protocol package;\n * a host assigns the result straight to a `FileOps`-typed capability. */\nexport interface ByPathFileOps {\n read: (rel: string) => Promise<string>;\n readBytes: (rel: string) => Promise<Uint8Array>;\n write: (rel: string, content: string | Uint8Array) => Promise<void>;\n readDir: (rel: string) => Promise<string[]>;\n stat: (rel: string) => Promise<{ mtimeMs: number; size: number }>;\n exists: (rel: string) => Promise<boolean>;\n unlink: (rel: string) => Promise<void>;\n}\n\n/** Absolute on-disk path for a caller-supplied path with one of `extensions`,\n * or null when the value is not usable on this platform. */\nexport function resolveByPath(root: string, value: string, extensions: readonly string[]): string | null {\n const kind = classifyFilePath(value, extensions);\n if (kind === null) return null;\n if (kind === \"relative\") return path.resolve(root, value);\n return path.isAbsolute(value) ? path.resolve(value) : null;\n}\n\n/** True when the path names an existing regular file. */\nexport async function existsAsFile(root: string, value: string, extensions: readonly string[]): Promise<boolean> {\n const absPath = resolveByPath(root, value, extensions);\n return absPath === null ? false : (await regularFileTarget(absPath)) !== null;\n}\n\nexport interface ByPathOptions {\n /** The root relative values resolve against, read PER CALL — hosts inject the\n * workspace after these ops are already bound into plugin closures. */\n rootFor: () => string;\n extensions: readonly string[];\n}\n\nfunction resolveOrThrow({ rootFor, extensions }: ByPathOptions, value: string): string {\n const absPath = resolveByPath(rootFor(), value, extensions);\n if (absPath === null) throw new Error(`invalid path: ${value}`);\n return absPath;\n}\n\n/** The canonical REGULAR FILE a path names, or null when it is missing, is a\n * directory, or is anything else (a FIFO would block a read forever).\n * Resolved through `realpath` so a symlink is judged — and later written — by\n * what it points at: `writeFileAtomic` renames a temp file into place, which\n * through a link would replace the link itself and leave the real document\n * untouched. */\nasync function regularFileTarget(absPath: string): Promise<string | null> {\n try {\n const target = await realpath(absPath);\n return (await fsStat(target)).isFile() ? target : null;\n } catch {\n return null;\n }\n}\n\n/** The file to read or overwrite. Throws rather than creating: neither tool\n * ever writes to a path that does not already hold a document. */\nasync function existingFileFor(options: ByPathOptions, rel: string): Promise<string> {\n const target = await regularFileTarget(resolveOrThrow(options, rel));\n if (target === null) throw new Error(`no file exists at ${rel}`);\n return target;\n}\n\n/** Browsing and deleting are NOT part of this capability: it exists so a view\n * can read and re-save the ONE document the tool call named, not to enumerate\n * a directory it was never pointed at or remove a file. */\nfunction unsupported(operation: string): () => Promise<never> {\n return () => Promise.reject(new Error(`byPath FileOps does not support ${operation}`));\n}\n\n/** FileOps over caller-supplied paths — what a host injects as `files.byPath`\n * for plugins whose `path` argument may leave their artifact directory. Every\n * method takes the same value the tool call carried, not a scope-relative one. */\nexport function createByPathFileOps(options: ByPathOptions): ByPathFileOps {\n return {\n read: async (rel) => readFile(await existingFileFor(options, rel), \"utf-8\"),\n readBytes: async (rel) => {\n const buf = await readFile(await existingFileFor(options, rel));\n return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);\n },\n write: async (rel, content) => writeFileAtomic(await existingFileFor(options, rel), content),\n readDir: unsupported(\"readDir\"),\n stat: async (rel) => {\n const { mtimeMs, size } = await fsStat(resolveOrThrow(options, rel));\n return { mtimeMs, size };\n },\n exists: (rel) => existsAsFile(options.rootFor(), rel, options.extensions),\n unlink: unsupported(\"unlink\"),\n };\n}\n","// Server half of the `/htmlfile` scheme (client half: `src/config/htmlFileUrl.ts`).\n// Turns a request path into the absolute file to serve, or null.\n//\n// Shared by both hosts so the URL the View asks for and the file a host resolves\n// cannot drift apart.\n//\n// This mount is deliberately NOT contained to a root: presentHtml's `path` form\n// may name any HTML page on disk, and serving it is the point. What still holds:\n//\n// - only `ws` (workspace-relative) and `abs` (absolute) scopes exist; anything\n// else 404s, so a bare `/htmlfile/etc/passwd` is not a path at all.\n// - no `.` / `..` / empty segment survives, so a vetted URL cannot be\n// re-pointed by traversal, and no dotfile segment is reachable (same policy\n// as `resolveArtifactRequestPath`, which the artifact mounts share).\n// - extension allowlist, realpath and regular-file checks stay with the host's\n// mount, which is also where the CSP header and rate limit are applied.\n//\n// The trust boundary is whatever the host applies to its `/artifacts/html`\n// mount — in MulmoClaude, the loopback-only listener plus `requireSameOrigin`\n// (bearer auth does not apply outside `/api`, and an iframe `src` request cannot\n// carry an Authorization header anyway).\n\nimport path from \"node:path\";\n// The scope constants are re-declared rather than imported: core must not\n// depend on a plugin (dependencies flow the other way). `html-plugin`'s\n// `htmlFileUrl` builds the URLs these parse, and the round-trip test in the host\n// is what keeps the two halves honest.\nconst HTML_FILE_SCOPE_WORKSPACE = \"ws\";\nconst HTML_FILE_SCOPE_ABSOLUTE = \"abs\";\n\nconst WINDOWS_DRIVE_ONLY_RE = /^[a-zA-Z]:$/;\n\nfunction decodeSegments(reqPath: string): string[] | null {\n let decoded: string[];\n try {\n decoded = reqPath\n .replace(/^\\//, \"\")\n .split(\"/\")\n .map((segment) => decodeURIComponent(segment));\n } catch {\n // Malformed escape (`%ZZ`) — fail closed rather than bubbling a URIError.\n return null;\n }\n // Segmentation happens on the ENCODED path, so a `%2F` (or `%5C`) becomes a\n // separator only after this decode — `a%2F..%2F..%2Ftmp%2Fx.html` would arrive\n // as one segment that the `..` check below never sees, and then split apart\n // inside `path.resolve`. A decoded segment that still contains a separator is\n // never legitimate here, so refuse it.\n return decoded.some((segment) => segment.includes(\"/\") || segment.includes(\"\\\\\")) ? null : decoded;\n}\n\n/** Absolute path for a `/htmlfile/<scope>/<segments…>` request, or null when\n * the URL is malformed, uses an unknown scope, or touches a `.` / `..` /\n * dotfile segment. `workspaceRoot` should already be a realpath. */\nexport function resolveHtmlFileRequestPath(workspaceRoot: string, reqPath: string): string | null {\n const decoded = decodeSegments(reqPath);\n if (decoded === null || decoded.length < 2) return null;\n const [scope, ...rest] = decoded;\n if (scope !== HTML_FILE_SCOPE_WORKSPACE && scope !== HTML_FILE_SCOPE_ABSOLUTE) return null;\n const [firstRest] = rest;\n if (firstRest === undefined) return null;\n if (rest.some((segment) => segment === \"\" || segment === \".\" || segment === \"..\" || segment.startsWith(\".\") || segment.includes(\"\\0\"))) {\n return null;\n }\n if (scope === HTML_FILE_SCOPE_WORKSPACE) return path.resolve(workspaceRoot, ...rest);\n // `abs/C:/proj/page.html` — the drive letter arrives as its own segment, so\n // rejoin it before resolving; everything else is rooted at `/`.\n const candidate = WINDOWS_DRIVE_ONLY_RE.test(firstRest) ? rest.join(\"/\") : `/${rest.join(\"/\")}`;\n // Only THIS platform's `path` can say whether that is really absolute: on\n // POSIX, `path.resolve(\"C:/proj/page.html\")` would land under the process cwd\n // rather than at any drive, silently serving the wrong file. Refuse instead.\n return path.isAbsolute(candidate) ? path.resolve(candidate) : null;\n}\n"],"mappings":"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"}
|
|
@@ -39,47 +39,47 @@ export type CalendarEventSpan = {
|
|
|
39
39
|
};
|
|
40
40
|
export type CalendarEventInput = {
|
|
41
41
|
summary: string;
|
|
42
|
-
description?: string;
|
|
43
|
-
location?: string;
|
|
42
|
+
description?: string | undefined;
|
|
43
|
+
location?: string | undefined;
|
|
44
44
|
/** Calendar to create the event on; defaults to the user's primary. */
|
|
45
|
-
calendarId?: string;
|
|
45
|
+
calendarId?: string | undefined;
|
|
46
46
|
/** Event colour (Google event palette id "1".."11"); omit to inherit the calendar's colour. */
|
|
47
|
-
colorId?: string;
|
|
47
|
+
colorId?: string | undefined;
|
|
48
48
|
/** Caller-chosen event id. Google requires base32hex (`0-9a-v`), 5-1024 chars,
|
|
49
49
|
* and answers 409 when it is already taken. A collection push sets this so a
|
|
50
50
|
* locally-created record keeps its own record id as the event id, instead of
|
|
51
51
|
* being re-keyed to Google's after the fact — a re-key that is missed leaves
|
|
52
52
|
* a duplicate record on the next pull. */
|
|
53
|
-
eventId?: string;
|
|
53
|
+
eventId?: string | undefined;
|
|
54
54
|
} & CalendarEventSpan;
|
|
55
55
|
export interface UpdateCalendarEventInput {
|
|
56
56
|
eventId: string;
|
|
57
|
-
summary?: string;
|
|
58
|
-
startDateTime?: string;
|
|
59
|
-
endDateTime?: string;
|
|
57
|
+
summary?: string | undefined;
|
|
58
|
+
startDateTime?: string | undefined;
|
|
59
|
+
endDateTime?: string | undefined;
|
|
60
60
|
/** Structured span; wins over the flat pair when both are present. */
|
|
61
|
-
start?: CalendarEventTime;
|
|
62
|
-
end?: CalendarEventTime;
|
|
61
|
+
start?: CalendarEventTime | undefined;
|
|
62
|
+
end?: CalendarEventTime | undefined;
|
|
63
63
|
/** `""` clears the description; omit to leave it untouched. */
|
|
64
|
-
description?: string;
|
|
64
|
+
description?: string | undefined;
|
|
65
65
|
/** `""` clears the location; omit to leave it untouched. */
|
|
66
|
-
location?: string;
|
|
67
|
-
calendarId?: string;
|
|
68
|
-
colorId?: string;
|
|
66
|
+
location?: string | undefined;
|
|
67
|
+
calendarId?: string | undefined;
|
|
68
|
+
colorId?: string | undefined;
|
|
69
69
|
/** Etag of the version this edit was computed against. Sent as `If-Match`, so
|
|
70
70
|
* Google answers 412 rather than letting the PATCH clobber a change that
|
|
71
71
|
* landed after the caller read the event. Omit for an unconditional write. */
|
|
72
|
-
ifMatch?: string;
|
|
72
|
+
ifMatch?: string | undefined;
|
|
73
73
|
}
|
|
74
74
|
export interface DeleteCalendarEventInput {
|
|
75
75
|
eventId: string;
|
|
76
|
-
calendarId?: string;
|
|
76
|
+
calendarId?: string | undefined;
|
|
77
77
|
}
|
|
78
78
|
export interface ListEventsInput {
|
|
79
|
-
timeMin?: string;
|
|
80
|
-
maxResults?: number;
|
|
79
|
+
timeMin?: string | undefined;
|
|
80
|
+
maxResults?: number | undefined;
|
|
81
81
|
/** Calendar to read; defaults to the user's primary. */
|
|
82
|
-
calendarId?: string;
|
|
82
|
+
calendarId?: string | undefined;
|
|
83
83
|
}
|
|
84
84
|
export interface CalendarEventSummary {
|
|
85
85
|
id: string;
|
|
@@ -160,18 +160,18 @@ export declare function deleteCalendarEvent(accessToken: string, input: DeleteCa
|
|
|
160
160
|
export declare function listCalendarEvents(accessToken: string, input?: ListEventsInput): Promise<CalendarEventSummary[]>;
|
|
161
161
|
export interface SyncEventsInput {
|
|
162
162
|
/** Calendar to sync; defaults to the user's primary. */
|
|
163
|
-
calendarId?: string;
|
|
163
|
+
calendarId?: string | undefined;
|
|
164
164
|
/** Token from the previous sync. Omit for a full sync. */
|
|
165
|
-
syncToken?: string;
|
|
165
|
+
syncToken?: string | undefined;
|
|
166
166
|
/** Page size for the underlying list calls. */
|
|
167
|
-
maxResults?: number;
|
|
167
|
+
maxResults?: number | undefined;
|
|
168
168
|
}
|
|
169
169
|
export interface CalendarSyncResult {
|
|
170
170
|
/** Changed events since `syncToken` (all of them on a full sync). Deletions
|
|
171
171
|
* arrive here too, as `status: "cancelled"`. */
|
|
172
172
|
events: CalendarEventSummary[];
|
|
173
173
|
/** Token to pass to the NEXT sync. Absent only if Google omitted it. */
|
|
174
|
-
nextSyncToken?: string;
|
|
174
|
+
nextSyncToken?: string | undefined;
|
|
175
175
|
/** The stored token had expired (410) — the caller must drop it and re-sync
|
|
176
176
|
* from scratch; no events are returned in that case. */
|
|
177
177
|
fullResyncRequired: boolean;
|
|
@@ -189,23 +189,37 @@ export interface CalendarSyncResult {
|
|
|
189
189
|
export declare function syncCalendarEvents(accessToken: string, input?: SyncEventsInput): Promise<CalendarSyncResult>;
|
|
190
190
|
export interface CalendarListPage {
|
|
191
191
|
items: unknown[];
|
|
192
|
-
nextPageToken?: string;
|
|
192
|
+
nextPageToken?: string | undefined;
|
|
193
193
|
}
|
|
194
194
|
/** Pagination loop for CalendarList.list, extracted so it can be tested without
|
|
195
195
|
* network. Stops at the last page (no token) or the runaway page cap. */
|
|
196
196
|
export declare function collectCalendarPages(fetchPage: (pageToken?: string) => Promise<CalendarListPage>, maxPages?: number): Promise<CalendarSummary[]>;
|
|
197
|
+
/** What a calendar absent from the user's list can still tell about itself. */
|
|
198
|
+
export interface CalendarMeta {
|
|
199
|
+
/** IANA zone (`Asia/Tokyo`), `""` when Google omits it. */
|
|
200
|
+
timeZone: string;
|
|
201
|
+
/** `owner` / `writer` / `reader` / `freeBusyReader`, `""` when unreported. */
|
|
202
|
+
accessRole: string;
|
|
203
|
+
}
|
|
204
|
+
export declare const toCalendarMeta: (value: unknown) => CalendarMeta;
|
|
205
|
+
/** One calendar's zone and the caller's role on it, read by id rather than
|
|
206
|
+
* looked up in the user's list.
|
|
207
|
+
*
|
|
208
|
+
* `calendarList` only holds calendars the user has ADDED; a calendar shared
|
|
209
|
+
* with them can be readable and writable by id without appearing there, and a
|
|
210
|
+
* push still needs its zone to send an offset-less `dateTime`.
|
|
211
|
+
*
|
|
212
|
+
* Deliberately `events.list` and NOT `calendars.get`: the latter takes only
|
|
213
|
+
* full-calendar scopes (`calendar`, `calendar.readonly`, `calendar.calendars*`)
|
|
214
|
+
* and this app asks for none of them, so it answers 403 for every account it
|
|
215
|
+
* links (#2735). The events list carries the same `timeZone` — plus the
|
|
216
|
+
* `accessRole` the calendar resource does not have — under `calendar.events`,
|
|
217
|
+
* which the push already holds because it writes with it. Only the envelope is
|
|
218
|
+
* read; the one event asked for is the smallest page Google will serve. */
|
|
219
|
+
export declare function getCalendarMeta(accessToken: string, calendarId: string | undefined): Promise<CalendarMeta>;
|
|
197
220
|
/** The calendars the user has added/subscribed to (primary + secondary +
|
|
198
221
|
* shared), each with its id, name and colour, following pagination. Needs the
|
|
199
222
|
* calendar-list read scope (GOOGLE_CALENDARLIST_SCOPE). */
|
|
200
|
-
/** One calendar's own resource, addressed by id rather than looked up in the
|
|
201
|
-
* user's list.
|
|
202
|
-
*
|
|
203
|
-
* `calendarList` only holds calendars the user has ADDED; a calendar shared
|
|
204
|
-
* with them can be readable and writable by id without appearing there. This is
|
|
205
|
-
* how a push learns such a calendar's `timeZone`. Note the resource carries no
|
|
206
|
-
* `accessRole` — that is a calendarList property — so reachability here says
|
|
207
|
-
* nothing about writability. */
|
|
208
|
-
export declare function getCalendar(accessToken: string, calendarId: string | undefined): Promise<CalendarSummary>;
|
|
209
223
|
export declare function listCalendars(accessToken: string): Promise<CalendarSummary[]>;
|
|
210
224
|
/** Resolve a `colorId` (on an event or calendar) to its hex background/foreground. */
|
|
211
225
|
export declare function getCalendarColors(accessToken: string): Promise<CalendarColors>;
|
|
@@ -5,21 +5,26 @@ import { ShadowEvent } from './calendarPushState.js';
|
|
|
5
5
|
import { PushableSourceField } from './pushPlan.js';
|
|
6
6
|
/** What the up-front writability gate knows about the target calendar. */
|
|
7
7
|
export interface CalendarWriteTarget {
|
|
8
|
-
/** The role
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* from it (Codex review). */
|
|
8
|
+
/** The caller's role on the calendar, or null when neither source reported one.
|
|
9
|
+
* Null means unknown, never read-only: hard-denying an unknown role would
|
|
10
|
+
* refuse a calendar the user can in fact write to. */
|
|
12
11
|
accessRole: string | null;
|
|
13
12
|
/** IANA zone for rebuilding a zone-less stored clock, `""` when unreported. */
|
|
14
13
|
timeZone: string;
|
|
15
14
|
}
|
|
16
15
|
/** Whether to refuse the whole push before touching a single event.
|
|
17
16
|
*
|
|
18
|
-
* Only on POSITIVE evidence of a non-writable role.
|
|
19
|
-
* unknown and must fall through: hard-denying it
|
|
20
|
-
* outright for a calendar the user can in fact write
|
|
21
|
-
* Google's own 403 per record if the write really is
|
|
17
|
+
* Only on POSITIVE evidence of a non-writable role. A calendar whose role
|
|
18
|
+
* neither source reported is unknown and must fall through: hard-denying it
|
|
19
|
+
* would block the feature outright for a calendar the user can in fact write
|
|
20
|
+
* to. Those attempts surface Google's own 403 per record if the write really is
|
|
21
|
+
* not allowed. */
|
|
22
22
|
export declare const isDeniedAccessRole: (accessRole: string | null) => boolean;
|
|
23
|
+
/** A role Google left blank is UNKNOWN, not a denial. `isDeniedAccessRole`
|
|
24
|
+
* refuses every role it does not recognise — `""` included — so passing an
|
|
25
|
+
* unreported one straight through would refuse a calendar Google simply said
|
|
26
|
+
* nothing about. */
|
|
27
|
+
export declare const reportedAccessRole: (accessRole: string) => string | null;
|
|
23
28
|
export interface CalendarCollectionPushResult {
|
|
24
29
|
slug: string;
|
|
25
30
|
created: number;
|
|
@@ -194,8 +194,8 @@ export declare function groupByCalendar(collections: readonly LoadedCollection[]
|
|
|
194
194
|
* Structural so the rule can be exercised without building a LoadedCollection. */
|
|
195
195
|
export interface CalendarDeclaring {
|
|
196
196
|
googleCalendar?: {
|
|
197
|
-
calendarId?: string;
|
|
198
|
-
};
|
|
197
|
+
calendarId?: string | undefined;
|
|
198
|
+
} | undefined;
|
|
199
199
|
}
|
|
200
200
|
/** The canonical calendar whose sync token nothing needs any more, or null.
|
|
201
201
|
*
|
package/dist/google/index.cjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_rolldown_runtime = require("../rolldown-runtime-D6vf50IK.cjs");
|
|
3
|
-
const require_dist = require("../dist-
|
|
3
|
+
const require_dist = require("../dist-CsgSfWwR.cjs");
|
|
4
4
|
const require_text = require("../text-CaAh9ZSO.cjs");
|
|
5
5
|
const require_atomic = require("../atomic-C_7YpMiM.cjs");
|
|
6
6
|
const require_utils_fetch = require("../utils/fetch.cjs");
|
|
7
|
-
const require_calendarGrid = require("../calendarGrid-
|
|
8
|
-
const require_project = require("../project-
|
|
9
|
-
const require_discovery = require("../discovery-
|
|
7
|
+
const require_calendarGrid = require("../calendarGrid-p8K-7cuB.cjs");
|
|
8
|
+
const require_project = require("../project-C1ep9pvo.cjs");
|
|
9
|
+
const require_discovery = require("../discovery-DSLgRQhN.cjs");
|
|
10
10
|
let node_fs = require("node:fs");
|
|
11
11
|
let node_path = require("node:path");
|
|
12
12
|
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
@@ -305,7 +305,7 @@ var EXPIRY_MARGIN_MS = ONE_MINUTE_MS;
|
|
|
305
305
|
var createClient = (secret, redirectUri) => new google_auth_library.OAuth2Client({
|
|
306
306
|
clientId: secret.client_id,
|
|
307
307
|
clientSecret: secret.client_secret,
|
|
308
|
-
redirectUri
|
|
308
|
+
...redirectUri !== void 0 ? { redirectUri } : {}
|
|
309
309
|
});
|
|
310
310
|
var persistRotatedTokens = (client, home) => {
|
|
311
311
|
client.on("tokens", (tokens) => {
|
|
@@ -593,6 +593,7 @@ var CALENDAR_LIST_PAGE_SIZE = 250;
|
|
|
593
593
|
var MAX_CALENDAR_LIST_PAGES = 40;
|
|
594
594
|
var EVENT_SYNC_PAGE_SIZE = 2500;
|
|
595
595
|
var MAX_EVENT_SYNC_PAGES = 200;
|
|
596
|
+
var CALENDAR_META_PAGE_SIZE = 1;
|
|
596
597
|
var HTTP_GONE = 410;
|
|
597
598
|
var EVENT_ABSENT_STATUSES = [404, HTTP_GONE];
|
|
598
599
|
/** An `If-Match` write whose etag no longer matches — someone else changed the
|
|
@@ -804,20 +805,34 @@ async function collectCalendarPages(fetchPage, maxPages = MAX_CALENDAR_LIST_PAGE
|
|
|
804
805
|
}
|
|
805
806
|
return calendars;
|
|
806
807
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
808
|
+
var toCalendarMeta = (value) => {
|
|
809
|
+
const record = asRecord(value);
|
|
810
|
+
return {
|
|
811
|
+
timeZone: stringField(record, "timeZone"),
|
|
812
|
+
accessRole: stringField(record, "accessRole")
|
|
813
|
+
};
|
|
814
|
+
};
|
|
815
|
+
/** One calendar's zone and the caller's role on it, read by id rather than
|
|
816
|
+
* looked up in the user's list.
|
|
812
817
|
*
|
|
813
818
|
* `calendarList` only holds calendars the user has ADDED; a calendar shared
|
|
814
|
-
* with them can be readable and writable by id without appearing there
|
|
815
|
-
*
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
|
|
819
|
-
|
|
819
|
+
* with them can be readable and writable by id without appearing there, and a
|
|
820
|
+
* push still needs its zone to send an offset-less `dateTime`.
|
|
821
|
+
*
|
|
822
|
+
* Deliberately `events.list` and NOT `calendars.get`: the latter takes only
|
|
823
|
+
* full-calendar scopes (`calendar`, `calendar.readonly`, `calendar.calendars*`)
|
|
824
|
+
* and this app asks for none of them, so it answers 403 for every account it
|
|
825
|
+
* links (#2735). The events list carries the same `timeZone` — plus the
|
|
826
|
+
* `accessRole` the calendar resource does not have — under `calendar.events`,
|
|
827
|
+
* which the push already holds because it writes with it. Only the envelope is
|
|
828
|
+
* read; the one event asked for is the smallest page Google will serve. */
|
|
829
|
+
async function getCalendarMeta(accessToken, calendarId) {
|
|
830
|
+
const params = new URLSearchParams({ maxResults: String(CALENDAR_META_PAGE_SIZE) });
|
|
831
|
+
return toCalendarMeta(await googleRequest(CALENDAR_API_LABEL, accessToken, `${eventsUrl(calendarId)}?${params.toString()}`));
|
|
820
832
|
}
|
|
833
|
+
/** The calendars the user has added/subscribed to (primary + secondary +
|
|
834
|
+
* shared), each with its id, name and colour, following pagination. Needs the
|
|
835
|
+
* calendar-list read scope (GOOGLE_CALENDARLIST_SCOPE). */
|
|
821
836
|
async function listCalendars(accessToken) {
|
|
822
837
|
return collectCalendarPages(async (pageToken) => {
|
|
823
838
|
const params = new URLSearchParams({ maxResults: String(CALENDAR_LIST_PAGE_SIZE) });
|
|
@@ -868,9 +883,7 @@ async function readLockRaw(lockPath) {
|
|
|
868
883
|
function parseLock(raw) {
|
|
869
884
|
try {
|
|
870
885
|
const parsed = JSON.parse(raw);
|
|
871
|
-
|
|
872
|
-
const { holder } = parsed;
|
|
873
|
-
return typeof holder === "string" ? { holder } : null;
|
|
886
|
+
return require_dist.hasStringProp(parsed, "holder") ? { holder: parsed.holder } : null;
|
|
874
887
|
} catch {
|
|
875
888
|
return null;
|
|
876
889
|
}
|
|
@@ -1369,7 +1382,7 @@ var PUSHABLE_SOURCE_FIELDS = [
|
|
|
1369
1382
|
"description",
|
|
1370
1383
|
"location"
|
|
1371
1384
|
];
|
|
1372
|
-
var isPushableSource = (source) => PUSHABLE_SOURCE_FIELDS.
|
|
1385
|
+
var isPushableSource = (source) => PUSHABLE_SOURCE_FIELDS.some((field) => field === source);
|
|
1373
1386
|
/** The subset of a schema's `map` a push can act on: collection field → event field. */
|
|
1374
1387
|
function pushableMap(map) {
|
|
1375
1388
|
return Object.fromEntries(Object.entries(map).filter((entry) => isPushableSource(entry[1])));
|
|
@@ -1491,21 +1504,28 @@ function locallyDeletedIds(shadow, presentIds) {
|
|
|
1491
1504
|
var WRITABLE_ACCESS_ROLES = ["owner", "writer"];
|
|
1492
1505
|
/** Whether to refuse the whole push before touching a single event.
|
|
1493
1506
|
*
|
|
1494
|
-
* Only on POSITIVE evidence of a non-writable role.
|
|
1495
|
-
* unknown and must fall through: hard-denying it
|
|
1496
|
-
* outright for a calendar the user can in fact write
|
|
1497
|
-
* Google's own 403 per record if the write really is
|
|
1507
|
+
* Only on POSITIVE evidence of a non-writable role. A calendar whose role
|
|
1508
|
+
* neither source reported is unknown and must fall through: hard-denying it
|
|
1509
|
+
* would block the feature outright for a calendar the user can in fact write
|
|
1510
|
+
* to. Those attempts surface Google's own 403 per record if the write really is
|
|
1511
|
+
* not allowed. */
|
|
1498
1512
|
var isDeniedAccessRole = (accessRole) => accessRole !== null && !WRITABLE_ACCESS_ROLES.includes(accessRole);
|
|
1513
|
+
/** A role Google left blank is UNKNOWN, not a denial. `isDeniedAccessRole`
|
|
1514
|
+
* refuses every role it does not recognise — `""` included — so passing an
|
|
1515
|
+
* unreported one straight through would refuse a calendar Google simply said
|
|
1516
|
+
* nothing about. */
|
|
1517
|
+
var reportedAccessRole = (accessRole) => accessRole || null;
|
|
1499
1518
|
/** Resolve the calendar a schema names, for its writability and its timezone.
|
|
1500
1519
|
*
|
|
1501
|
-
* The list is consulted first because
|
|
1502
|
-
*
|
|
1503
|
-
*
|
|
1504
|
-
*
|
|
1505
|
-
* equals the literal the schema declares.
|
|
1520
|
+
* The list is consulted first because it answers for every added calendar in
|
|
1521
|
+
* one call the push already makes. `"primary"` is matched on the `primary`
|
|
1522
|
+
* flag, not the id — the primary calendar's own id is the account's email
|
|
1523
|
+
* address, which never equals the literal the schema declares.
|
|
1506
1524
|
*
|
|
1507
|
-
* A calendar absent from the list is NOT denied:
|
|
1508
|
-
*
|
|
1525
|
+
* A calendar absent from the list is NOT denied: `calendarList` holds only the
|
|
1526
|
+
* calendars the user has added, so one shared with write access can be missing
|
|
1527
|
+
* from it (Codex review). It is asked about itself instead, which reports the
|
|
1528
|
+
* same zone AND the real role. A 404 there means the calendar is genuinely
|
|
1509
1529
|
* unreachable, and propagates as a `failed` outcome. */
|
|
1510
1530
|
async function liveCalendarMeta(accessToken, calendarId) {
|
|
1511
1531
|
const key = canonicalCalendarId(calendarId);
|
|
@@ -1515,10 +1535,13 @@ async function liveCalendarMeta(accessToken, calendarId) {
|
|
|
1515
1535
|
accessRole: listed.accessRole,
|
|
1516
1536
|
timeZone: listed.timeZone
|
|
1517
1537
|
};
|
|
1518
|
-
const direct = await
|
|
1519
|
-
log.info("google", "calendar is not in the user's list —
|
|
1538
|
+
const direct = await getCalendarMeta(accessToken, calendarId);
|
|
1539
|
+
log.info("google", "calendar is not in the user's list — role read from the calendar itself", {
|
|
1540
|
+
calendarId: key,
|
|
1541
|
+
accessRole: direct.accessRole
|
|
1542
|
+
});
|
|
1520
1543
|
return {
|
|
1521
|
-
accessRole:
|
|
1544
|
+
accessRole: reportedAccessRole(direct.accessRole),
|
|
1522
1545
|
timeZone: direct.timeZone
|
|
1523
1546
|
};
|
|
1524
1547
|
}
|
|
@@ -1703,9 +1726,9 @@ async function createOrAdopt(ctx, eventId, record) {
|
|
|
1703
1726
|
}
|
|
1704
1727
|
/** A 403 on a write is a permissions answer, but `googleApiError` appends the
|
|
1705
1728
|
* "is the API enabled for the Cloud project?" hint to every 403 — true for a
|
|
1706
|
-
* disabled API, misleading here. Reached when the up-front
|
|
1707
|
-
*
|
|
1708
|
-
*
|
|
1729
|
+
* disabled API, misleading here. Reached when the up-front gate let the push
|
|
1730
|
+
* through on an unknown role, which is exactly when the user needs the real
|
|
1731
|
+
* reason. */
|
|
1709
1732
|
function writeFailure(eventId, error) {
|
|
1710
1733
|
if (isGoogleApiError(error) && error.status === 403) return {
|
|
1711
1734
|
kind: "skipped",
|
|
@@ -2734,9 +2757,9 @@ exports.deleteGoogleTokens = deleteGoogleTokens;
|
|
|
2734
2757
|
exports.deleteTask = deleteTask;
|
|
2735
2758
|
exports.fieldText = fieldText;
|
|
2736
2759
|
exports.findClientSecretPath = findClientSecretPath;
|
|
2737
|
-
exports.getCalendar = getCalendar;
|
|
2738
2760
|
exports.getCalendarColors = getCalendarColors;
|
|
2739
2761
|
exports.getCalendarEvent = getCalendarEvent;
|
|
2762
|
+
exports.getCalendarMeta = getCalendarMeta;
|
|
2740
2763
|
exports.getGoogleAccessToken = getGoogleAccessToken;
|
|
2741
2764
|
exports.googleApiError = googleApiError;
|
|
2742
2765
|
exports.googleAuthFlow = googleAuthFlow;
|
|
@@ -2783,6 +2806,7 @@ exports.pushCollectionNow = pushCollectionNow;
|
|
|
2783
2806
|
exports.pushableMap = pushableMap;
|
|
2784
2807
|
exports.readDriveFile = readDriveFile;
|
|
2785
2808
|
exports.releaseOrphanedCalendarToken = releaseOrphanedCalendarToken;
|
|
2809
|
+
exports.reportedAccessRole = reportedAccessRole;
|
|
2786
2810
|
exports.resolveEventSpan = resolveEventSpan;
|
|
2787
2811
|
exports.saveCalendarShadow = saveCalendarShadow;
|
|
2788
2812
|
exports.saveCalendarSyncToken = saveCalendarSyncToken;
|
|
@@ -2794,6 +2818,7 @@ exports.syncCalendarForCollection = syncCalendarForCollection;
|
|
|
2794
2818
|
exports.syncCalendarGroup = syncCalendarGroup;
|
|
2795
2819
|
exports.syncDueCalendarCollections = syncDueCalendarCollections;
|
|
2796
2820
|
exports.syncNewCalendarCollections = syncNewCalendarCollections;
|
|
2821
|
+
exports.toCalendarMeta = toCalendarMeta;
|
|
2797
2822
|
exports.toCalendarSummary = toCalendarSummary;
|
|
2798
2823
|
exports.toCollectionDateTime = toCollectionDateTime;
|
|
2799
2824
|
exports.toCollectionRecord = toCollectionRecord;
|