tandem-editor 0.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/shared/constants.ts","../../src/server/yjs/provider.ts","../../src/server/platform.ts","../../src/server/session/manager.ts","../../src/server/file-io/mdast-ydoc.ts","../../src/server/file-io/markdown.ts","../../src/shared/offsets.ts","../../src/server/mcp/document-model.ts","../../src/server/file-io/docx-html.ts","../../src/server/file-io/docx.ts","../../src/shared/positions/types.ts","../../src/shared/positions/index.ts","../../src/server/positions.ts","../../src/shared/types.ts","../../src/server/file-io/docx-comments.ts","../../src/server/file-io/index.ts","../../src/server/mcp/file-opener.ts","../../src/server/mcp/document-service.ts","../../src/shared/utils.ts","../../src/server/events/types.ts","../../src/server/events/queue.ts","../../src/server/mcp/launcher.ts","../../src/server/index.ts","../../src/server/mcp/server.ts","../../src/server/open-browser.ts","../../src/server/mcp/annotations.ts","../../src/server/mcp/document.ts","../../src/server/mcp/response.ts","../../src/server/notifications.ts","../../src/server/mcp/convert.ts","../../src/server/mcp/api-routes.ts","../../src/server/mcp/awareness.ts","../../src/server/mcp/channel-routes.ts","../../src/server/events/sse.ts","../../src/server/mcp/navigation.ts","../../src/server/error-filter.ts","../../src/server/mcp/tutorial-annotations.ts"],"sourcesContent":["export const DEFAULT_WS_PORT = 3478;\r\nexport const DEFAULT_MCP_PORT = 3479;\r\n\r\n/** File extensions the server accepts for opening. */\r\nexport const SUPPORTED_EXTENSIONS = new Set([\".md\", \".txt\", \".html\", \".htm\", \".docx\"]);\r\nexport const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB\r\nexport const MAX_WS_PAYLOAD = 10 * 1024 * 1024; // 10MB\r\nexport const MAX_WS_CONNECTIONS = 4;\r\nexport const IDLE_TIMEOUT = 30 * 60 * 1000; // 30 minutes\r\nexport const SESSION_MAX_AGE = 30 * 24 * 60 * 60 * 1000; // 30 days\r\nexport const TYPING_DEBOUNCE = 3000; // 3 seconds\r\nexport const DISCONNECT_DEBOUNCE_MS = 3000; // 3 seconds before showing \"server not reachable\"\r\nexport const PROLONGED_DISCONNECT_MS = 30_000; // 30 seconds before showing App-level disconnect banner\r\nexport const OVERLAY_STALE_DEBOUNCE = 200; // 200ms\r\nexport const REVIEW_BANNER_THRESHOLD = 5;\r\n\r\nexport const HIGHLIGHT_COLORS: Record<string, string> = {\r\n yellow: \"rgba(255, 235, 59, 0.3)\",\r\n red: \"rgba(244, 67, 54, 0.3)\",\r\n green: \"rgba(76, 175, 80, 0.3)\",\r\n blue: \"rgba(33, 150, 243, 0.3)\",\r\n purple: \"rgba(156, 39, 176, 0.3)\",\r\n};\r\n\r\nexport const INTERRUPTION_MODE_DEFAULT = \"all\" as const;\r\nexport const INTERRUPTION_MODE_KEY = \"tandem:interruptionMode\";\r\n\r\n// Large file thresholds\r\nexport const CHARS_PER_PAGE = 3_000;\r\nexport const LARGE_FILE_PAGE_THRESHOLD = 50;\r\nexport const VERY_LARGE_FILE_PAGE_THRESHOLD = 100;\r\n\r\nexport const CLAUDE_PRESENCE_COLOR = \"#6366f1\";\r\nexport const CLAUDE_FOCUS_OPACITY = 0.1;\r\n\r\nexport const CTRL_ROOM = \"__tandem_ctrl__\";\r\n\r\n/** Y.Map key constants — centralized to prevent silent bugs from string typos. */\r\nexport const Y_MAP_ANNOTATIONS = \"annotations\";\r\nexport const Y_MAP_AWARENESS = \"awareness\";\r\nexport const Y_MAP_USER_AWARENESS = \"userAwareness\";\r\nexport const Y_MAP_CHAT = \"chat\";\r\nexport const Y_MAP_DOCUMENT_META = \"documentMeta\";\r\nexport const Y_MAP_SAVED_AT_VERSION = \"savedAtVersion\";\r\n\r\nexport const SERVER_INFO_DIR = \".tandem\";\r\nexport const SERVER_INFO_FILE = \".tandem/.server-info\";\r\n\r\nexport const RECENT_FILES_KEY = \"tandem:recentFiles\";\r\nexport const RECENT_FILES_CAP = 20;\r\n\r\nexport const USER_NAME_KEY = \"tandem:userName\";\r\nexport const USER_NAME_DEFAULT = \"You\";\r\n\r\n// Toast notifications\r\nexport const TOAST_DISMISS_MS = { error: 8000, warning: 6000, info: 4000 } as const;\r\nexport const MAX_VISIBLE_TOASTS = 5;\r\nexport const NOTIFICATION_BUFFER_SIZE = 50;\r\n\r\n// Onboarding tutorial\r\nexport const TUTORIAL_COMPLETED_KEY = \"tandem:tutorialCompleted\";\r\nexport const TUTORIAL_ANNOTATION_PREFIX = \"tutorial-\";\r\n\r\n// Channel / event queue\r\nexport const CHANNEL_EVENT_BUFFER_SIZE = 200;\r\nexport const CHANNEL_EVENT_BUFFER_AGE_MS = 60_000; // 60 seconds\r\nexport const CHANNEL_SSE_KEEPALIVE_MS = 15_000; // 15 seconds\r\nexport const CHANNEL_MAX_RETRIES = 5;\r\nexport const CHANNEL_RETRY_DELAY_MS = 2_000;\r\n","import { Hocuspocus } from \"@hocuspocus/server\";\r\nimport * as Y from \"yjs\";\r\n\r\nlet hocuspocusInstance: Hocuspocus | null = null;\r\nconst documents = new Map<string, Y.Doc>();\r\n\r\n// Callback predicate: returns true if Hocuspocus should keep a document in the\r\n// map even after all WebSocket clients disconnect. Registered by document-service\r\n// to avoid a circular import (provider -> document-service -> provider).\r\nlet shouldKeepDocument: ((name: string) => boolean) | null = null;\r\n\r\n// Callback for event queue observer lifecycle (registered by events/queue.ts to avoid circular import).\r\nlet onDocSwapped: ((docName: string, newDoc: Y.Doc) => void) | null = null;\r\nlet onDocUnloaded: ((docName: string) => void) | null = null;\r\n\r\nexport function setDocLifecycleCallbacks(\r\n swapped: (docName: string, newDoc: Y.Doc) => void,\r\n unloaded: (docName: string) => void,\r\n): void {\r\n onDocSwapped = swapped;\r\n onDocUnloaded = unloaded;\r\n}\r\n\r\n/** Register a predicate that prevents afterUnloadDocument from evicting docs\r\n * that MCP (or the bootstrap channel) still needs. */\r\nexport function setShouldKeepDocument(fn: (name: string) => boolean): void {\r\n shouldKeepDocument = fn;\r\n}\r\n\r\n/**\r\n * Get a document by room name. Returns undefined if it doesn't exist.\r\n */\r\nexport function getDocument(name: string): Y.Doc | undefined {\r\n return documents.get(name);\r\n}\r\n\r\n/**\r\n * Get or create a Y.Doc for the given room name.\r\n * If Hocuspocus has already created a doc for this room (browser connected first),\r\n * returns that doc. Otherwise creates a new one that will be merged into the\r\n * Hocuspocus doc when a browser connects.\r\n */\r\nexport function getOrCreateDocument(name: string): Y.Doc {\r\n let doc = documents.get(name);\r\n if (!doc) {\r\n doc = new Y.Doc();\r\n documents.set(name, doc);\r\n }\r\n return doc;\r\n}\r\n\r\n/**\r\n * Remove a document from the map. Called by afterUnloadDocument when\r\n * Hocuspocus destroys a room's doc after all clients disconnect.\r\n */\r\nexport function removeDocument(name: string): boolean {\r\n return documents.delete(name);\r\n}\r\n\r\nexport async function startHocuspocus(port: number): Promise<Hocuspocus> {\r\n hocuspocusInstance = new Hocuspocus({\r\n port,\r\n address: \"127.0.0.1\",\r\n quiet: true, // stdout is the MCP wire — suppress the startup banner\r\n\r\n async onConnect({ request, documentName }) {\r\n // Origin validation: reject connections not from localhost (prevents DNS rebinding)\r\n const origin = request?.headers?.origin;\r\n if (origin) {\r\n const url = new URL(origin);\r\n if (url.hostname !== \"localhost\" && url.hostname !== \"127.0.0.1\") {\r\n console.error(`[Hocuspocus] Rejected connection from origin: ${origin}`);\r\n throw new Error(\"Connection rejected: invalid origin\");\r\n }\r\n }\r\n console.error(`[Hocuspocus] Client connected to: ${documentName}`);\r\n },\r\n\r\n async onDisconnect({ documentName }) {\r\n console.error(`[Hocuspocus] Client disconnected from: ${documentName}`);\r\n },\r\n\r\n async onLoadDocument({ document, documentName }) {\r\n console.error(`[Hocuspocus] Loading document: ${documentName}`);\r\n\r\n // If MCP tools have already created and populated a doc for this room,\r\n // merge its state into the Hocuspocus-provided doc, then swap the map entry\r\n const existing = documents.get(documentName);\r\n if (existing && existing !== document) {\r\n const update = Y.encodeStateAsUpdate(existing);\r\n Y.applyUpdate(document, update);\r\n existing.destroy();\r\n console.error(`[Hocuspocus] Merged pre-existing content into document: ${documentName}`);\r\n }\r\n\r\n // The Hocuspocus-provided doc is now the authoritative instance\r\n documents.set(documentName, document);\r\n\r\n // Notify event queue to reattach observers to the new doc instance\r\n onDocSwapped?.(documentName, document);\r\n\r\n return document;\r\n },\r\n\r\n async afterUnloadDocument({ documentName }) {\r\n if (shouldKeepDocument?.(documentName)) {\r\n console.error(`[Hocuspocus] Kept document in map (MCP still tracking): ${documentName}`);\r\n return;\r\n }\r\n if (documents.has(documentName)) {\r\n onDocUnloaded?.(documentName);\r\n documents.delete(documentName);\r\n console.error(`[Hocuspocus] Unloaded document from map: ${documentName}`);\r\n }\r\n },\r\n });\r\n\r\n // Hocuspocus.listen() never rejects on EADDRINUSE — the error goes to\r\n // uncaughtException instead. Race listen() against an error listener on the\r\n // internal httpServer so we surface bind failures properly.\r\n // NOTE: accesses Hocuspocus internals (.server.httpServer) — verify on upgrades.\r\n const internal = (hocuspocusInstance as any).server?.httpServer;\r\n if (internal) {\r\n let onError: (err: Error) => void;\r\n await Promise.race([\r\n hocuspocusInstance.listen().then((result) => {\r\n internal.removeListener(\"error\", onError);\r\n return result;\r\n }),\r\n new Promise<never>((_, reject) => {\r\n onError = (err: Error) => reject(err);\r\n internal.once(\"error\", onError);\r\n }),\r\n ]);\r\n } else {\r\n console.error(\r\n \"[Tandem] Warning: could not access Hocuspocus internal httpServer — \" +\r\n \"bind failures may not be caught. Hocuspocus internals may have changed.\",\r\n );\r\n await hocuspocusInstance.listen();\r\n }\r\n return hocuspocusInstance;\r\n}\r\n\r\nexport function getHocuspocus(): Hocuspocus | null {\r\n return hocuspocusInstance;\r\n}\r\n","import { execSync } from \"child_process\";\r\nimport net from \"net\";\r\nimport path from \"path\";\r\nimport envPaths from \"env-paths\";\r\n\r\nconst paths = envPaths(\"tandem\", { suffix: \"\" });\r\n\r\n/** Platform-appropriate session storage directory. */\r\nexport const SESSION_DIR = path.join(paths.data, \"sessions\");\r\n\r\n/**\r\n * Kill any process currently listening on the given TCP port.\r\n * Best-effort — swallows all errors so startup always proceeds.\r\n */\r\nexport function freePort(port: number): void {\r\n try {\r\n if (process.platform === \"win32\") {\r\n freePortWindows(port);\r\n } else {\r\n freePortUnix(port);\r\n }\r\n } catch {\r\n // Nothing listening or kill failed — proceed anyway\r\n }\r\n}\r\n\r\n/**\r\n * Poll until a TCP port is available for binding.\r\n * Replaces the fixed 300ms sleep after freePort() — the OS may need\r\n * longer to release a killed process's socket (especially on Windows).\r\n */\r\nexport async function waitForPort(port: number, timeoutMs = 5000): Promise<void> {\r\n const start = Date.now();\r\n while (Date.now() - start < timeoutMs) {\r\n if (await tryBind(port)) return;\r\n await new Promise((r) => setTimeout(r, 200));\r\n }\r\n console.error(\r\n `[Tandem] Warning: port ${port} still not available after ${timeoutMs}ms, proceeding anyway`,\r\n );\r\n}\r\n\r\n/** Attempt to bind a port and immediately release it. Returns true if available. */\r\nfunction tryBind(port: number): Promise<boolean> {\r\n return new Promise((resolve, reject) => {\r\n const srv = net.createServer();\r\n srv.once(\"error\", (err: NodeJS.ErrnoException) => {\r\n srv.close(() => {\r\n if (err.code === \"EADDRINUSE\") {\r\n resolve(false);\r\n } else {\r\n reject(err); // EACCES, etc. — don't mask as \"port in use\"\r\n }\r\n });\r\n });\r\n srv.listen(port, \"127.0.0.1\", () => {\r\n srv.close(() => resolve(true));\r\n });\r\n });\r\n}\r\n\r\n/** Parse PIDs from lsof output (one PID per line). */\r\nexport function parseLsofPids(output: string): number[] {\r\n return output\r\n .trim()\r\n .split(\"\\n\")\r\n .map((line) => parseInt(line.trim(), 10))\r\n .filter((pid) => Number.isFinite(pid) && pid > 0);\r\n}\r\n\r\n/** Parse a PID from ss output (e.g. `pid=1234`). */\r\nexport function parseSsPid(output: string): number | null {\r\n const match = output.match(/pid=(\\d+)/);\r\n return match ? parseInt(match[1], 10) : null;\r\n}\r\n\r\nfunction freePortWindows(port: number): void {\r\n const out = execSync(`netstat -ano | findstr \":${port}.*LISTENING\"`, {\r\n encoding: \"utf-8\",\r\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\r\n });\r\n const pid = out.trim().split(/\\s+/).at(-1);\r\n if (pid && /^\\d+$/.test(pid)) {\r\n execSync(`taskkill /PID ${pid} /F`, { stdio: \"ignore\" });\r\n console.error(`[Tandem] Killed stale PID ${pid} holding port ${port}`);\r\n }\r\n}\r\n\r\nfunction freePortUnix(port: number): void {\r\n let pids: number[] = [];\r\n\r\n try {\r\n const out = execSync(`lsof -ti TCP:${port} -sTCP:LISTEN`, {\r\n encoding: \"utf-8\",\r\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\r\n });\r\n pids = parseLsofPids(out);\r\n } catch {\r\n // lsof not available — try ss (Linux)\r\n try {\r\n const out = execSync(`ss -tlnp sport = :${port}`, {\r\n encoding: \"utf-8\",\r\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\r\n });\r\n const pid = parseSsPid(out);\r\n if (pid) pids = [pid];\r\n } catch {\r\n // ss also unavailable — give up\r\n }\r\n }\r\n\r\n for (const pid of pids) {\r\n try {\r\n process.kill(pid, \"SIGKILL\");\r\n console.error(`[Tandem] Killed stale PID ${pid} holding port ${port}`);\r\n } catch {\r\n // Process already gone\r\n }\r\n }\r\n}\r\n","import fs from \"fs/promises\";\r\nimport path from \"path\";\r\nimport * as Y from \"yjs\";\r\nimport type { SessionData } from \"../../shared/types.js\";\r\nimport { SESSION_DIR } from \"../platform.js\";\r\n\r\nimport { SESSION_MAX_AGE, CTRL_ROOM, Y_MAP_CHAT } from \"../../shared/constants.js\";\r\nimport { MCP_ORIGIN } from \"../events/queue.js\";\r\n\r\nconst AUTO_SAVE_INTERVAL = 60 * 1000; // 60 seconds\r\nlet sessionDirReady = false;\r\n\r\n/** Generate a session key from a file path */\r\nexport function sessionKey(filePath: string): string {\r\n return encodeURIComponent(filePath.replace(/\\\\/g, \"/\"));\r\n}\r\n\r\n/** Save Y.Doc state + metadata as a session file */\r\nexport async function saveSession(filePath: string, format: string, doc: Y.Doc): Promise<void> {\r\n const key = sessionKey(filePath);\r\n let sourceFileMtime = 0;\r\n // Upload paths have no disk file — skip stat\r\n if (!filePath.startsWith(\"upload://\")) {\r\n try {\r\n const stat = await fs.stat(filePath);\r\n sourceFileMtime = stat.mtimeMs;\r\n } catch {\r\n // File may not exist yet (new doc)\r\n }\r\n }\r\n\r\n const state = Y.encodeStateAsUpdate(doc);\r\n const ydocState = Buffer.from(state).toString(\"base64\");\r\n\r\n const data: SessionData = {\r\n filePath,\r\n format,\r\n ydocState,\r\n sourceFileMtime,\r\n lastAccessed: Date.now(),\r\n };\r\n\r\n if (!sessionDirReady) {\r\n await fs.mkdir(SESSION_DIR, { recursive: true });\r\n sessionDirReady = true;\r\n }\r\n const sessionPath = path.join(SESSION_DIR, `${key}.json`);\r\n const tmpPath = `${sessionPath}.tmp`;\r\n await fs.writeFile(tmpPath, JSON.stringify(data), \"utf-8\");\r\n try {\r\n await fs.rename(tmpPath, sessionPath);\r\n } catch (err) {\r\n await fs.unlink(tmpPath).catch(() => {});\r\n throw err;\r\n }\r\n}\r\n\r\n/** Load a session file if it exists */\r\nexport async function loadSession(filePath: string): Promise<SessionData | null> {\r\n const key = sessionKey(filePath);\r\n const sessionPath = path.join(SESSION_DIR, `${key}.json`);\r\n try {\r\n const content = await fs.readFile(sessionPath, \"utf-8\");\r\n return JSON.parse(content) as SessionData;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/** Restore a Y.Doc from a session's base64-encoded state */\r\nexport function restoreYDoc(doc: Y.Doc, session: SessionData): void {\r\n const state = Buffer.from(session.ydocState, \"base64\");\r\n Y.applyUpdate(doc, new Uint8Array(state));\r\n}\r\n\r\n/** Check if the source file has changed since the session was saved */\r\nexport async function sourceFileChanged(session: SessionData): Promise<boolean> {\r\n // Uploaded files have no disk path — session is the only truth\r\n if (session.filePath.startsWith(\"upload://\")) return false;\r\n try {\r\n const stat = await fs.stat(session.filePath);\r\n return stat.mtimeMs !== session.sourceFileMtime;\r\n } catch {\r\n return true; // File doesn't exist — treat as changed\r\n }\r\n}\r\n\r\n/** Delete a session file */\r\nexport async function deleteSession(filePath: string): Promise<void> {\r\n const key = sessionKey(filePath);\r\n const sessionPath = path.join(SESSION_DIR, `${key}.json`);\r\n try {\r\n await fs.unlink(sessionPath);\r\n } catch (err: unknown) {\r\n const code = (err as NodeJS.ErrnoException).code;\r\n if (code !== \"ENOENT\") {\r\n console.error(`[Tandem] deleteSession: failed to delete ${sessionPath}:`, err);\r\n }\r\n }\r\n}\r\n\r\n// --- CTRL_ROOM persistence (chat history) ---\r\n\r\nconst CTRL_SESSION_KEY = CTRL_ROOM;\r\n\r\n/** Save the CTRL_ROOM Y.Doc (chat history) */\r\nexport async function saveCtrlSession(doc: Y.Doc): Promise<void> {\r\n if (!sessionDirReady) {\r\n await fs.mkdir(SESSION_DIR, { recursive: true });\r\n sessionDirReady = true;\r\n }\r\n\r\n // Prune chat to newest 200 messages before saving\r\n const chatMap = doc.getMap(Y_MAP_CHAT);\r\n const entries: Array<{ id: string; timestamp: number }> = [];\r\n chatMap.forEach((value, key) => {\r\n const msg = value as { timestamp: number };\r\n entries.push({ id: key, timestamp: msg.timestamp });\r\n });\r\n if (entries.length > 200) {\r\n entries.sort((a, b) => a.timestamp - b.timestamp);\r\n const toDelete = entries.slice(0, entries.length - 200);\r\n doc.transact(() => {\r\n for (const entry of toDelete) {\r\n chatMap.delete(entry.id);\r\n }\r\n }, MCP_ORIGIN);\r\n }\r\n\r\n const state = Y.encodeStateAsUpdate(doc);\r\n const ydocState = Buffer.from(state).toString(\"base64\");\r\n\r\n const data = { ydocState, lastAccessed: Date.now() };\r\n const sessionPath = path.join(SESSION_DIR, `${CTRL_SESSION_KEY}.json`);\r\n const tmpPath = `${sessionPath}.tmp`;\r\n await fs.writeFile(tmpPath, JSON.stringify(data), \"utf-8\");\r\n try {\r\n await fs.rename(tmpPath, sessionPath);\r\n } catch (err) {\r\n await fs.unlink(tmpPath).catch(() => {});\r\n throw err;\r\n }\r\n}\r\n\r\n/** Load the CTRL_ROOM session if it exists */\r\nexport async function loadCtrlSession(): Promise<string | null> {\r\n const sessionPath = path.join(SESSION_DIR, `${CTRL_SESSION_KEY}.json`);\r\n try {\r\n const content = await fs.readFile(sessionPath, \"utf-8\");\r\n const data = JSON.parse(content);\r\n return data.ydocState ?? null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/** Restore a CTRL_ROOM Y.Doc from base64 state */\r\nexport function restoreCtrlDoc(doc: Y.Doc, base64State: string): void {\r\n const state = Buffer.from(base64State, \"base64\");\r\n Y.applyUpdate(doc, new Uint8Array(state));\r\n}\r\n\r\n/**\r\n * Scan the session directory for document sessions that can be restored.\r\n * Skips the ctrl session, upload:// paths, and corrupt files.\r\n * Returns file paths sorted by most recently accessed first.\r\n */\r\nexport async function listSessionFilePaths(): Promise<\r\n Array<{ filePath: string; lastAccessed: number }>\r\n> {\r\n try {\r\n await fs.mkdir(SESSION_DIR, { recursive: true });\r\n const files = await fs.readdir(SESSION_DIR);\r\n const results: Array<{ filePath: string; lastAccessed: number }> = [];\r\n\r\n for (const file of files) {\r\n if (!file.endsWith(\".json\")) continue;\r\n // Skip ctrl session (key is the CTRL_ROOM name)\r\n if (file === `${encodeURIComponent(CTRL_ROOM)}.json`) continue;\r\n\r\n try {\r\n const raw = await fs.readFile(path.join(SESSION_DIR, file), \"utf-8\");\r\n const data = JSON.parse(raw) as SessionData;\r\n if (!data.filePath || data.filePath.startsWith(\"upload://\")) continue;\r\n results.push({ filePath: data.filePath, lastAccessed: data.lastAccessed ?? 0 });\r\n } catch (err) {\r\n console.error(`[Tandem] Skipping unreadable session file ${file}:`, err);\r\n }\r\n }\r\n\r\n results.sort((a, b) => b.lastAccessed - a.lastAccessed);\r\n return results;\r\n } catch (err) {\r\n console.error(\"[Tandem] Failed to read session directory:\", err);\r\n return [];\r\n }\r\n}\r\n\r\n/** Delete sessions older than 30 days */\r\nexport async function cleanupSessions(): Promise<number> {\r\n let cleaned = 0;\r\n try {\r\n const files = await fs.readdir(SESSION_DIR);\r\n const now = Date.now();\r\n\r\n for (const file of files) {\r\n const filePath = path.join(SESSION_DIR, file);\r\n const stat = await fs.stat(filePath);\r\n if (now - stat.mtimeMs > SESSION_MAX_AGE) {\r\n await fs.unlink(filePath);\r\n cleaned++;\r\n }\r\n }\r\n } catch {\r\n // Session dir doesn't exist yet\r\n }\r\n return cleaned;\r\n}\r\n\r\n// --- Auto-save ---\r\n\r\nlet autoSaveTimer: ReturnType<typeof setInterval> | null = null;\r\nlet autoSaveCallback: (() => Promise<void>) | null = null;\r\n\r\n/** Check if auto-save is currently running */\r\nexport function isAutoSaveRunning(): boolean {\r\n return autoSaveTimer !== null;\r\n}\r\n\r\n/** Start auto-saving every 60 seconds. Pass a callback that saves the current session. */\r\nexport function startAutoSave(callback: () => Promise<void>): void {\r\n stopAutoSave();\r\n autoSaveCallback = callback;\r\n autoSaveTimer = setInterval(async () => {\r\n try {\r\n await autoSaveCallback?.();\r\n } catch (err) {\r\n console.error(\"[Tandem] Auto-save failed:\", err);\r\n }\r\n }, AUTO_SAVE_INTERVAL);\r\n}\r\n\r\n/** Stop auto-save timer */\r\nexport function stopAutoSave(): void {\r\n if (autoSaveTimer) {\r\n clearInterval(autoSaveTimer);\r\n autoSaveTimer = null;\r\n }\r\n autoSaveCallback = null;\r\n}\r\n","import * as Y from 'yjs';\r\nimport type { Root, RootContent, PhrasingContent } from 'mdast';\r\n\r\n/**\r\n * Convert an MDAST tree into Y.Doc XmlFragment elements.\r\n * Block nodes become Y.XmlElements with Tiptap-compatible nodeNames.\r\n * Inline content becomes formatted Y.XmlText within those elements.\r\n *\r\n * Elements are attached to the doc BEFORE text is populated — Yjs\r\n * requires this for correct insert ordering on Y.XmlText.\r\n */\r\nexport function mdastToYDoc(doc: Y.Doc, tree: Root): void {\r\n const fragment = doc.getXmlFragment('default');\r\n\r\n // Clear existing content in a single operation\r\n if (fragment.length > 0) {\r\n fragment.delete(0, fragment.length);\r\n }\r\n\r\n // Two-pass: build structure first, then populate text.\r\n // Pass 1 collects deferred text operations while building the element tree.\r\n // Yjs requires Y.XmlText to be attached to a doc for correct insert ordering.\r\n const deferred: Array<{ xmlText: Y.XmlText; nodes?: PhrasingContent[]; plainText?: string }> = [];\r\n const allElements: Y.XmlElement[] = [];\r\n for (const node of tree.children) {\r\n allElements.push(...blockToYxml(node, deferred));\r\n }\r\n\r\n // Attach all elements to the doc (pass 1 complete)\r\n if (allElements.length > 0) {\r\n fragment.insert(0, allElements);\r\n }\r\n\r\n // Pass 2: populate text now that elements are attached to the Y.Doc\r\n for (const { xmlText, nodes, plainText } of deferred) {\r\n if (nodes) {\r\n processInline(xmlText, nodes, {});\r\n } else if (plainText != null) {\r\n xmlText.insert(0, plainText);\r\n }\r\n }\r\n}\r\n\r\n/** Convert a block-level MDAST node to one or more Y.XmlElements */\r\nfunction blockToYxml(\r\n node: RootContent,\r\n deferred: Array<{ xmlText: Y.XmlText; nodes?: PhrasingContent[]; plainText?: string }>,\r\n): Y.XmlElement[] {\r\n switch (node.type) {\r\n case 'heading': {\r\n const el = new Y.XmlElement('heading');\r\n el.setAttribute('level', node.depth as any);\r\n const text = new Y.XmlText();\r\n el.insert(0, [text]);\r\n deferred.push({ xmlText: text, nodes: node.children });\r\n return [el];\r\n }\r\n\r\n case 'paragraph': {\r\n const el = new Y.XmlElement('paragraph');\r\n const text = new Y.XmlText();\r\n el.insert(0, [text]);\r\n deferred.push({ xmlText: text, nodes: node.children });\r\n return [el];\r\n }\r\n\r\n case 'blockquote': {\r\n const el = new Y.XmlElement('blockquote');\r\n for (const child of node.children) {\r\n const childEls = blockToYxml(child, deferred);\r\n for (const c of childEls) {\r\n el.insert(el.length, [c]);\r\n }\r\n }\r\n return [el];\r\n }\r\n\r\n case 'list': {\r\n const nodeName = node.ordered ? 'orderedList' : 'bulletList';\r\n const el = new Y.XmlElement(nodeName);\r\n if (node.ordered && node.start != null && node.start !== 1) {\r\n el.setAttribute('start', node.start as any);\r\n }\r\n for (const item of node.children) {\r\n const listItem = new Y.XmlElement('listItem');\r\n for (const child of item.children) {\r\n const childEls = blockToYxml(child, deferred);\r\n for (const c of childEls) {\r\n listItem.insert(listItem.length, [c]);\r\n }\r\n }\r\n el.insert(el.length, [listItem]);\r\n }\r\n return [el];\r\n }\r\n\r\n case 'code': {\r\n const el = new Y.XmlElement('codeBlock');\r\n if (node.lang) {\r\n el.setAttribute('language', node.lang);\r\n }\r\n const text = new Y.XmlText();\r\n el.insert(0, [text]);\r\n deferred.push({ xmlText: text, plainText: node.value });\r\n return [el];\r\n }\r\n\r\n case 'thematicBreak': {\r\n return [new Y.XmlElement('horizontalRule')];\r\n }\r\n\r\n case 'image': {\r\n const el = new Y.XmlElement('image');\r\n el.setAttribute('src', node.url);\r\n if (node.alt) el.setAttribute('alt', node.alt);\r\n if (node.title) el.setAttribute('title', node.title);\r\n return [el];\r\n }\r\n\r\n // html blocks, definitions, etc. — wrap as paragraphs to avoid data loss\r\n default: {\r\n if ('value' in node && typeof node.value === 'string') {\r\n const el = new Y.XmlElement('paragraph');\r\n const text = new Y.XmlText();\r\n el.insert(0, [text]);\r\n deferred.push({ xmlText: text, plainText: node.value });\r\n return [el];\r\n }\r\n return [];\r\n }\r\n }\r\n}\r\n\r\n/** All mark names that can appear on inline text */\r\nconst ALL_MARKS = ['bold', 'italic', 'strike', 'code', 'link'] as const;\r\n\r\n/**\r\n * Build Yjs insert attributes from the current mark stack.\r\n * Explicitly sets null for inactive marks to prevent Yjs from\r\n * inheriting formatting from adjacent formatted segments.\r\n */\r\nfunction buildAttrs(marks: Record<string, object>): Record<string, object | null> {\r\n const attrs: Record<string, object | null> = {};\r\n for (const name of ALL_MARKS) {\r\n attrs[name] = name in marks ? marks[name] : null;\r\n }\r\n return attrs;\r\n}\r\n\r\n/**\r\n * Process inline/phrasing MDAST nodes into a single Y.XmlText with marks.\r\n * Uses insert-with-attributes (not insert + format) because Yjs requires\r\n * the Y.XmlText to be attached to a doc for format() to preserve order.\r\n */\r\nfunction processInline(\r\n xmlText: Y.XmlText,\r\n nodes: PhrasingContent[],\r\n marks: Record<string, object>,\r\n): void {\r\n for (const node of nodes) {\r\n switch (node.type) {\r\n case 'text': {\r\n xmlText.insert(xmlText.length, node.value, buildAttrs(marks));\r\n break;\r\n }\r\n\r\n case 'strong':\r\n processInline(xmlText, node.children, { ...marks, bold: {} });\r\n break;\r\n\r\n case 'emphasis':\r\n processInline(xmlText, node.children, { ...marks, italic: {} });\r\n break;\r\n\r\n case 'delete':\r\n processInline(xmlText, node.children, { ...marks, strike: {} });\r\n break;\r\n\r\n case 'inlineCode': {\r\n xmlText.insert(xmlText.length, node.value, buildAttrs({ ...marks, code: {} }));\r\n break;\r\n }\r\n\r\n case 'link':\r\n processInline(xmlText, node.children, {\r\n ...marks,\r\n link: { href: node.url, ...(node.title ? { title: node.title } : {}) },\r\n });\r\n break;\r\n\r\n case 'break': {\r\n const embed = new Y.XmlElement('hardBreak');\r\n xmlText.insertEmbed(xmlText.length, embed);\r\n break;\r\n }\r\n\r\n case 'image': {\r\n // Inline images: insert alt text (best-effort)\r\n xmlText.insert(xmlText.length, node.alt || node.url, buildAttrs(marks));\r\n break;\r\n }\r\n\r\n // html inline, footnoteReference, etc. — insert raw value if available\r\n default:\r\n if ('value' in node && typeof node.value === 'string') {\r\n xmlText.insert(xmlText.length, node.value, buildAttrs(marks));\r\n }\r\n break;\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Convert a Y.Doc's XmlFragment back to an MDAST Root tree.\r\n */\r\nexport function yDocToMdast(doc: Y.Doc): Root {\r\n const fragment = doc.getXmlFragment('default');\r\n const children: RootContent[] = [];\r\n\r\n for (let i = 0; i < fragment.length; i++) {\r\n const node = fragment.get(i);\r\n if (node instanceof Y.XmlElement) {\r\n const mdastNode = yxmlToMdast(node);\r\n if (mdastNode) children.push(mdastNode);\r\n }\r\n }\r\n\r\n return { type: 'root', children };\r\n}\r\n\r\n/** Convert a Y.XmlElement back to an MDAST block node */\r\nfunction yxmlToMdast(el: Y.XmlElement): RootContent | null {\r\n switch (el.nodeName) {\r\n case 'heading': {\r\n const depth = (Number(el.getAttribute('level') ?? 1)) as 1 | 2 | 3 | 4 | 5 | 6;\r\n return { type: 'heading', depth, children: deltaToPhrasingContent(el) };\r\n }\r\n\r\n case 'paragraph':\r\n return { type: 'paragraph', children: deltaToPhrasingContent(el) };\r\n\r\n case 'blockquote': {\r\n const children: RootContent[] = [];\r\n for (let i = 0; i < el.length; i++) {\r\n const child = el.get(i);\r\n if (child instanceof Y.XmlElement) {\r\n const m = yxmlToMdast(child);\r\n if (m) children.push(m);\r\n }\r\n }\r\n // blockquote.children is BlockContent[] but RootContent covers it\r\n return { type: 'blockquote', children: children as any };\r\n }\r\n\r\n case 'bulletList':\r\n case 'orderedList': {\r\n const ordered = el.nodeName === 'orderedList';\r\n const start = ordered ? (Number(el.getAttribute('start')) || 1) : undefined;\r\n const listItems: any[] = [];\r\n for (let i = 0; i < el.length; i++) {\r\n const child = el.get(i);\r\n if (child instanceof Y.XmlElement && child.nodeName === 'listItem') {\r\n const itemChildren: any[] = [];\r\n for (let j = 0; j < child.length; j++) {\r\n const grandchild = child.get(j);\r\n if (grandchild instanceof Y.XmlElement) {\r\n const m = yxmlToMdast(grandchild);\r\n if (m) itemChildren.push(m);\r\n }\r\n }\r\n listItems.push({ type: 'listItem', spread: false, children: itemChildren });\r\n }\r\n }\r\n return {\r\n type: 'list',\r\n ordered,\r\n spread: false,\r\n ...(ordered && start !== 1 ? { start } : {}),\r\n children: listItems,\r\n } as any;\r\n }\r\n\r\n case 'codeBlock': {\r\n const lang = el.getAttribute('language') as string | undefined;\r\n let value = '';\r\n for (let i = 0; i < el.length; i++) {\r\n const child = el.get(i);\r\n if (child instanceof Y.XmlText) {\r\n value += child.toString();\r\n }\r\n }\r\n return { type: 'code', lang: lang || null, value } as any;\r\n }\r\n\r\n case 'horizontalRule':\r\n return { type: 'thematicBreak' };\r\n\r\n case 'image': {\r\n return {\r\n type: 'image',\r\n url: (el.getAttribute('src') as string) || '',\r\n alt: (el.getAttribute('alt') as string) || undefined,\r\n title: (el.getAttribute('title') as string) || null,\r\n } as any;\r\n }\r\n\r\n // Unknown node types — try to extract text content as a paragraph\r\n default: {\r\n const phrasing = deltaToPhrasingContent(el);\r\n if (phrasing.length > 0) {\r\n return { type: 'paragraph', children: phrasing };\r\n }\r\n return null;\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Strip y-prosemirror hash suffixes from attribute keys.\r\n * y-prosemirror appends \"--<hash>\" to mark names in delta attributes.\r\n */\r\nfunction stripHashSuffix(key: string): string {\r\n const dashIdx = key.indexOf('--');\r\n return dashIdx >= 0 ? key.slice(0, dashIdx) : key;\r\n}\r\n\r\n/**\r\n * Convert Y.XmlText delta segments into MDAST phrasing content.\r\n * Handles marks (bold, italic, strike, code, link) and hardBreak embeds.\r\n */\r\nfunction deltaToPhrasingContent(el: Y.XmlElement): PhrasingContent[] {\r\n const result: PhrasingContent[] = [];\r\n\r\n for (let i = 0; i < el.length; i++) {\r\n const child = el.get(i);\r\n\r\n if (child instanceof Y.XmlText) {\r\n const delta = child.toDelta();\r\n for (const op of delta) {\r\n // Embedded elements (hardBreak, etc.)\r\n if (typeof op.insert !== 'string') {\r\n if (op.insert instanceof Y.XmlElement && op.insert.nodeName === 'hardBreak') {\r\n result.push({ type: 'break' });\r\n }\r\n continue;\r\n }\r\n\r\n const text = op.insert;\r\n if (text.length === 0) continue;\r\n\r\n // Collect marks from delta attributes\r\n const attrs = op.attributes || {};\r\n const marks = new Map<string, any>();\r\n for (const [key, value] of Object.entries(attrs)) {\r\n marks.set(stripHashSuffix(key), value);\r\n }\r\n\r\n // Build phrasing node, wrapping with marks from inside out\r\n let node: PhrasingContent = { type: 'text', value: text };\r\n\r\n // link wraps first (innermost), then code, then strike, then italic, then bold\r\n if (marks.has('link')) {\r\n const linkAttrs = marks.get('link') || {};\r\n node = {\r\n type: 'link',\r\n url: linkAttrs.href || '',\r\n ...(linkAttrs.title ? { title: linkAttrs.title } : {}),\r\n children: [node],\r\n };\r\n }\r\n if (marks.has('code')) {\r\n // Code is a leaf node — extract text value\r\n node = { type: 'inlineCode', value: text };\r\n }\r\n if (marks.has('strike')) {\r\n if (node.type === 'inlineCode') {\r\n // Can't nest inlineCode inside delete — best effort\r\n node = { type: 'delete', children: [{ type: 'text', value: text }] } as any;\r\n } else {\r\n node = { type: 'delete', children: [node] } as any;\r\n }\r\n }\r\n if (marks.has('italic')) {\r\n if (node.type === 'inlineCode') {\r\n node = { type: 'emphasis', children: [{ type: 'text', value: text }] };\r\n } else {\r\n node = { type: 'emphasis', children: [node] };\r\n }\r\n }\r\n if (marks.has('bold')) {\r\n if (node.type === 'inlineCode') {\r\n node = { type: 'strong', children: [{ type: 'text', value: text }] };\r\n } else {\r\n node = { type: 'strong', children: [node] };\r\n }\r\n }\r\n\r\n result.push(node);\r\n }\r\n } else if (child instanceof Y.XmlElement) {\r\n // Non-text child elements embedded in a block (shouldn't happen often)\r\n if (child.nodeName === 'hardBreak') {\r\n result.push({ type: 'break' });\r\n }\r\n }\r\n }\r\n\r\n return result;\r\n}\r\n","import * as Y from 'yjs';\r\nimport { unified } from 'unified';\r\nimport remarkParse from 'remark-parse';\r\nimport remarkGfm from 'remark-gfm';\r\nimport remarkStringify from 'remark-stringify';\r\nimport type { Root } from 'mdast';\r\nimport { mdastToYDoc, yDocToMdast } from './mdast-ydoc.js';\r\n\r\n// Cached processors — stateless and safe to reuse across calls\r\nconst parser = unified().use(remarkParse).use(remarkGfm).freeze();\r\nconst serializer = unified()\r\n .use(remarkGfm)\r\n .use(remarkStringify, {\r\n bullet: '-',\r\n emphasis: '*',\r\n strong: '*',\r\n listItemIndent: 'one',\r\n rule: '-',\r\n })\r\n .freeze();\r\n\r\n/** Parse markdown string and populate a Y.Doc's XmlFragment */\r\nexport function loadMarkdown(doc: Y.Doc, markdown: string): void {\r\n const tree = parser.parse(markdown) as Root;\r\n mdastToYDoc(doc, tree);\r\n}\r\n\r\n/** Serialize a Y.Doc's XmlFragment back to markdown */\r\nexport function saveMarkdown(doc: Y.Doc): string {\r\n const tree = yDocToMdast(doc);\r\n return serializer.stringify(tree);\r\n}\r\n","/**\r\n * Shared offset math for the flat-text coordinate system.\r\n *\r\n * The server's extractText() builds a flat string from Y.Doc elements by:\r\n * 1. Prepending heading prefixes (\"# \", \"## \", \"### \") to heading content\r\n * 2. Joining elements with \"\\n\" separators\r\n *\r\n * Both server (Y.Doc → flat offsets) and client (ProseMirror positions ↔ flat offsets)\r\n * must agree on these conventions. This module is the single source of truth.\r\n */\r\n\r\n/** Flat-text separator between block elements. */\r\nexport const FLAT_SEPARATOR = '\\n';\r\n\r\n/**\r\n * Length of the heading prefix in flat text for a given heading level.\r\n * Level 1 → \"# \" (2 chars), level 2 → \"## \" (3 chars), etc.\r\n * Returns 0 for non-heading nodes (level null/undefined/0).\r\n */\r\nexport function headingPrefixLength(level: number | null | undefined): number {\r\n if (!level) return 0;\r\n return level + 1;\r\n}\r\n\r\n/**\r\n * Build the heading prefix string for a given level.\r\n * Level 1 → \"# \", level 2 → \"## \", etc.\r\n */\r\nexport function headingPrefix(level: number): string {\r\n return '#'.repeat(level) + ' ';\r\n}\r\n","import path from \"path\";\r\nimport * as Y from \"yjs\";\r\nimport {\r\n headingPrefixLength as sharedHeadingPrefixLength,\r\n headingPrefix,\r\n FLAT_SEPARATOR,\r\n} from \"../../shared/offsets.js\";\r\nimport { saveMarkdown } from \"../file-io/markdown.js\";\r\n\r\n/**\r\n * Detect file format from extension.\r\n */\r\nexport function detectFormat(filePath: string): string {\r\n const ext = path.extname(filePath).toLowerCase();\r\n switch (ext) {\r\n case \".md\":\r\n return \"md\";\r\n case \".txt\":\r\n return \"txt\";\r\n case \".html\":\r\n case \".htm\":\r\n return \"html\";\r\n case \".docx\":\r\n return \"docx\";\r\n default:\r\n return \"txt\";\r\n }\r\n}\r\n\r\n/**\r\n * Generate a stable, readable document ID from a file path.\r\n * Used as both the map key and the Hocuspocus room name.\r\n */\r\nexport function docIdFromPath(filePath: string): string {\r\n const normalized = filePath.replace(/\\\\/g, \"/\").toLowerCase();\r\n let hash = 0;\r\n for (let i = 0; i < normalized.length; i++) {\r\n hash = ((hash << 5) - hash + normalized.charCodeAt(i)) | 0;\r\n }\r\n const name = path\r\n .basename(normalized, path.extname(normalized))\r\n .replace(/[^a-zA-Z0-9]/g, \"-\")\r\n .replace(/-+/g, \"-\")\r\n .slice(0, 16);\r\n return `${name}-${Math.abs(hash).toString(36).slice(0, 6)}`;\r\n}\r\n\r\n/** Insert text content into a Y.Doc's XmlFragment as paragraphs */\r\nexport function populateYDoc(doc: Y.Doc, text: string): void {\r\n const fragment = doc.getXmlFragment(\"default\");\r\n\r\n if (fragment.length > 0) {\r\n fragment.delete(0, fragment.length);\r\n }\r\n\r\n if (text === \"\") return;\r\n\r\n const lines = text.split(\"\\n\");\r\n for (const line of lines) {\r\n if (line === \"\") {\r\n const empty = new Y.XmlElement(\"paragraph\");\r\n empty.insert(0, [new Y.XmlText(\"\")]);\r\n fragment.insert(fragment.length, [empty]);\r\n continue;\r\n }\r\n\r\n let element: Y.XmlElement;\r\n\r\n if (line.startsWith(\"### \")) {\r\n element = new Y.XmlElement(\"heading\");\r\n element.setAttribute(\"level\", 3 as any);\r\n element.insert(0, [new Y.XmlText(line.slice(4))]);\r\n } else if (line.startsWith(\"## \")) {\r\n element = new Y.XmlElement(\"heading\");\r\n element.setAttribute(\"level\", 2 as any);\r\n element.insert(0, [new Y.XmlText(line.slice(3))]);\r\n } else if (line.startsWith(\"# \")) {\r\n element = new Y.XmlElement(\"heading\");\r\n element.setAttribute(\"level\", 1 as any);\r\n element.insert(0, [new Y.XmlText(line.slice(2))]);\r\n } else {\r\n element = new Y.XmlElement(\"paragraph\");\r\n element.insert(0, [new Y.XmlText(line)]);\r\n }\r\n\r\n fragment.insert(fragment.length, [element]);\r\n }\r\n}\r\n\r\n/**\r\n * Extract plain text from a Y.XmlElement by recursively collecting Y.XmlText content.\r\n */\r\nexport function getElementText(element: Y.XmlElement): string {\r\n const parts: string[] = [];\r\n for (let i = 0; i < element.length; i++) {\r\n const child = element.get(i);\r\n if (child instanceof Y.XmlText) {\r\n parts.push(child.toString());\r\n } else if (child instanceof Y.XmlElement) {\r\n parts.push(getElementText(child));\r\n }\r\n }\r\n return parts.join(\"\");\r\n}\r\n\r\n/** Extract plain text from a Y.Doc's XmlFragment */\r\nexport function extractText(doc: Y.Doc): string {\r\n const fragment = doc.getXmlFragment(\"default\");\r\n const lines: string[] = [];\r\n\r\n for (let i = 0; i < fragment.length; i++) {\r\n const node = fragment.get(i);\r\n if (node instanceof Y.XmlElement) {\r\n const text = getElementText(node);\r\n if (node.nodeName === \"heading\") {\r\n const level = Number(node.getAttribute(\"level\") ?? 1);\r\n lines.push(headingPrefix(level) + text);\r\n } else {\r\n lines.push(text);\r\n }\r\n }\r\n }\r\n\r\n return lines.join(FLAT_SEPARATOR);\r\n}\r\n\r\n/**\r\n * Extract readable markdown from a Y.Doc via remark serialization.\r\n * NOT used by resolveOffset or tandem_edit (those use extractText).\r\n */\r\nexport function extractMarkdown(doc: Y.Doc): string {\r\n return saveMarkdown(doc).trimEnd();\r\n}\r\n\r\n/**\r\n * Get the heading prefix length for a Y.XmlElement.\r\n * Delegates to shared headingPrefixLength for the actual math.\r\n */\r\nexport function getHeadingPrefixLength(node: Y.XmlElement): number {\r\n if (node.nodeName === \"heading\") {\r\n const level = Number(node.getAttribute(\"level\") ?? 1);\r\n return sharedHeadingPrefixLength(level);\r\n }\r\n return 0;\r\n}\r\n\r\n// -- Range staleness detection ------------------------------------------------\r\n\r\nexport type RangeVerifyResult =\r\n | { valid: true }\r\n | { valid: false; gone: true }\r\n | { valid: false; gone: false; resolvedFrom: number; resolvedTo: number };\r\n\r\n/**\r\n * Check whether [from, to] still contains textSnapshot. If not, search the\r\n * full document and return the relocated range or { gone: true }.\r\n */\r\nexport function verifyAndResolveRange(\r\n doc: Y.Doc,\r\n from: number,\r\n to: number,\r\n textSnapshot: string | undefined,\r\n): RangeVerifyResult {\r\n if (!textSnapshot) return { valid: true };\r\n const fullText = extractText(doc);\r\n if (fullText.slice(from, to) === textSnapshot) return { valid: true };\r\n const candidates: number[] = [];\r\n let searchFrom = 0;\r\n while (true) {\r\n const idx = fullText.indexOf(textSnapshot, searchFrom);\r\n if (idx === -1) break;\r\n candidates.push(idx);\r\n searchFrom = idx + 1;\r\n }\r\n if (candidates.length === 0) return { valid: false, gone: true };\r\n const best = candidates.reduce((a, b) => (Math.abs(a - from) <= Math.abs(b - from) ? a : b));\r\n return { valid: false, gone: false, resolvedFrom: best, resolvedTo: best + textSnapshot.length };\r\n}\r\n\r\nexport interface ResolvedOffset {\r\n elementIndex: number;\r\n textOffset: number;\r\n /** True if the original offset fell inside a heading prefix and was clamped to 0 */\r\n clampedFromPrefix: boolean;\r\n}\r\n\r\n/**\r\n * Resolve a flat character offset (from extractText) to a Y.Doc element position.\r\n */\r\nexport function resolveOffset(fragment: Y.XmlFragment, charOffset: number): ResolvedOffset | null {\r\n let accumulated = 0;\r\n\r\n for (let i = 0; i < fragment.length; i++) {\r\n const node = fragment.get(i);\r\n if (!(node instanceof Y.XmlElement)) continue;\r\n\r\n const prefixLen = getHeadingPrefixLength(node);\r\n const text = getElementText(node);\r\n const fullLen = prefixLen + text.length;\r\n\r\n if (accumulated + fullLen > charOffset) {\r\n const offsetInFull = charOffset - accumulated;\r\n const clampedFromPrefix = offsetInFull < prefixLen && prefixLen > 0;\r\n const textOffset = Math.max(0, offsetInFull - prefixLen);\r\n return { elementIndex: i, textOffset, clampedFromPrefix };\r\n }\r\n\r\n accumulated += fullLen;\r\n\r\n if (i < fragment.length - 1) {\r\n accumulated += 1;\r\n if (accumulated > charOffset) {\r\n return { elementIndex: i, textOffset: text.length, clampedFromPrefix: false };\r\n }\r\n }\r\n }\r\n\r\n if (fragment.length > 0) {\r\n const lastNode = fragment.get(fragment.length - 1);\r\n if (lastNode instanceof Y.XmlElement) {\r\n return {\r\n elementIndex: fragment.length - 1,\r\n textOffset: getElementText(lastNode).length,\r\n clampedFromPrefix: false,\r\n };\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n\r\n/**\r\n * Find the first Y.XmlText child of a Y.XmlElement (read-only).\r\n * Returns null if no XmlText child exists.\r\n */\r\nexport function findXmlText(element: Y.XmlElement): Y.XmlText | null {\r\n for (let i = 0; i < element.length; i++) {\r\n const child = element.get(i);\r\n if (child instanceof Y.XmlText) {\r\n return child;\r\n }\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Find the first Y.XmlText child of a Y.XmlElement.\r\n * Creates one if the element is empty.\r\n */\r\nexport function getOrCreateXmlText(element: Y.XmlElement): Y.XmlText {\r\n return (\r\n findXmlText(element) ??\r\n (() => {\r\n const textNode = new Y.XmlText(\"\");\r\n element.insert(0, [textNode]);\r\n return textNode;\r\n })()\r\n );\r\n}\r\n\r\n/**\r\n * Convert a flat text offset to a JSON-serialized Yjs RelativePosition.\r\n * Returns null if the offset falls in a heading prefix or can't be resolved.\r\n */\r\nfunction flatOffsetToRelPos(doc: Y.Doc, offset: number, assoc: 0 | -1): unknown | null {\r\n const fragment = doc.getXmlFragment(\"default\");\r\n const resolved = resolveOffset(fragment, offset);\r\n if (!resolved || resolved.clampedFromPrefix) return null;\r\n\r\n const node = fragment.get(resolved.elementIndex);\r\n if (!(node instanceof Y.XmlElement)) return null;\r\n\r\n const xmlText = getOrCreateXmlText(node);\r\n const rpos = Y.createRelativePositionFromTypeIndex(xmlText, resolved.textOffset, assoc);\r\n return Y.relativePositionToJSON(rpos);\r\n}\r\n\r\n/**\r\n * Resolve a JSON-serialized Yjs RelativePosition back to a flat text offset.\r\n * Returns null if the referenced content was deleted.\r\n */\r\nfunction relPosToFlatOffset(doc: Y.Doc, relPosJson: unknown): number | null {\r\n const rpos = Y.createRelativePositionFromJSON(relPosJson);\r\n const absPos = Y.createAbsolutePositionFromRelativePosition(rpos, doc);\r\n if (!absPos) return null;\r\n\r\n const fragment = doc.getXmlFragment(\"default\");\r\n let accumulated = 0;\r\n\r\n for (let i = 0; i < fragment.length; i++) {\r\n const node = fragment.get(i);\r\n if (!(node instanceof Y.XmlElement)) continue;\r\n\r\n const prefixLen = getHeadingPrefixLength(node);\r\n const text = getElementText(node);\r\n\r\n // Check if this element contains the resolved XmlText (read-only)\r\n const xmlText = findXmlText(node);\r\n if (xmlText && xmlText === absPos.type) {\r\n return accumulated + prefixLen + absPos.index;\r\n }\r\n\r\n accumulated += prefixLen + text.length;\r\n\r\n // Separator between elements (not after last)\r\n if (i < fragment.length - 1) {\r\n accumulated += 1;\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n","// HTML → Y.Doc conversion: htmlparser2 DOM traversal → Yjs XmlFragment\r\n\r\nimport * as htmlparser2 from \"htmlparser2\";\r\nimport type { Element, Text, ChildNode } from \"domhandler\";\r\nimport * as Y from \"yjs\";\r\n\r\n/** All marks that can appear on inline text (superset of mdast-ydoc) */\r\nconst ALL_MARKS = [\r\n \"bold\",\r\n \"italic\",\r\n \"strike\",\r\n \"code\",\r\n \"link\",\r\n \"underline\",\r\n \"superscript\",\r\n \"subscript\",\r\n] as const;\r\n\r\n/** Map HTML tag names to the mark they apply */\r\nconst INLINE_MARK_TAGS: Record<string, (el: Element) => Record<string, object>> = {\r\n strong: () => ({ bold: {} }),\r\n b: () => ({ bold: {} }),\r\n em: () => ({ italic: {} }),\r\n i: () => ({ italic: {} }),\r\n u: () => ({ underline: {} }),\r\n s: () => ({ strike: {} }),\r\n del: () => ({ strike: {} }),\r\n sup: () => ({ superscript: {} }),\r\n sub: () => ({ subscript: {} }),\r\n a: (el) => ({\r\n link: { href: el.attribs.href || \"\" },\r\n }),\r\n};\r\n\r\n/** Tags that represent block-level elements */\r\nconst BLOCK_TAGS = new Set([\r\n \"h1\",\r\n \"h2\",\r\n \"h3\",\r\n \"h4\",\r\n \"h5\",\r\n \"h6\",\r\n \"p\",\r\n \"ul\",\r\n \"ol\",\r\n \"li\",\r\n \"blockquote\",\r\n \"table\",\r\n \"tr\",\r\n \"td\",\r\n \"th\",\r\n \"pre\",\r\n \"img\",\r\n \"hr\",\r\n \"br\",\r\n \"div\",\r\n]);\r\n\r\ntype DeferredText = { xmlText: Y.XmlText; children: ChildNode[]; marks: Record<string, object> };\r\n\r\n/**\r\n * Build Yjs insert attributes from the current mark stack.\r\n * Explicitly sets null for inactive marks to prevent Yjs mark inheritance.\r\n */\r\nfunction buildAttrs(marks: Record<string, object>): Record<string, object | null> {\r\n const attrs: Record<string, object | null> = {};\r\n for (const name of ALL_MARKS) {\r\n attrs[name] = name in marks ? marks[name] : null;\r\n }\r\n return attrs;\r\n}\r\n\r\nfunction isElement(node: ChildNode): node is Element {\r\n return node.type === \"tag\";\r\n}\r\n\r\nfunction isText(node: ChildNode): node is Text {\r\n return node.type === \"text\";\r\n}\r\n\r\n/**\r\n * Convert parsed HTML into Y.Doc XmlFragment elements.\r\n * Two-pass pattern per ADR-009: build element tree first, then populate text.\r\n */\r\nexport function htmlToYDoc(doc: Y.Doc, html: string): void {\r\n const fragment = doc.getXmlFragment(\"default\");\r\n\r\n // Clear existing content\r\n if (fragment.length > 0) {\r\n fragment.delete(0, fragment.length);\r\n }\r\n\r\n if (!html.trim()) return;\r\n\r\n const parsed = htmlparser2.parseDocument(html);\r\n const deferred: DeferredText[] = [];\r\n const allElements: Y.XmlElement[] = [];\r\n\r\n // Pass 1: build element tree, collect deferred text ops\r\n for (const child of parsed.children) {\r\n allElements.push(...domNodeToYxml(child, deferred));\r\n }\r\n\r\n // Attach all elements to the doc\r\n if (allElements.length > 0) {\r\n fragment.insert(0, allElements);\r\n }\r\n\r\n // Pass 2: populate text now that elements are attached to Y.Doc\r\n for (const { xmlText, children, marks } of deferred) {\r\n processInlineNodes(xmlText, children, marks);\r\n }\r\n}\r\n\r\n/** Convert a DOM node to Y.XmlElement(s). Inline-only containers become paragraphs. */\r\nfunction domNodeToYxml(node: ChildNode, deferred: DeferredText[]): Y.XmlElement[] {\r\n if (isText(node)) {\r\n // Top-level text node — wrap in paragraph\r\n const text = node.data;\r\n if (!text.trim()) return [];\r\n const el = new Y.XmlElement(\"paragraph\");\r\n const xmlText = new Y.XmlText();\r\n el.insert(0, [xmlText]);\r\n deferred.push({ xmlText, children: [node], marks: {} });\r\n return [el];\r\n }\r\n\r\n if (!isElement(node)) return [];\r\n\r\n const tag = node.tagName.toLowerCase();\r\n\r\n // Heading\r\n const headingMatch = tag.match(/^h([1-6])$/);\r\n if (headingMatch) {\r\n const el = new Y.XmlElement(\"heading\");\r\n el.setAttribute(\"level\", parseInt(headingMatch[1]) as any);\r\n const xmlText = new Y.XmlText();\r\n el.insert(0, [xmlText]);\r\n deferred.push({ xmlText, children: node.children, marks: {} });\r\n return [el];\r\n }\r\n\r\n switch (tag) {\r\n case \"p\": {\r\n const el = new Y.XmlElement(\"paragraph\");\r\n const xmlText = new Y.XmlText();\r\n el.insert(0, [xmlText]);\r\n deferred.push({ xmlText, children: node.children, marks: {} });\r\n return [el];\r\n }\r\n\r\n case \"blockquote\": {\r\n const el = new Y.XmlElement(\"blockquote\");\r\n const blockChildren = collectBlockChildren(node.children, deferred);\r\n for (const child of blockChildren) {\r\n el.insert(el.length, [child]);\r\n }\r\n return [el];\r\n }\r\n\r\n case \"ul\": {\r\n const el = new Y.XmlElement(\"bulletList\");\r\n for (const child of node.children) {\r\n if (isElement(child) && child.tagName.toLowerCase() === \"li\") {\r\n el.insert(el.length, [buildListItem(child, deferred)]);\r\n }\r\n }\r\n return [el];\r\n }\r\n\r\n case \"ol\": {\r\n const el = new Y.XmlElement(\"orderedList\");\r\n const start = parseInt(node.attribs.start || \"1\");\r\n if (start !== 1) {\r\n el.setAttribute(\"start\", start as any);\r\n }\r\n for (const child of node.children) {\r\n if (isElement(child) && child.tagName.toLowerCase() === \"li\") {\r\n el.insert(el.length, [buildListItem(child, deferred)]);\r\n }\r\n }\r\n return [el];\r\n }\r\n\r\n case \"table\": {\r\n const el = new Y.XmlElement(\"table\");\r\n // Walk tbody/thead/tfoot or direct tr children\r\n const rows = collectTableRows(node);\r\n for (const row of rows) {\r\n el.insert(el.length, [buildTableRow(row, deferred)]);\r\n }\r\n return [el];\r\n }\r\n\r\n case \"pre\": {\r\n const el = new Y.XmlElement(\"codeBlock\");\r\n const xmlText = new Y.XmlText();\r\n el.insert(0, [xmlText]);\r\n // Collect all text content from pre (which may contain a <code> child)\r\n deferred.push({ xmlText, children: node.children, marks: {} });\r\n return [el];\r\n }\r\n\r\n case \"img\": {\r\n const el = new Y.XmlElement(\"image\");\r\n el.setAttribute(\"src\", node.attribs.src || \"\");\r\n if (node.attribs.alt) el.setAttribute(\"alt\", node.attribs.alt);\r\n if (node.attribs.title) el.setAttribute(\"title\", node.attribs.title);\r\n return [el];\r\n }\r\n\r\n case \"hr\": {\r\n return [new Y.XmlElement(\"horizontalRule\")];\r\n }\r\n\r\n case \"br\": {\r\n // Top-level <br> — produce an empty paragraph\r\n const el = new Y.XmlElement(\"paragraph\");\r\n el.insert(0, [new Y.XmlText(\"\")]);\r\n return [el];\r\n }\r\n\r\n case \"div\": {\r\n // Recurse into div, treating it as a transparent container\r\n const results: Y.XmlElement[] = [];\r\n for (const child of node.children) {\r\n results.push(...domNodeToYxml(child, deferred));\r\n }\r\n return results;\r\n }\r\n\r\n default: {\r\n // Unknown block tag or inline-as-block: wrap in paragraph\r\n if (hasBlockChildren(node)) {\r\n // Contains blocks — recurse\r\n const results: Y.XmlElement[] = [];\r\n for (const child of node.children) {\r\n results.push(...domNodeToYxml(child, deferred));\r\n }\r\n return results;\r\n }\r\n // Pure inline content — wrap in paragraph\r\n const el = new Y.XmlElement(\"paragraph\");\r\n const xmlText = new Y.XmlText();\r\n el.insert(0, [xmlText]);\r\n deferred.push({ xmlText, children: node.children, marks: {} });\r\n return [el];\r\n }\r\n }\r\n}\r\n\r\n/** Check if a node has any block-level element children */\r\nfunction hasBlockChildren(node: Element): boolean {\r\n return node.children.some(\r\n (child) => isElement(child) && BLOCK_TAGS.has(child.tagName.toLowerCase()),\r\n );\r\n}\r\n\r\n/** Collect block children from a list of DOM nodes, wrapping stray text in paragraphs */\r\nfunction collectBlockChildren(children: ChildNode[], deferred: DeferredText[]): Y.XmlElement[] {\r\n const result: Y.XmlElement[] = [];\r\n let inlineBuffer: ChildNode[] = [];\r\n\r\n const flushInline = () => {\r\n if (inlineBuffer.length === 0) return;\r\n // Only flush if there's non-whitespace content\r\n const hasContent = inlineBuffer.some((n) => (isText(n) ? n.data.trim().length > 0 : true));\r\n if (hasContent) {\r\n const el = new Y.XmlElement(\"paragraph\");\r\n const xmlText = new Y.XmlText();\r\n el.insert(0, [xmlText]);\r\n deferred.push({ xmlText, children: inlineBuffer, marks: {} });\r\n result.push(el);\r\n }\r\n inlineBuffer = [];\r\n };\r\n\r\n for (const child of children) {\r\n if (isElement(child) && BLOCK_TAGS.has(child.tagName.toLowerCase())) {\r\n flushInline();\r\n result.push(...domNodeToYxml(child, deferred));\r\n } else {\r\n inlineBuffer.push(child);\r\n }\r\n }\r\n flushInline();\r\n\r\n // Ensure at least one paragraph (Tiptap requires content in block containers)\r\n if (result.length === 0) {\r\n const el = new Y.XmlElement(\"paragraph\");\r\n el.insert(0, [new Y.XmlText(\"\")]);\r\n result.push(el);\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/** Build a listItem Y.XmlElement from an <li> DOM node */\r\nfunction buildListItem(li: Element, deferred: DeferredText[]): Y.XmlElement {\r\n const listItem = new Y.XmlElement(\"listItem\");\r\n const blockChildren = collectBlockChildren(li.children, deferred);\r\n for (const child of blockChildren) {\r\n listItem.insert(listItem.length, [child]);\r\n }\r\n return listItem;\r\n}\r\n\r\n/** Collect all <tr> elements from a <table>, walking through tbody/thead/tfoot */\r\nfunction collectTableRows(table: Element): Element[] {\r\n const rows: Element[] = [];\r\n for (const child of table.children) {\r\n if (!isElement(child)) continue;\r\n const tag = child.tagName.toLowerCase();\r\n if (tag === \"tr\") {\r\n rows.push(child);\r\n } else if (tag === \"thead\" || tag === \"tbody\" || tag === \"tfoot\") {\r\n for (const grandchild of child.children) {\r\n if (isElement(grandchild) && grandchild.tagName.toLowerCase() === \"tr\") {\r\n rows.push(grandchild);\r\n }\r\n }\r\n }\r\n }\r\n return rows;\r\n}\r\n\r\n/** Build a tableRow Y.XmlElement from a <tr> */\r\nfunction buildTableRow(tr: Element, deferred: DeferredText[]): Y.XmlElement {\r\n const row = new Y.XmlElement(\"tableRow\");\r\n for (const child of tr.children) {\r\n if (!isElement(child)) continue;\r\n const tag = child.tagName.toLowerCase();\r\n if (tag === \"td\" || tag === \"th\") {\r\n const nodeName = tag === \"th\" ? \"tableHeader\" : \"tableCell\";\r\n const cell = new Y.XmlElement(nodeName);\r\n\r\n // Copy colspan/rowspan\r\n if (child.attribs.colspan && child.attribs.colspan !== \"1\") {\r\n cell.setAttribute(\"colspan\", parseInt(child.attribs.colspan) as any);\r\n }\r\n if (child.attribs.rowspan && child.attribs.rowspan !== \"1\") {\r\n cell.setAttribute(\"rowspan\", parseInt(child.attribs.rowspan) as any);\r\n }\r\n\r\n // Tiptap requires cells to contain block elements (content: 'block+')\r\n const cellBlocks = collectBlockChildren(child.children, deferred);\r\n for (const block of cellBlocks) {\r\n cell.insert(cell.length, [block]);\r\n }\r\n\r\n row.insert(row.length, [cell]);\r\n }\r\n }\r\n return row;\r\n}\r\n\r\n/**\r\n * Process inline DOM nodes into a Y.XmlText with marks.\r\n * Uses insert-with-attributes per ADR-009.\r\n */\r\nfunction processInlineNodes(\r\n xmlText: Y.XmlText,\r\n nodes: ChildNode[],\r\n marks: Record<string, object>,\r\n): void {\r\n for (const node of nodes) {\r\n if (isText(node)) {\r\n const text = node.data;\r\n if (text.length > 0) {\r\n xmlText.insert(xmlText.length, text, buildAttrs(marks));\r\n }\r\n continue;\r\n }\r\n\r\n if (!isElement(node)) continue;\r\n\r\n const tag = node.tagName.toLowerCase();\r\n\r\n // Hard break\r\n if (tag === \"br\") {\r\n const embed = new Y.XmlElement(\"hardBreak\");\r\n xmlText.insertEmbed(xmlText.length, embed);\r\n continue;\r\n }\r\n\r\n // Inline mark tag?\r\n const markFactory = INLINE_MARK_TAGS[tag];\r\n if (markFactory) {\r\n const newMarks = { ...marks, ...markFactory(node) };\r\n processInlineNodes(xmlText, node.children, newMarks);\r\n continue;\r\n }\r\n\r\n // Code element inside pre — just extract text\r\n if (tag === \"code\") {\r\n processInlineNodes(xmlText, node.children, marks);\r\n continue;\r\n }\r\n\r\n // Unknown inline element — recurse (best effort)\r\n processInlineNodes(xmlText, node.children, marks);\r\n }\r\n}\r\n","// .docx review-only mode: mammoth.js → HTML → Y.Doc\r\n// Editing disabled; annotations persist via session system\r\n\r\nimport mammoth from \"mammoth\";\r\nimport * as Y from \"yjs\";\r\nimport type { Annotation } from \"../../shared/types.js\";\r\nimport { getElementText } from \"../mcp/document-model.js\";\r\n\r\n// Re-export for backward compatibility — consumers can import from either module\r\nexport { htmlToYDoc } from \"./docx-html.js\";\r\n\r\n/**\r\n * Convert a .docx buffer to HTML via mammoth.js.\r\n * Warnings logged to stderr (stdout reserved for MCP).\r\n */\r\nexport async function loadDocx(content: Buffer): Promise<string> {\r\n const result = await mammoth.convertToHtml({ buffer: content });\r\n\r\n for (const msg of result.messages) {\r\n console.error(`[mammoth] ${msg.type}: ${msg.message}`);\r\n }\r\n\r\n return result.value;\r\n}\r\n\r\n// -- Annotation export --\r\n\r\n/**\r\n * Generate a Markdown summary of all annotations, grouped by type.\r\n * Includes a text snippet from the document for context.\r\n */\r\nexport function exportAnnotations(doc: Y.Doc, annotations: Annotation[]): string {\r\n if (annotations.length === 0) {\r\n return \"# Document Review\\n\\nNo annotations found.\";\r\n }\r\n\r\n const fragment = doc.getXmlFragment(\"default\");\r\n const fullText = extractFullText(fragment);\r\n\r\n const groups: Record<string, Annotation[]> = {};\r\n for (const ann of annotations) {\r\n const key = ann.type;\r\n if (!groups[key]) groups[key] = [];\r\n groups[key].push(ann);\r\n }\r\n\r\n const lines: string[] = [\"# Document Review\", \"\"];\r\n\r\n const typeLabels: Record<string, string> = {\r\n highlight: \"Highlights\",\r\n comment: \"Comments\",\r\n suggestion: \"Suggestions\",\r\n overlay: \"Overlays\",\r\n question: \"Questions\",\r\n flag: \"Flags\",\r\n };\r\n\r\n for (const [type, anns] of Object.entries(groups)) {\r\n lines.push(`## ${typeLabels[type] || type}`, \"\");\r\n\r\n for (const ann of anns) {\r\n const snippet = safeSlice(fullText, ann.range.from, ann.range.to);\r\n const truncated = snippet.length > 80 ? snippet.slice(0, 77) + \"...\" : snippet;\r\n\r\n lines.push(`- **\"${truncated}\"** (${ann.author})`);\r\n\r\n if (ann.type === \"suggestion\") {\r\n try {\r\n const { newText, reason } = JSON.parse(ann.content);\r\n lines.push(` - Replace with: \"${newText}\"`);\r\n if (reason) lines.push(` - Reason: ${reason}`);\r\n } catch {\r\n lines.push(` - ${ann.content}`);\r\n }\r\n } else if (ann.content) {\r\n lines.push(` - ${ann.content}`);\r\n }\r\n\r\n if (ann.color) {\r\n lines.push(` - Color: ${ann.color}`);\r\n }\r\n\r\n lines.push(\"\");\r\n }\r\n }\r\n\r\n return lines.join(\"\\n\").trimEnd();\r\n}\r\n\r\n/** Extract full flat text from a Y.Doc fragment (simplified — no heading prefixes) */\r\nfunction extractFullText(fragment: Y.XmlFragment): string {\r\n const parts: string[] = [];\r\n for (let i = 0; i < fragment.length; i++) {\r\n const node = fragment.get(i);\r\n if (node instanceof Y.XmlElement) {\r\n parts.push(getElementText(node));\r\n }\r\n }\r\n return parts.join(\"\\n\");\r\n}\r\n\r\n/** Safe string slice that handles out-of-bounds gracefully */\r\nfunction safeSlice(text: string, from: number, to: number): string {\r\n const start = Math.max(0, Math.min(from, text.length));\r\n const end = Math.max(start, Math.min(to, text.length));\r\n return text.slice(start, end);\r\n}\r\n","/**\r\n * Shared types for the position/coordinate system.\r\n *\r\n * Three coordinate systems exist in Tandem:\r\n * 1. Flat text offsets — server-side, includes heading prefixes and \\n separators\r\n * 2. ProseMirror positions — client-side, structural node positions\r\n * 3. Yjs RelativePositions — CRDT-anchored, survive concurrent edits\r\n *\r\n * This module defines the shared vocabulary. Environment-specific logic lives in:\r\n * - src/server/positions.ts (Y.Doc operations)\r\n * - src/client/positions.ts (ProseMirror operations)\r\n */\r\n\r\n// ---------------------------------------------------------------------------\r\n// Branded types — compile-time guards against mixing coordinate systems\r\n// ---------------------------------------------------------------------------\r\n\r\ndeclare const FlatOffsetBrand: unique symbol;\r\ndeclare const PmPosBrand: unique symbol;\r\ndeclare const SerializedRelPosBrand: unique symbol;\r\n\r\n/** Flat text offset (includes heading prefixes & \\n separators). Server/MCP boundary. */\r\nexport type FlatOffset = number & { readonly [FlatOffsetBrand]: true };\r\n\r\n/** ProseMirror position (structural node boundaries). Client-side only. */\r\nexport type PmPos = number & { readonly [PmPosBrand]: true };\r\n\r\n/** JSON-serialized Y.js RelativePosition. Opaque — only created/consumed by position modules. */\r\nexport type SerializedRelPos = unknown & { readonly [SerializedRelPosBrand]: true };\r\n\r\n// ---------------------------------------------------------------------------\r\n// Factory functions — cast raw values into branded types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport const toFlatOffset = (n: number): FlatOffset => n as FlatOffset;\r\nexport const toPmPos = (n: number): PmPos => n as PmPos;\r\nexport const toSerializedRelPos = (json: unknown): SerializedRelPos => json as SerializedRelPos;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Range and result types\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Flat-offset range used by MCP tools and annotations. */\r\nexport interface DocumentRange {\r\n from: FlatOffset;\r\n to: FlatOffset;\r\n}\r\n\r\n/** CRDT-anchored range that survives concurrent edits. Serialized via Y.relativePositionToJSON(). */\r\nexport interface RelativeRange {\r\n fromRel: SerializedRelPos;\r\n toRel: SerializedRelPos;\r\n}\r\n\r\n/** Result of validating a flat-offset range against a document. */\r\nexport type RangeValidation =\r\n | { ok: true; range: DocumentRange }\r\n | { ok: false; code: \"RANGE_GONE\" }\r\n | { ok: false; code: \"RANGE_MOVED\"; resolvedFrom: FlatOffset; resolvedTo: FlatOffset }\r\n | { ok: false; code: \"INVALID_RANGE\"; message: string }\r\n | { ok: false; code: \"HEADING_OVERLAP\" };\r\n\r\n/** Result of anchoredRange: validated flat + CRDT-anchored range ready to store on an Annotation. */\r\nexport type AnchoredRangeResult =\r\n | { ok: true; fullyAnchored: true; range: DocumentRange; relRange: RelativeRange }\r\n | { ok: true; fullyAnchored: false; range: DocumentRange; relRange?: undefined };\r\n\r\n/** A resolved element position inside a Y.Doc XmlFragment. */\r\nexport interface ElementPosition {\r\n elementIndex: number;\r\n /** Character offset within the element's text. Always 0 when clampedFromPrefix is true. */\r\n textOffset: number;\r\n /** True if the original offset fell inside a heading prefix and was clamped to 0 */\r\n clampedFromPrefix: boolean;\r\n}\r\n\r\n/** Resolution method used by annotationToPmRange, for diagnostic observability. */\r\nexport type ResolutionMethod = \"rel\" | \"flat\";\r\n\r\n/** Result of resolving an annotation to ProseMirror positions. */\r\nexport interface PmRangeResult {\r\n from: PmPos;\r\n to: PmPos;\r\n /** Which coordinate path was used to resolve the range. */\r\n method: ResolutionMethod;\r\n}\r\n","export type {\r\n FlatOffset,\r\n PmPos,\r\n SerializedRelPos,\r\n DocumentRange,\r\n RelativeRange,\r\n RangeValidation,\r\n AnchoredRangeResult,\r\n ElementPosition,\r\n ResolutionMethod,\r\n PmRangeResult,\r\n} from \"./types.js\";\r\n\r\nexport {\r\n toFlatOffset,\r\n toPmPos,\r\n toSerializedRelPos,\r\n} from \"./types.js\";\r\n","/**\r\n * Server-side position module.\r\n *\r\n * Consolidates all flat-offset, Y.Doc element resolution, RelativePosition,\r\n * and range validation logic into caller-optimized functions.\r\n *\r\n * High-level (use these):\r\n * - validateRange() — validate + stale-check a flat-offset range\r\n * - anchoredRange() — validate + create both flat and CRDT-anchored range\r\n * - refreshRange() — resolve relRange → flat offsets (or lazily attach)\r\n * - refreshAllRanges() — batch version in a Y.Doc transaction\r\n *\r\n * Low-level (escape hatches):\r\n * - resolveToElement() — flat offset → Y.Doc element position\r\n * - flatOffsetToRelPos() — flat offset → serialized RelativePosition\r\n * - relPosToFlatOffset() — serialized RelativePosition → flat offset\r\n */\r\n\r\nimport * as Y from \"yjs\";\r\nimport type { Annotation } from \"../shared/types.js\";\r\nimport { MCP_ORIGIN } from \"./events/queue.js\";\r\nimport type {\r\n FlatOffset,\r\n SerializedRelPos,\r\n DocumentRange,\r\n RelativeRange,\r\n RangeValidation,\r\n AnchoredRangeResult,\r\n ElementPosition,\r\n} from \"../shared/positions/index.js\";\r\nimport { toFlatOffset, toSerializedRelPos } from \"../shared/positions/index.js\";\r\nimport {\r\n getElementText,\r\n extractText,\r\n findXmlText,\r\n getHeadingPrefixLength,\r\n} from \"./mcp/document-model.js\";\r\n\r\n// ---------------------------------------------------------------------------\r\n// Low-level: element resolution\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Resolve a flat character offset to a Y.Doc element position.\r\n * Needed by tandem_edit for cross-element deletion logic.\r\n */\r\nexport function resolveToElement(\r\n fragment: Y.XmlFragment,\r\n charOffset: FlatOffset,\r\n): ElementPosition | null {\r\n let accumulated = 0;\r\n\r\n for (let i = 0; i < fragment.length; i++) {\r\n const node = fragment.get(i);\r\n if (!(node instanceof Y.XmlElement)) continue;\r\n\r\n const prefixLen = getHeadingPrefixLength(node);\r\n const text = getElementText(node);\r\n const fullLen = prefixLen + text.length;\r\n\r\n if (accumulated + fullLen > charOffset) {\r\n const offsetInFull = charOffset - accumulated;\r\n const clampedFromPrefix = offsetInFull < prefixLen && prefixLen > 0;\r\n const textOffset = Math.max(0, offsetInFull - prefixLen);\r\n return { elementIndex: i, textOffset, clampedFromPrefix };\r\n }\r\n\r\n accumulated += fullLen;\r\n\r\n if (i < fragment.length - 1) {\r\n accumulated += 1; // \\n separator\r\n if (accumulated > charOffset) {\r\n return { elementIndex: i, textOffset: text.length, clampedFromPrefix: false };\r\n }\r\n }\r\n }\r\n\r\n if (fragment.length > 0) {\r\n const lastNode = fragment.get(fragment.length - 1);\r\n if (lastNode instanceof Y.XmlElement) {\r\n return {\r\n elementIndex: fragment.length - 1,\r\n textOffset: getElementText(lastNode).length,\r\n clampedFromPrefix: false,\r\n };\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Low-level: RelativePosition conversion\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Convert a flat text offset to a JSON-serialized Yjs RelativePosition.\r\n * Returns null if the offset falls in a heading prefix or can't be resolved.\r\n */\r\nexport function flatOffsetToRelPos(\r\n doc: Y.Doc,\r\n offset: FlatOffset,\r\n assoc: 0 | -1,\r\n): SerializedRelPos | null {\r\n const fragment = doc.getXmlFragment(\"default\");\r\n const resolved = resolveToElement(fragment, offset);\r\n if (!resolved || resolved.clampedFromPrefix) return null;\r\n\r\n const node = fragment.get(resolved.elementIndex);\r\n if (!(node instanceof Y.XmlElement)) return null;\r\n\r\n const xmlText = findXmlText(node);\r\n if (!xmlText) return null;\r\n const rpos = Y.createRelativePositionFromTypeIndex(xmlText, resolved.textOffset, assoc);\r\n return toSerializedRelPos(Y.relativePositionToJSON(rpos));\r\n}\r\n\r\n/**\r\n * Resolve a JSON-serialized Yjs RelativePosition back to a flat text offset.\r\n * Returns null if the referenced content was deleted.\r\n */\r\nexport function relPosToFlatOffset(doc: Y.Doc, relPosJson: SerializedRelPos): FlatOffset | null {\r\n let absPos;\r\n try {\r\n const rpos = Y.createRelativePositionFromJSON(relPosJson);\r\n absPos = Y.createAbsolutePositionFromRelativePosition(rpos, doc);\r\n } catch (err) {\r\n if (!(err instanceof TypeError) && !(err instanceof SyntaxError)) {\r\n console.error(\"[positions] relPosToFlatOffset: unexpected error resolving relRange:\", err);\r\n }\r\n return null;\r\n }\r\n if (!absPos) return null;\r\n\r\n const fragment = doc.getXmlFragment(\"default\");\r\n let accumulated = 0;\r\n\r\n for (let i = 0; i < fragment.length; i++) {\r\n const node = fragment.get(i);\r\n if (!(node instanceof Y.XmlElement)) continue;\r\n\r\n const prefixLen = getHeadingPrefixLength(node);\r\n const text = getElementText(node);\r\n\r\n const xmlText = findXmlText(node);\r\n if (xmlText && xmlText === absPos.type) {\r\n return toFlatOffset(accumulated + prefixLen + absPos.index);\r\n }\r\n\r\n accumulated += prefixLen + text.length;\r\n if (i < fragment.length - 1) {\r\n accumulated += 1;\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// High-level: range validation\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Validate a flat-offset range against a Y.Doc.\r\n *\r\n * Checks: ordering, textSnapshot staleness (with relocation), and optionally\r\n * heading-prefix overlap. Returns a structured RangeValidation.\r\n *\r\n */\r\nexport function validateRange(\r\n ydoc: Y.Doc,\r\n from: FlatOffset,\r\n to: FlatOffset,\r\n opts?: {\r\n textSnapshot?: string;\r\n rejectHeadingOverlap?: boolean;\r\n },\r\n): RangeValidation {\r\n const rejectHeadingOverlap = opts?.rejectHeadingOverlap ?? false;\r\n\r\n if (from > to) {\r\n return {\r\n ok: false,\r\n code: \"INVALID_RANGE\",\r\n message: `Invalid range: from (${from}) must be <= to (${to}).`,\r\n };\r\n }\r\n\r\n // Staleness check\r\n if (opts?.textSnapshot) {\r\n const fullText = extractText(ydoc);\r\n if (fullText.slice(from, to) !== opts.textSnapshot) {\r\n const candidates: number[] = [];\r\n let searchFrom = 0;\r\n while (true) {\r\n const idx = fullText.indexOf(opts.textSnapshot, searchFrom);\r\n if (idx === -1) break;\r\n candidates.push(idx);\r\n searchFrom = idx + 1;\r\n }\r\n if (candidates.length === 0) {\r\n return { ok: false, code: \"RANGE_GONE\" };\r\n }\r\n const best = candidates.reduce((a, b) => (Math.abs(a - from) <= Math.abs(b - from) ? a : b));\r\n return {\r\n ok: false,\r\n code: \"RANGE_MOVED\",\r\n resolvedFrom: toFlatOffset(best),\r\n resolvedTo: toFlatOffset(best + opts.textSnapshot.length),\r\n };\r\n }\r\n }\r\n\r\n // Heading overlap check\r\n if (rejectHeadingOverlap) {\r\n const fragment = ydoc.getXmlFragment(\"default\");\r\n const startPos = resolveToElement(fragment, from);\r\n const endPos = resolveToElement(fragment, to);\r\n if (!startPos || !endPos) {\r\n return {\r\n ok: false,\r\n code: \"INVALID_RANGE\",\r\n message: `Cannot resolve offset range [${from}, ${to}] in document.`,\r\n };\r\n }\r\n if (startPos.clampedFromPrefix || endPos.clampedFromPrefix) {\r\n return { ok: false, code: \"HEADING_OVERLAP\" };\r\n }\r\n }\r\n\r\n return { ok: true, range: { from, to } };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// High-level: anchored range creation\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Validate a range and create both flat and CRDT-anchored positions in one call.\r\n *\r\n */\r\nexport function anchoredRange(\r\n ydoc: Y.Doc,\r\n from: FlatOffset,\r\n to: FlatOffset,\r\n textSnapshot?: string,\r\n): AnchoredRangeResult | (RangeValidation & { ok: false }) {\r\n const validation = validateRange(ydoc, from, to, { textSnapshot });\r\n if (!validation.ok) return validation;\r\n\r\n const range: DocumentRange = { from, to };\r\n\r\n // Create CRDT-anchored positions\r\n const fromRel = flatOffsetToRelPos(ydoc, from, 0); // assoc 0: stick right\r\n const toRel = flatOffsetToRelPos(ydoc, to, -1); // assoc -1: stick left\r\n const relRange: RelativeRange | undefined = fromRel && toRel ? { fromRel, toRel } : undefined;\r\n\r\n if (!relRange) {\r\n const fragment = ydoc.getXmlFragment(\"default\");\r\n const fromEl = resolveToElement(fragment, from);\r\n const toEl = resolveToElement(fragment, to);\r\n if (fromEl && !fromEl.clampedFromPrefix && toEl && !toEl.clampedFromPrefix) {\r\n console.error(`[positions] anchoredRange: relRange creation failed for [${from}, ${to}]`);\r\n }\r\n }\r\n\r\n if (relRange) {\r\n return { ok: true, fullyAnchored: true, range, relRange };\r\n }\r\n return { ok: true, fullyAnchored: false, range };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// High-level: annotation range refresh\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Refresh an annotation's flat offsets from its relRange, or lazily attach\r\n * relRange if missing. Returns the (possibly updated) annotation.\r\n * If `map` is provided, persists changes back to the Y.Map.\r\n */\r\nexport function refreshRange(ann: Annotation, ydoc: Y.Doc, map?: Y.Map<unknown>): Annotation {\r\n if (!ann.relRange) {\r\n // Lazy attachment: compute relRange from current flat offsets\r\n const fromRel = flatOffsetToRelPos(ydoc, ann.range.from, 0);\r\n const toRel = flatOffsetToRelPos(ydoc, ann.range.to, -1);\r\n if (!fromRel || !toRel) return ann;\r\n const updated = { ...ann, relRange: { fromRel, toRel } };\r\n if (map) map.set(ann.id, updated);\r\n return updated;\r\n }\r\n\r\n // Resolve relRange to current flat offsets\r\n const newFrom = relPosToFlatOffset(ydoc, ann.relRange.fromRel);\r\n const newTo = relPosToFlatOffset(ydoc, ann.relRange.toRel);\r\n if (newFrom === null || newTo === null) return ann; // deleted content\r\n if (newFrom > newTo) {\r\n console.error(\r\n `[positions] refreshRange: inverted CRDT range for annotation ${ann.id}: ` +\r\n `resolved [${newFrom}, ${newTo}] from flat [${ann.range.from}, ${ann.range.to}]`,\r\n );\r\n return ann;\r\n }\r\n if (newFrom === ann.range.from && newTo === ann.range.to) return ann; // unchanged\r\n\r\n const updated = { ...ann, range: { from: newFrom, to: newTo } };\r\n if (map) map.set(ann.id, updated);\r\n return updated;\r\n}\r\n\r\n/** Refresh all annotations in a batch, wrapping Y.Map writes in a transaction. */\r\nexport function refreshAllRanges(\r\n annotations: Annotation[],\r\n ydoc: Y.Doc,\r\n map: Y.Map<unknown>,\r\n): Annotation[] {\r\n const results: Annotation[] = [];\r\n ydoc.transact(() => {\r\n for (const ann of annotations) {\r\n results.push(refreshRange(ann, ydoc, map));\r\n }\r\n }, MCP_ORIGIN);\r\n return results;\r\n}\r\n","import { z } from \"zod\";\r\nimport type { FlatOffset, DocumentRange, RelativeRange } from \"./positions/types.js\";\r\n\r\n// Canonical definitions live in the positions module; re-exported for backward compatibility.\r\nexport type {\r\n FlatOffset,\r\n PmPos,\r\n SerializedRelPos,\r\n DocumentRange,\r\n RelativeRange,\r\n} from \"./positions/types.js\";\r\nexport { toFlatOffset, toPmPos, toSerializedRelPos } from \"./positions/types.js\";\r\n\r\n// --- Zod schemas (source of truth) ---\r\n\r\nexport const AnnotationTypeSchema = z.enum([\r\n \"highlight\",\r\n \"comment\",\r\n \"suggestion\",\r\n \"overlay\",\r\n \"question\",\r\n \"flag\",\r\n]);\r\nexport const AnnotationStatusSchema = z.enum([\"pending\", \"accepted\", \"dismissed\"]);\r\nexport const AnnotationPrioritySchema = z.enum([\"normal\", \"urgent\"]);\r\nexport const InterruptionModeSchema = z.enum([\"all\", \"urgent-only\", \"paused\"]);\r\nexport const HighlightColorSchema = z.enum([\"yellow\", \"red\", \"green\", \"blue\", \"purple\"]);\r\nexport const SeveritySchema = z.enum([\"info\", \"warning\", \"error\", \"success\"]);\r\nexport const AuthorSchema = z.enum([\"user\", \"claude\", \"import\"]);\r\nexport const AnnotationActionSchema = z.enum([\"accept\", \"dismiss\"]);\r\nexport const ExportFormatSchema = z.enum([\"markdown\", \"json\"]);\r\nexport const DocumentFormatSchema = z.enum([\"md\", \"txt\", \"html\", \"docx\"]);\r\nexport const ToolErrorCodeSchema = z.enum([\r\n \"RANGE_GONE\",\r\n \"RANGE_MOVED\",\r\n \"FILE_LOCKED\",\r\n \"FILE_NOT_FOUND\",\r\n \"NO_DOCUMENT\",\r\n \"INVALID_RANGE\",\r\n \"FORMAT_ERROR\",\r\n \"PERMISSION_DENIED\",\r\n]);\r\n\r\n// --- Derived TypeScript types ---\r\n\r\nexport type AnnotationType = z.infer<typeof AnnotationTypeSchema>;\r\nexport type AnnotationStatus = z.infer<typeof AnnotationStatusSchema>;\r\nexport type AnnotationPriority = z.infer<typeof AnnotationPrioritySchema>;\r\nexport type InterruptionMode = z.infer<typeof InterruptionModeSchema>;\r\nexport type HighlightColor = z.infer<typeof HighlightColorSchema>;\r\nexport type Severity = z.infer<typeof SeveritySchema>;\r\n\r\n// --- Interfaces (not worth converting to Zod — no runtime validation needed) ---\r\n\r\nexport interface Annotation {\r\n id: string;\r\n author: \"user\" | \"claude\" | \"import\";\r\n type: AnnotationType;\r\n range: DocumentRange;\r\n /** CRDT-anchored range that survives edits. Falls back to `range` if absent. */\r\n relRange?: RelativeRange;\r\n content: string;\r\n status: AnnotationStatus;\r\n timestamp: number;\r\n color?: HighlightColor;\r\n priority?: AnnotationPriority;\r\n /** Snapshot of the annotated document text at creation time. Truncated to 200 chars. */\r\n textSnapshot?: string;\r\n /** Timestamp of last edit to the annotation content. */\r\n editedAt?: number;\r\n}\r\n\r\nexport interface AnchoredRange {\r\n start: { nodeId: string; offset: number };\r\n end: { nodeId: string; offset: number };\r\n textSnapshot: string;\r\n stale: boolean;\r\n}\r\n\r\nexport interface OverlayEntry {\r\n id: string;\r\n overlayId: string;\r\n range: AnchoredRange;\r\n score: string;\r\n numericScore?: number;\r\n detail: {\r\n summary: string;\r\n explanation: string;\r\n suggestion?: string;\r\n severity?: Severity;\r\n references?: Array<{ label: string; url?: string; documentNodeId?: string }>;\r\n };\r\n dismissed: boolean;\r\n accepted?: boolean;\r\n data: Record<string, unknown>;\r\n}\r\n\r\nexport interface OverlayDefinition {\r\n id: string;\r\n label: string;\r\n type: string;\r\n visible: boolean;\r\n mode: \"snapshot\" | \"live\";\r\n entries: OverlayEntry[];\r\n createdAt: number;\r\n updatedAt: number;\r\n}\r\n\r\nexport interface DocumentGroup {\r\n id: string;\r\n name: string;\r\n documents: DocumentInfo[];\r\n createdAt: number;\r\n}\r\n\r\nexport interface DocumentInfo {\r\n id: string;\r\n filePath: string;\r\n fileName: string;\r\n format: z.infer<typeof DocumentFormatSchema>;\r\n tokenEstimate: number;\r\n pageEstimate: number;\r\n readOnly: boolean;\r\n}\r\n\r\nexport interface ServerInfo {\r\n port: number;\r\n url: string;\r\n pid: number;\r\n startedAt: number;\r\n}\r\n\r\nexport interface ToolSuccess<T = unknown> {\r\n error: false;\r\n data: T;\r\n version?: string;\r\n}\r\n\r\nexport interface ToolError {\r\n error: true;\r\n code: z.infer<typeof ToolErrorCodeSchema>;\r\n message: string;\r\n details?: Record<string, unknown>;\r\n}\r\n\r\nexport type ToolResponse<T = unknown> = ToolSuccess<T> | ToolError;\r\n\r\nexport interface AwarenessState {\r\n user: string;\r\n color: string;\r\n cursor?: { from: number; to: number };\r\n status?: string;\r\n isTyping?: boolean;\r\n lastActivity?: number;\r\n}\r\n\r\n/** Claude's awareness state as stored in Y.Map('awareness') key 'claude' */\r\nexport interface ClaudeAwareness {\r\n status: string;\r\n timestamp: number;\r\n active: boolean;\r\n focusParagraph: number | null;\r\n}\r\n\r\nexport interface SessionData {\r\n filePath: string;\r\n format: string;\r\n ydocState: string; // Base64-encoded Y.encodeStateAsUpdate()\r\n sourceFileMtime: number; // Source file mtime at save — detect external changes on resume\r\n lastAccessed: number;\r\n}\r\n\r\n/** Chat message between user and Claude, stored in Y.Map('chat') on CTRL_ROOM */\r\nexport interface ChatMessage {\r\n id: string;\r\n author: \"user\" | \"claude\";\r\n text: string;\r\n timestamp: number;\r\n documentId?: string;\r\n anchor?: {\r\n from: FlatOffset;\r\n to: FlatOffset;\r\n textSnapshot: string;\r\n };\r\n replyTo?: string;\r\n read: boolean;\r\n}\r\n\r\n/** Server-to-client ephemeral notification (toast). Not persisted via CRDT. */\r\nexport interface TandemNotification {\r\n id: string;\r\n type: \"annotation-error\" | \"save-error\" | \"session-restored\" | \"general-error\";\r\n severity: \"info\" | \"warning\" | \"error\";\r\n message: string;\r\n documentId?: string;\r\n dedupKey?: string;\r\n timestamp: number;\r\n toolName?: string;\r\n errorCode?: string;\r\n}\r\n","// Extract Word comments from .docx ZIP and inject as Tandem annotations.\r\n//\r\n// Comments are parsed from word/comments.xml; anchor ranges are calculated\r\n// by walking word/document.xml and tracking w:commentRangeStart/End markers\r\n// alongside character offsets. Heading prefix offsets are accounted for so\r\n// flat-text positions match Tandem's coordinate system after mammoth → htmlToYDoc.\r\n\r\nimport JSZip from \"jszip\";\r\nimport { parseDocument } from \"htmlparser2\";\r\nimport type { ChildNode, Element } from \"domhandler\";\r\nimport * as Y from \"yjs\";\r\nimport { Y_MAP_ANNOTATIONS } from \"../../shared/constants.js\";\r\nimport { headingPrefixLength } from \"../../shared/offsets.js\";\r\nimport { anchoredRange } from \"../positions.js\";\r\nimport type { FlatOffset } from \"../../shared/types.js\";\r\nimport { toFlatOffset } from \"../../shared/types.js\";\r\nimport { MCP_ORIGIN } from \"../events/queue.js\";\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface DocxComment {\r\n commentId: string;\r\n authorName: string;\r\n bodyText: string;\r\n from: FlatOffset;\r\n to: FlatOffset;\r\n date?: string;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Top-level extraction\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Extract comments and their document ranges from a .docx buffer.\r\n * Returns an empty array when the document has no comments.\r\n */\r\nexport async function extractDocxComments(buffer: Buffer): Promise<DocxComment[]> {\r\n const zip = await JSZip.loadAsync(buffer);\r\n\r\n const commentsXml = await zip.file(\"word/comments.xml\")?.async(\"text\");\r\n if (!commentsXml) return [];\r\n\r\n const documentXml = await zip.file(\"word/document.xml\")?.async(\"text\");\r\n if (!documentXml) return [];\r\n\r\n const commentMap = parseCommentMetadata(commentsXml);\r\n if (commentMap.size === 0) return [];\r\n\r\n const ranges = calculateCommentRanges(documentXml);\r\n\r\n const result: DocxComment[] = [];\r\n for (const [id, meta] of commentMap) {\r\n const range = ranges.get(id);\r\n if (!range) {\r\n console.error(\r\n `[docx-comments] Comment ${id} has no range markers in document.xml — skipping`,\r\n );\r\n continue;\r\n }\r\n result.push({\r\n commentId: id,\r\n authorName: meta.authorName,\r\n bodyText: meta.bodyText,\r\n from: range.from,\r\n to: range.to,\r\n date: meta.date,\r\n });\r\n }\r\n return result;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Comment metadata (word/comments.xml)\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface CommentMeta {\r\n authorName: string;\r\n bodyText: string;\r\n date?: string;\r\n}\r\n\r\n/** Parse comment id, author, body text, and optional date from comments XML. */\r\nexport function parseCommentMetadata(xml: string): Map<string, CommentMeta> {\r\n const doc = parseDocument(xml, { xmlMode: true });\r\n const map = new Map<string, CommentMeta>();\r\n\r\n for (const comment of findAllByName(\"w:comment\", doc.children)) {\r\n const id = getAttr(comment, \"w:id\");\r\n if (!id) continue;\r\n\r\n const author = getAttr(comment, \"w:author\") || \"Unknown\";\r\n const date = getAttr(comment, \"w:date\");\r\n\r\n // Collect text from <w:t> elements within the comment body\r\n const textNodes = findAllByName(\"w:t\", comment.children);\r\n const bodyText = textNodes.map((t) => getTextContent(t)).join(\"\");\r\n\r\n map.set(id, { authorName: author, bodyText, date });\r\n }\r\n return map;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Range calculation (word/document.xml)\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Walk the document body, counting flat-text characters (including heading\r\n * prefixes), and record start/end offsets for each comment range marker.\r\n */\r\nexport function calculateCommentRanges(\r\n xml: string,\r\n): Map<string, { from: FlatOffset; to: FlatOffset }> {\r\n const doc = parseDocument(xml, { xmlMode: true });\r\n const ranges = new Map<string, { from: FlatOffset; to: FlatOffset }>();\r\n const openRanges = new Map<string, number>(); // commentId → startOffset\r\n\r\n let offset = 0;\r\n let firstParagraph = true;\r\n\r\n function walk(nodes: ChildNode[]): void {\r\n for (const node of nodes) {\r\n if (!isElement(node)) continue;\r\n\r\n if (node.name === \"w:p\") {\r\n // Paragraph separator (except for first paragraph)\r\n if (!firstParagraph) offset += 1; // \\n\r\n firstParagraph = false;\r\n\r\n // Detect heading style → add prefix length to offset\r\n const headingLevel = detectHeadingLevel(node);\r\n if (headingLevel > 0) {\r\n offset += headingPrefixLength(headingLevel);\r\n }\r\n\r\n walk(node.children);\r\n } else if (node.name === \"w:commentRangeStart\") {\r\n const id = getAttr(node, \"w:id\");\r\n if (id) openRanges.set(id, offset);\r\n } else if (node.name === \"w:commentRangeEnd\") {\r\n const id = getAttr(node, \"w:id\");\r\n if (id && openRanges.has(id)) {\r\n ranges.set(id, { from: toFlatOffset(openRanges.get(id)!), to: toFlatOffset(offset) });\r\n openRanges.delete(id);\r\n }\r\n } else if (node.name === \"w:t\") {\r\n const text = getTextContent(node);\r\n offset += text.length;\r\n } else if (node.name === \"w:tab\" || node.name === \"w:br\") {\r\n offset += 1;\r\n } else {\r\n // Recurse into w:r, w:hyperlink, w:pPr children, etc.\r\n walk(node.children);\r\n }\r\n }\r\n }\r\n\r\n // Find <w:body> and walk its children\r\n const bodyElements = findAllByName(\"w:body\", doc.children);\r\n if (bodyElements.length === 0) {\r\n console.error(\r\n \"[docx-comments] No <w:body> found in document.xml — cannot calculate comment ranges\",\r\n );\r\n return ranges;\r\n }\r\n walk(bodyElements[0].children);\r\n\r\n if (openRanges.size > 0) {\r\n console.error(\r\n `[docx-comments] ${openRanges.size} comment range(s) had start markers but no end markers: ${[...openRanges.keys()].join(\", \")}`,\r\n );\r\n }\r\n\r\n return ranges;\r\n}\r\n\r\n/**\r\n * Detect whether a <w:p> has a heading paragraph style.\r\n * Returns the heading level (1–6) or 0 if not a heading.\r\n *\r\n * Word heading styles appear as:\r\n * <w:p><w:pPr><w:pStyle w:val=\"Heading1\"/></w:pPr>...</w:p>\r\n *\r\n * mammoth maps these to <h1>–<h6>, and htmlToYDoc maps those to\r\n * Y.XmlElement(\"heading\") with a `level` attribute.\r\n */\r\nfunction detectHeadingLevel(paragraph: Element): number {\r\n for (const child of paragraph.children) {\r\n if (!isElement(child) || child.name !== \"w:pPr\") continue;\r\n for (const prop of child.children) {\r\n if (!isElement(prop) || prop.name !== \"w:pStyle\") continue;\r\n const val = getAttr(prop, \"w:val\") || \"\";\r\n // Match \"Heading1\" through \"Heading6\" (case-insensitive)\r\n const match = val.match(/^heading\\s*(\\d)$/i);\r\n if (match) {\r\n const level = parseInt(match[1], 10);\r\n if (level >= 1 && level <= 6) return level;\r\n }\r\n }\r\n }\r\n return 0;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Annotation injection\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Inject extracted comments into a Y.Doc's annotation map.\r\n * Must be called AFTER htmlToYDoc has populated the document content,\r\n * so that anchoredRange can create CRDT-anchored positions.\r\n */\r\nexport function injectCommentsAsAnnotations(doc: Y.Doc, comments: DocxComment[]): number {\r\n if (comments.length === 0) return 0;\r\n\r\n const map = doc.getMap(Y_MAP_ANNOTATIONS);\r\n let injected = 0;\r\n\r\n doc.transact(() => {\r\n for (const comment of comments) {\r\n const result = anchoredRange(doc, toFlatOffset(comment.from), toFlatOffset(comment.to));\r\n if (!result.ok) {\r\n console.error(\r\n `[docx-comments] Skipping imported comment ${comment.commentId}: range [${comment.from}, ${comment.to}] — ${result.code}`,\r\n );\r\n continue;\r\n }\r\n\r\n const id = `import-${comment.commentId}-${Date.now()}`;\r\n const content =\r\n comment.authorName !== \"Unknown\"\r\n ? `[${comment.authorName}] ${comment.bodyText}`\r\n : comment.bodyText;\r\n\r\n const annotation: Record<string, unknown> = {\r\n id,\r\n author: \"import\" as const,\r\n type: \"comment\" as const,\r\n range: { from: result.range.from, to: result.range.to },\r\n content,\r\n status: \"pending\" as const,\r\n timestamp: comment.date ? new Date(comment.date).getTime() : Date.now(),\r\n };\r\n\r\n // Attach CRDT-anchored range when available\r\n if (result.fullyAnchored) {\r\n annotation.relRange = result.relRange;\r\n }\r\n\r\n map.set(id, annotation);\r\n injected++;\r\n }\r\n }, MCP_ORIGIN); // origin tag prevents channel event echo\r\n\r\n if (injected > 0 || comments.length > 0) {\r\n console.error(`[docx-comments] Imported ${injected}/${comments.length} Word comments`);\r\n }\r\n\r\n return injected;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// DOM helpers (lightweight — avoids adding domutils as a direct dependency)\r\n// ---------------------------------------------------------------------------\r\n\r\nfunction isElement(node: ChildNode): node is Element {\r\n return node.type === \"tag\";\r\n}\r\n\r\nfunction getAttr(el: Element, name: string): string | undefined {\r\n return el.attribs?.[name];\r\n}\r\n\r\n/** Recursively collect text content from a DOM node. */\r\nfunction getTextContent(node: ChildNode): string {\r\n if (node.type === \"text\") return (node as { data: string }).data;\r\n if (!isElement(node)) return \"\";\r\n return node.children.map(getTextContent).join(\"\");\r\n}\r\n\r\n/** Recursively find all elements with a given name. */\r\nfunction findAllByName(name: string, nodes: ChildNode[]): Element[] {\r\n const results: Element[] = [];\r\n for (const node of nodes) {\r\n if (isElement(node)) {\r\n if (node.name === name) results.push(node);\r\n results.push(...findAllByName(name, node.children));\r\n }\r\n }\r\n return results;\r\n}\r\n","import fs from \"fs/promises\";\r\nimport path from \"path\";\r\nimport type { FormatAdapter } from \"./types.js\";\r\nimport { loadMarkdown, saveMarkdown } from \"./markdown.js\";\r\nimport { htmlToYDoc, loadDocx } from \"./docx.js\";\r\nimport {\r\n extractDocxComments,\r\n injectCommentsAsAnnotations,\r\n type DocxComment,\r\n} from \"./docx-comments.js\";\r\nimport { populateYDoc, extractText } from \"../mcp/document-model.js\";\r\n\r\nexport type { FormatAdapter } from \"./types.js\";\r\n\r\n// -- Adapter implementations --\r\n\r\nconst markdownAdapter: FormatAdapter = {\r\n canSave: true,\r\n load(doc, content) {\r\n loadMarkdown(doc, content as string);\r\n },\r\n save(doc) {\r\n return saveMarkdown(doc);\r\n },\r\n};\r\n\r\nconst plaintextAdapter: FormatAdapter = {\r\n canSave: true,\r\n load(doc, content) {\r\n populateYDoc(doc, content as string);\r\n },\r\n save(doc) {\r\n return extractText(doc);\r\n },\r\n};\r\n\r\nconst docxAdapter: FormatAdapter = {\r\n canSave: false,\r\n async load(doc, content) {\r\n const buffer = content as Buffer;\r\n const [html, comments] = await Promise.all([\r\n loadDocx(buffer),\r\n extractDocxComments(buffer).catch((err) => {\r\n console.error(\r\n \"[docx-comments] Comment extraction failed; document will load without imported comments:\",\r\n err,\r\n );\r\n return [] as DocxComment[];\r\n }),\r\n ]);\r\n htmlToYDoc(doc, html);\r\n if (comments.length > 0) {\r\n injectCommentsAsAnnotations(doc, comments);\r\n }\r\n },\r\n save() {\r\n return null;\r\n },\r\n};\r\n\r\n// -- Registry --\r\n\r\nconst adapters: Record<string, FormatAdapter> = {\r\n md: markdownAdapter,\r\n txt: plaintextAdapter,\r\n docx: docxAdapter,\r\n};\r\n\r\n/** Look up the adapter for a given format string */\r\nexport function getAdapter(format: string): FormatAdapter {\r\n return adapters[format] ?? plaintextAdapter;\r\n}\r\n\r\n// -- Shared helpers --\r\n\r\n/**\r\n * Atomic file write: write to a temp file, then rename.\r\n * Prevents partial writes on crash.\r\n */\r\nexport async function atomicWrite(filePath: string, content: string): Promise<void> {\r\n const tempPath = path.join(path.dirname(filePath), `.tandem-tmp-${Date.now()}`);\r\n await fs.writeFile(tempPath, content, \"utf-8\");\r\n await fs.rename(tempPath, filePath);\r\n}\r\n","import fs from \"fs/promises\";\r\nimport fsSync from \"fs\";\r\nimport path from \"path\";\r\nimport type * as Y from \"yjs\";\r\nimport { randomUUID } from \"node:crypto\";\r\nimport {\r\n getOrCreateDocument,\r\n getDocument,\r\n removeDocument,\r\n getHocuspocus,\r\n} from \"../yjs/provider.js\";\r\nimport {\r\n MAX_FILE_SIZE,\r\n CHARS_PER_PAGE,\r\n LARGE_FILE_PAGE_THRESHOLD,\r\n VERY_LARGE_FILE_PAGE_THRESHOLD,\r\n SUPPORTED_EXTENSIONS,\r\n Y_MAP_DOCUMENT_META,\r\n Y_MAP_SAVED_AT_VERSION,\r\n} from \"../../shared/constants.js\";\r\nimport { MCP_ORIGIN, detachObservers } from \"../events/queue.js\";\r\nimport { getAdapter } from \"../file-io/index.js\";\r\nimport {\r\n saveSession,\r\n loadSession,\r\n restoreYDoc,\r\n sourceFileChanged,\r\n deleteSession,\r\n startAutoSave,\r\n isAutoSaveRunning,\r\n} from \"../session/manager.js\";\r\nimport { extractText, detectFormat, docIdFromPath } from \"./document-model.js\";\r\nimport {\r\n type OpenDoc,\r\n getOpenDocs,\r\n setActiveDocId,\r\n broadcastOpenDocs,\r\n addDoc,\r\n removeDoc,\r\n} from \"./document-service.js\";\r\n\r\nexport { SUPPORTED_EXTENSIONS };\r\n\r\nexport interface OpenFileResult {\r\n documentId: string;\r\n filePath: string;\r\n fileName: string;\r\n format: string;\r\n readOnly: boolean;\r\n source: \"file\" | \"upload\";\r\n tokenEstimate: number;\r\n pageEstimate: number;\r\n restoredFromSession: boolean;\r\n alreadyOpen: boolean;\r\n forceReloaded: boolean;\r\n warnings?: string[];\r\n}\r\n\r\n/**\r\n * Open a file by its absolute path on disk.\r\n * Throws on errors (ENOENT, EACCES, EBUSY, etc.) — caller maps to MCP or HTTP responses.\r\n * Pass `force: true` to reload from disk even if already open (clears all document state).\r\n */\r\nexport async function openFileByPath(\r\n filePath: string,\r\n options?: { force?: boolean },\r\n): Promise<OpenFileResult> {\r\n let resolved = path.resolve(filePath);\r\n try {\r\n resolved = fsSync.realpathSync(resolved);\r\n } catch {\r\n resolved = path.resolve(filePath);\r\n }\r\n\r\n if (process.platform === \"win32\" && (resolved.startsWith(\"\\\\\\\\\") || resolved.startsWith(\"//\"))) {\r\n throw Object.assign(new Error(\"UNC paths are not supported for security reasons.\"), {\r\n code: \"INVALID_PATH\",\r\n });\r\n }\r\n\r\n const ext = path.extname(resolved).toLowerCase();\r\n if (!SUPPORTED_EXTENSIONS.has(ext)) {\r\n throw Object.assign(\r\n new Error(\r\n `Unsupported file format: ${ext}. Supported: ${[...SUPPORTED_EXTENSIONS].join(\", \")}`,\r\n ),\r\n { code: \"UNSUPPORTED_FORMAT\" },\r\n );\r\n }\r\n\r\n const stat = await fs.stat(resolved);\r\n if (stat.size > MAX_FILE_SIZE) {\r\n throw Object.assign(new Error(\"File exceeds 50MB limit.\"), { code: \"FILE_TOO_LARGE\" });\r\n }\r\n\r\n const format = detectFormat(resolved);\r\n const isDocx = format === \"docx\";\r\n const readOnly = isDocx;\r\n const id = docIdFromPath(resolved);\r\n const openDocs = getOpenDocs();\r\n\r\n // Already open — force-reload from disk or switch to it\r\n const existing = openDocs.get(id);\r\n const forceReload = existing && options?.force === true;\r\n if (existing && !forceReload) {\r\n setActiveDocId(id);\r\n broadcastOpenDocs();\r\n const doc = getOrCreateDocument(id);\r\n return {\r\n ...buildResult(doc, {\r\n documentId: id,\r\n filePath: resolved,\r\n fileName: path.basename(resolved),\r\n format,\r\n readOnly,\r\n source: \"file\",\r\n restoredFromSession: false,\r\n }),\r\n alreadyOpen: true,\r\n };\r\n }\r\n\r\n if (forceReload) {\r\n await forceCloseDocument(id, existing);\r\n }\r\n\r\n const doc = getOrCreateDocument(id);\r\n const fileName = path.basename(resolved);\r\n let restoredFromSession = false;\r\n\r\n const session = await loadSession(resolved);\r\n if (session) {\r\n const changed = await sourceFileChanged(session);\r\n if (!changed) {\r\n restoreYDoc(doc, session);\r\n const fragment = doc.getXmlFragment(\"default\");\r\n if (fragment.length > 0) {\r\n restoredFromSession = true;\r\n } else {\r\n console.error(\r\n `[Tandem] Session restore yielded empty doc for ${fileName}, falling back to source file`,\r\n );\r\n }\r\n }\r\n }\r\n\r\n if (!restoredFromSession) {\r\n const adapter = getAdapter(format);\r\n const fileContent = isDocx ? await fs.readFile(resolved) : await fs.readFile(resolved, \"utf-8\");\r\n await adapter.load(doc, fileContent);\r\n }\r\n\r\n addDoc(id, { id, filePath: resolved, format, readOnly, source: \"file\" });\r\n setActiveDocId(id);\r\n writeDocMeta(doc, id, fileName, format, readOnly);\r\n initSavedBaseline(doc);\r\n broadcastOpenDocs();\r\n ensureAutoSave();\r\n\r\n return {\r\n ...buildResult(doc, {\r\n documentId: id,\r\n filePath: resolved,\r\n fileName,\r\n format,\r\n readOnly,\r\n source: \"file\",\r\n restoredFromSession,\r\n }),\r\n forceReloaded: forceReload === true,\r\n };\r\n}\r\n\r\n/**\r\n * Open a file from uploaded content (no disk path).\r\n * Used when the browser drag-and-drops or selects a file.\r\n */\r\nexport async function openFileFromContent(\r\n fileName: string,\r\n content: string | Buffer,\r\n): Promise<OpenFileResult> {\r\n const ext = path.extname(fileName).toLowerCase();\r\n if (!SUPPORTED_EXTENSIONS.has(ext)) {\r\n throw Object.assign(\r\n new Error(\r\n `Unsupported file format: ${ext}. Supported: ${[...SUPPORTED_EXTENSIONS].join(\", \")}`,\r\n ),\r\n { code: \"UNSUPPORTED_FORMAT\" },\r\n );\r\n }\r\n\r\n const contentSize =\r\n content instanceof Buffer ? content.length : Buffer.byteLength(content as string);\r\n if (contentSize > MAX_FILE_SIZE) {\r\n throw Object.assign(new Error(\"File exceeds 50MB limit.\"), { code: \"FILE_TOO_LARGE\" });\r\n }\r\n\r\n const format = detectFormat(fileName);\r\n const readOnly = true;\r\n const syntheticPath = `upload://${randomUUID()}/${fileName}`;\r\n const id = docIdFromPath(syntheticPath);\r\n\r\n const doc = getOrCreateDocument(id);\r\n const adapter = getAdapter(format);\r\n await adapter.load(doc, content);\r\n\r\n addDoc(id, { id, filePath: syntheticPath, format, readOnly, source: \"upload\" });\r\n setActiveDocId(id);\r\n writeDocMeta(doc, id, fileName, format, readOnly);\r\n initSavedBaseline(doc);\r\n broadcastOpenDocs();\r\n ensureAutoSave();\r\n\r\n return buildResult(doc, {\r\n documentId: id,\r\n filePath: syntheticPath,\r\n fileName,\r\n format,\r\n readOnly,\r\n source: \"upload\",\r\n restoredFromSession: false,\r\n });\r\n}\r\n\r\n// --- Private helpers ---\r\n\r\n/**\r\n * Tear down an open document so it can be re-opened fresh from disk.\r\n * Clears all document state (content, annotations, awareness, session).\r\n * Directly manipulates Hocuspocus internals to avoid the nondeterministic\r\n * async timing of `unloadDocument()` (which bails if connections > 0).\r\n * Best-effort: each step is wrapped in try-catch so a failure in one step\r\n * doesn't prevent subsequent cleanup.\r\n */\r\nasync function forceCloseDocument(id: string, existing: OpenDoc): Promise<void> {\r\n console.error(`[Tandem] forceCloseDocument: tearing down ${id}`);\r\n let errors = 0;\r\n\r\n // 1. Stop event queue observers for this document\r\n try {\r\n detachObservers(id);\r\n } catch (err) {\r\n errors++;\r\n console.error(`[Tandem] forceCloseDocument: detachObservers failed for ${id}:`, err);\r\n }\r\n\r\n // 2. Remove from open-docs tracking and broadcast removal to clients.\r\n // Clients see the doc disappear from openDocuments and destroy their provider+ydoc.\r\n try {\r\n removeDoc(id);\r\n broadcastOpenDocs();\r\n } catch (err) {\r\n errors++;\r\n console.error(`[Tandem] forceCloseDocument: removeDoc/broadcast failed for ${id}:`, err);\r\n }\r\n\r\n // 3. Hocuspocus cleanup (may not be running in tests / MCP-only mode)\r\n try {\r\n const hp = getHocuspocus();\r\n if (hp) {\r\n // Force-close WebSocket connections so clients disconnect\r\n hp.closeConnections(id);\r\n\r\n // Delete from hp.documents so that any deferred unloadDocument calls\r\n // (triggered by closeConnections settling) become no-ops\r\n // (line 594 of src/Hocuspocus.ts: `if (!this.documents.has(documentName)) return`).\r\n const hpDoc = hp.documents.get(id);\r\n if (hpDoc) {\r\n hp.documents.delete(id);\r\n hpDoc.destroy();\r\n }\r\n\r\n // Clear any in-flight load promise to prevent stale state on reconnect\r\n hp.loadingDocuments.delete(id);\r\n }\r\n } catch (err) {\r\n errors++;\r\n console.error(`[Tandem] forceCloseDocument: Hocuspocus cleanup failed for ${id}:`, err);\r\n }\r\n\r\n // 4. Remove from Tandem's provider Y.Doc map (idempotent if afterUnloadDocument already ran)\r\n try {\r\n const oldDoc = getDocument(id);\r\n if (oldDoc) {\r\n oldDoc.destroy();\r\n removeDocument(id);\r\n }\r\n } catch (err) {\r\n errors++;\r\n console.error(`[Tandem] forceCloseDocument: Y.Doc removal failed for ${id}:`, err);\r\n }\r\n\r\n // 5. Delete session so it doesn't restore stale state\r\n try {\r\n await deleteSession(existing.filePath);\r\n } catch (err) {\r\n errors++;\r\n console.error(`[Tandem] forceCloseDocument: deleteSession failed for ${id}:`, err);\r\n }\r\n\r\n // 6. Best-effort delay so the removal broadcast propagates to clients before re-add.\r\n // Without this, React's automatic batching could merge remove+add into one render,\r\n // causing the stale provider to survive. 100ms is a heuristic, not a guarantee.\r\n await new Promise((resolve) => setTimeout(resolve, 100));\r\n\r\n console.error(\r\n `[Tandem] forceCloseDocument: teardown complete for ${id}${errors > 0 ? ` with ${errors} error(s)` : \"\"}`,\r\n );\r\n}\r\n\r\n/** Set the initial savedAtVersion baseline so the client knows the file is clean on open. */\r\nfunction initSavedBaseline(doc: Y.Doc): void {\r\n const meta = doc.getMap(Y_MAP_DOCUMENT_META);\r\n doc.transact(() => meta.set(Y_MAP_SAVED_AT_VERSION, Date.now()), MCP_ORIGIN);\r\n}\r\n\r\nfunction writeDocMeta(\r\n doc: Y.Doc,\r\n id: string,\r\n fileName: string,\r\n format: string,\r\n readOnly: boolean,\r\n): void {\r\n const meta = doc.getMap(Y_MAP_DOCUMENT_META);\r\n doc.transact(() => {\r\n meta.set(\"readOnly\", readOnly);\r\n meta.set(\"format\", format);\r\n meta.set(\"documentId\", id);\r\n meta.set(\"fileName\", fileName);\r\n }, MCP_ORIGIN);\r\n}\r\n\r\nfunction buildResult(\r\n doc: Y.Doc,\r\n base: Omit<\r\n OpenFileResult,\r\n \"tokenEstimate\" | \"pageEstimate\" | \"alreadyOpen\" | \"forceReloaded\" | \"warnings\"\r\n >,\r\n): OpenFileResult {\r\n const textContent = extractText(doc);\r\n const textLen = textContent.length;\r\n const pageEstimate = Math.ceil(textLen / CHARS_PER_PAGE);\r\n\r\n const warnings: string[] = [];\r\n if (pageEstimate >= VERY_LARGE_FILE_PAGE_THRESHOLD) {\r\n warnings.push(\r\n `Very large document (~${pageEstimate} pages). Consider splitting into smaller files.`,\r\n );\r\n } else if (pageEstimate >= LARGE_FILE_PAGE_THRESHOLD) {\r\n warnings.push(`Large document (~${pageEstimate} pages). Operations may be slower than usual.`);\r\n }\r\n\r\n return {\r\n ...base,\r\n tokenEstimate: Math.ceil(textLen / 4),\r\n pageEstimate,\r\n alreadyOpen: false,\r\n forceReloaded: false,\r\n ...(warnings.length > 0 ? { warnings } : {}),\r\n };\r\n}\r\n\r\nfunction ensureAutoSave(): void {\r\n if (isAutoSaveRunning()) return;\r\n startAutoSave(async () => {\r\n for (const [docId, state] of getOpenDocs()) {\r\n const d = getOrCreateDocument(docId);\r\n await saveSession(state.filePath, state.format, d);\r\n }\r\n });\r\n}\r\n","import path from \"path\";\r\nimport * as Y from \"yjs\";\r\nimport { getOrCreateDocument, setShouldKeepDocument } from \"../yjs/provider.js\";\r\nimport {\r\n saveSession,\r\n deleteSession,\r\n saveCtrlSession,\r\n loadCtrlSession,\r\n restoreCtrlDoc,\r\n listSessionFilePaths,\r\n stopAutoSave,\r\n} from \"../session/manager.js\";\r\nimport { CTRL_ROOM, Y_MAP_DOCUMENT_META } from \"../../shared/constants.js\";\r\nimport { MCP_ORIGIN } from \"../events/queue.js\";\r\nimport { randomUUID } from \"node:crypto\";\r\n\r\n// --- Multi-document state ---\r\n\r\nexport interface OpenDoc {\r\n id: string;\r\n filePath: string;\r\n format: string;\r\n readOnly: boolean;\r\n source: \"file\" | \"upload\";\r\n}\r\n\r\n/** All open documents, keyed by document ID (which is also the Hocuspocus room name) */\r\nconst openDocs = new Map<string, OpenDoc>();\r\n\r\n// Prevent Hocuspocus from evicting Y.Docs that MCP still tracks as open,\r\n// or the bootstrap channel (CTRL_ROOM) which holds persistent chat history.\r\nsetShouldKeepDocument((name) => openDocs.has(name) || name === CTRL_ROOM);\r\n\r\n/** The active document ID — tools default to this when no documentId is specified */\r\nlet activeDocId: string | null = null;\r\n\r\nexport function getOpenDocs(): ReadonlyMap<string, OpenDoc> {\r\n return openDocs;\r\n}\r\n\r\nexport function addDoc(id: string, entry: OpenDoc): void {\r\n openDocs.set(id, entry);\r\n}\r\n\r\nexport function removeDoc(id: string): boolean {\r\n return openDocs.delete(id);\r\n}\r\n\r\nexport function hasDoc(id: string): boolean {\r\n return openDocs.has(id);\r\n}\r\n\r\nexport function docCount(): number {\r\n return openDocs.size;\r\n}\r\n\r\nexport function getActiveDocId(): string | null {\r\n return activeDocId;\r\n}\r\n\r\nexport function setActiveDocId(id: string | null): void {\r\n activeDocId = id;\r\n}\r\n\r\n/**\r\n * Resolve which document to operate on.\r\n * If documentId is provided, use that. Otherwise use the active doc.\r\n */\r\nexport function getCurrentDoc(documentId?: string) {\r\n const id = documentId ?? activeDocId;\r\n if (!id) return null;\r\n const doc = openDocs.get(id);\r\n if (!doc) return null;\r\n return { ...doc, docName: id };\r\n}\r\n\r\n/** Returns the shared Y.Doc or null if the target doc isn't open */\r\nexport function requireDocument(\r\n documentId?: string,\r\n): { doc: Y.Doc; filePath: string; docId: string } | null {\r\n const current = getCurrentDoc(documentId);\r\n if (!current) return null;\r\n return {\r\n doc: getOrCreateDocument(current.docName),\r\n filePath: current.filePath,\r\n docId: current.id,\r\n };\r\n}\r\n\r\n/** Build the document list entry for a single OpenDoc */\r\nexport function toDocListEntry(d: OpenDoc) {\r\n return {\r\n id: d.id,\r\n filePath: d.filePath,\r\n fileName: path.basename(d.filePath),\r\n format: d.format,\r\n readOnly: d.readOnly,\r\n };\r\n}\r\n\r\n/** Broadcast the open documents list to connected clients.\r\n * Writes to both the bootstrap room (CTRL_ROOM) so new clients discover\r\n * docs, and to the active document's room so tab-switching clients stay in sync. */\r\nexport function broadcastOpenDocs(): void {\r\n try {\r\n const docList = Array.from(openDocs.values()).map(toDocListEntry);\r\n const id = activeDocId;\r\n\r\n const ctrl = getOrCreateDocument(CTRL_ROOM);\r\n const ctrlMeta = ctrl.getMap(Y_MAP_DOCUMENT_META);\r\n ctrl.transact(() => {\r\n ctrlMeta.set(\"openDocuments\", docList);\r\n ctrlMeta.set(\"activeDocumentId\", id);\r\n }, MCP_ORIGIN);\r\n\r\n // Update ALL open doc rooms so no per-doc Y.Doc ever has a stale list.\r\n // If only the active doc were updated, a previously active doc's stale list\r\n // could fire the client's metaObserver and incorrectly remove tabs.\r\n for (const [docId] of openDocs) {\r\n const ydoc = getOrCreateDocument(docId);\r\n const meta = ydoc.getMap(Y_MAP_DOCUMENT_META);\r\n ydoc.transact(() => {\r\n meta.set(\"openDocuments\", docList);\r\n meta.set(\"activeDocumentId\", id);\r\n }, MCP_ORIGIN);\r\n }\r\n } catch (err) {\r\n console.error(\"[Tandem] broadcastOpenDocs error:\", err);\r\n }\r\n}\r\n\r\n/**\r\n * Close a document by ID. Saves the session, removes from tracking,\r\n * picks a new active doc if needed, stops auto-save if no docs remain,\r\n * and broadcasts the updated document list.\r\n */\r\nexport async function closeDocumentById(\r\n id: string,\r\n): Promise<\r\n | { success: true; closedPath: string; activeDocumentId: string | null }\r\n | { success: false; error: string }\r\n> {\r\n const docState = openDocs.get(id);\r\n if (!docState) {\r\n return { success: false, error: `Document ${id} not found.` };\r\n }\r\n\r\n const closedPath = docState.filePath;\r\n\r\n // Best-effort persist before removing — save failure should not prevent close\r\n try {\r\n const doc = getOrCreateDocument(id);\r\n await saveSession(docState.filePath, docState.format, doc);\r\n } catch (err) {\r\n console.error(`[Tandem] Failed to save session before closing ${id}:`, err);\r\n }\r\n\r\n removeDoc(id);\r\n\r\n if (getActiveDocId() === id) {\r\n const remaining = Array.from(openDocs.keys());\r\n setActiveDocId(remaining.length > 0 ? remaining[0] : null);\r\n }\r\n\r\n if (docCount() === 0) {\r\n stopAutoSave();\r\n }\r\n\r\n broadcastOpenDocs();\r\n\r\n return { success: true, closedPath, activeDocumentId: getActiveDocId() };\r\n}\r\n\r\n/** Save all open sessions (for shutdown handler). */\r\nexport async function saveCurrentSession(): Promise<void> {\r\n for (const [id, state] of openDocs) {\r\n const doc = getOrCreateDocument(id);\r\n await saveSession(state.filePath, state.format, doc);\r\n }\r\n const ctrlDoc = getOrCreateDocument(CTRL_ROOM);\r\n await saveCtrlSession(ctrlDoc);\r\n}\r\n\r\n/** Restore CTRL_ROOM chat history from session file if available.\r\n * Returns the previously active documentId (if any) so startup can restore it. */\r\nexport async function restoreCtrlSession(): Promise<string | null> {\r\n const saved = await loadCtrlSession();\r\n if (!saved) return null;\r\n\r\n const ctrlDoc = getOrCreateDocument(CTRL_ROOM);\r\n restoreCtrlDoc(ctrlDoc, saved);\r\n\r\n // Read the previous active doc before clearing stale tracking\r\n const meta = ctrlDoc.getMap(Y_MAP_DOCUMENT_META);\r\n const previousActiveDocId = (meta.get(\"activeDocumentId\") as string) ?? null;\r\n\r\n // Clear stale document tracking — no docs are actually open after a restart.\r\n // Chat history is preserved; only the document list is wiped.\r\n ctrlDoc.transact(() => {\r\n meta.delete(\"openDocuments\");\r\n meta.delete(\"activeDocumentId\");\r\n }, MCP_ORIGIN);\r\n\r\n console.error(\"[Tandem] Restored chat history from session (cleared stale doc list)\");\r\n return previousActiveDocId;\r\n}\r\n\r\n/** Write a unique generationId to the ctrl doc so clients can detect server restarts. */\r\nexport function writeGenerationId(): void {\r\n const ctrlDoc = getOrCreateDocument(CTRL_ROOM);\r\n const meta = ctrlDoc.getMap(Y_MAP_DOCUMENT_META);\r\n const generationId = randomUUID();\r\n ctrlDoc.transact(() => meta.set(\"generationId\", generationId), MCP_ORIGIN);\r\n console.error(`[Tandem] Server generationId: ${generationId}`);\r\n}\r\n\r\n/**\r\n * Scan sessions and re-open previously open documents.\r\n * Called during startup to restore the working set.\r\n */\r\nexport async function restoreOpenDocuments(previousActiveDocId: string | null): Promise<number> {\r\n // Import dynamically to avoid circular dependency (file-opener imports document-service)\r\n const { openFileByPath } = await import(\"./file-opener.js\");\r\n\r\n const sessions = await listSessionFilePaths();\r\n if (sessions.length === 0) return 0;\r\n\r\n let restoredCount = 0;\r\n for (const { filePath } of sessions) {\r\n try {\r\n await openFileByPath(filePath);\r\n restoredCount++;\r\n } catch (err) {\r\n const code = (err as NodeJS.ErrnoException).code;\r\n if (code === \"ENOENT\") {\r\n console.error(`[Tandem] Skipping deleted file (removing stale session): ${filePath}`);\r\n deleteSession(filePath).catch(() => {});\r\n } else {\r\n console.error(`[Tandem] Failed to restore ${filePath}:`, err);\r\n }\r\n }\r\n }\r\n\r\n // Restore the previously active document if it was successfully reopened\r\n if (previousActiveDocId && openDocs.has(previousActiveDocId)) {\r\n setActiveDocId(previousActiveDocId);\r\n broadcastOpenDocs();\r\n }\r\n\r\n if (restoredCount > 0) {\r\n console.error(`[Tandem] Restored ${restoredCount} document(s) from session`);\r\n }\r\n\r\n return restoredCount;\r\n}\r\n","function generateId(prefix: string): string {\r\n return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\r\n}\r\n\r\nexport function generateAnnotationId(): string {\r\n return generateId(\"ann\");\r\n}\r\n\r\nexport function generateMessageId(): string {\r\n return generateId(\"msg\");\r\n}\r\n\r\nexport function generateEventId(): string {\r\n return generateId(\"evt\");\r\n}\r\n\r\nexport function generateNotificationId(): string {\r\n return generateId(\"ntf\");\r\n}\r\n","/**\r\n * Event types for the Tandem → Claude Code channel.\r\n *\r\n * These events flow from browser-originated Y.Map changes through an SSE\r\n * endpoint to the channel shim, which pushes them into Claude Code as\r\n * `notifications/claude/channel` messages.\r\n */\r\n\r\n// --- Per-event payload interfaces ---\r\n\r\nexport interface AnnotationCreatedPayload {\r\n annotationId: string;\r\n annotationType: string;\r\n content: string;\r\n textSnippet: string;\r\n}\r\n\r\nexport interface AnnotationAcceptedPayload {\r\n annotationId: string;\r\n textSnippet: string;\r\n}\r\n\r\nexport interface AnnotationDismissedPayload {\r\n annotationId: string;\r\n textSnippet: string;\r\n}\r\n\r\nexport interface ChatMessagePayload {\r\n messageId: string;\r\n text: string;\r\n replyTo: string | null;\r\n anchor: { from: number; to: number; textSnapshot: string } | null;\r\n}\r\n\r\nexport interface SelectionChangedPayload {\r\n from: number;\r\n to: number;\r\n selectedText: string;\r\n}\r\n\r\nexport interface DocumentOpenedPayload {\r\n fileName: string;\r\n format: string;\r\n}\r\n\r\nexport interface DocumentClosedPayload {\r\n fileName: string;\r\n}\r\n\r\nexport interface DocumentSwitchedPayload {\r\n fileName: string;\r\n}\r\n\r\n// --- Discriminated union ---\r\n\r\ninterface TandemEventBase {\r\n /** Timestamp-based unique ID for SSE `Last-Event-ID` reconnection. Format: `evt_<timestamp>_<rand>`. Roughly ordered but not strictly monotonic. */\r\n id: string;\r\n timestamp: number;\r\n /** Which document this event relates to (absent for global events). */\r\n documentId?: string;\r\n}\r\n\r\nexport type TandemEvent =\r\n | (TandemEventBase & { type: \"annotation:created\"; payload: AnnotationCreatedPayload })\r\n | (TandemEventBase & { type: \"annotation:accepted\"; payload: AnnotationAcceptedPayload })\r\n | (TandemEventBase & { type: \"annotation:dismissed\"; payload: AnnotationDismissedPayload })\r\n | (TandemEventBase & { type: \"chat:message\"; payload: ChatMessagePayload })\r\n | (TandemEventBase & { type: \"selection:changed\"; payload: SelectionChangedPayload })\r\n | (TandemEventBase & { type: \"document:opened\"; payload: DocumentOpenedPayload })\r\n | (TandemEventBase & { type: \"document:closed\"; payload: DocumentClosedPayload })\r\n | (TandemEventBase & { type: \"document:switched\"; payload: DocumentSwitchedPayload });\r\n\r\n/** Union of all event type discriminants. */\r\nexport type TandemEventType = TandemEvent[\"type\"];\r\n\r\n// Re-export from shared utils (single ID generation pattern)\r\nexport { generateEventId } from \"../../shared/utils.js\";\r\n\r\n// --- Parse guard for SSE consumers ---\r\n\r\nconst VALID_EVENT_TYPES = new Set<TandemEventType>([\r\n \"annotation:created\",\r\n \"annotation:accepted\",\r\n \"annotation:dismissed\",\r\n \"chat:message\",\r\n \"selection:changed\",\r\n \"document:opened\",\r\n \"document:closed\",\r\n \"document:switched\",\r\n]);\r\n\r\n/**\r\n * Validate a JSON-parsed value as a TandemEvent.\r\n * Used by the event-bridge to safely consume SSE data.\r\n */\r\nexport function parseTandemEvent(raw: unknown): TandemEvent | null {\r\n if (\r\n typeof raw !== \"object\" ||\r\n raw === null ||\r\n !(\"id\" in raw) ||\r\n typeof (raw as Record<string, unknown>).id !== \"string\" ||\r\n !(\"type\" in raw) ||\r\n !VALID_EVENT_TYPES.has((raw as Record<string, unknown>).type as TandemEventType) ||\r\n !(\"timestamp\" in raw) ||\r\n typeof (raw as Record<string, unknown>).timestamp !== \"number\" ||\r\n !(\"payload\" in raw) ||\r\n typeof (raw as Record<string, unknown>).payload !== \"object\"\r\n ) {\r\n return null;\r\n }\r\n return raw as TandemEvent;\r\n}\r\n\r\n/**\r\n * Convert a TandemEvent into a human-readable string for the channel `content` field.\r\n * Claude sees this text inside `<channel source=\"tandem-channel\">` tags.\r\n */\r\nexport function formatEventContent(event: TandemEvent): string {\r\n const doc = event.documentId ? ` [doc: ${event.documentId}]` : \"\";\r\n\r\n switch (event.type) {\r\n case \"annotation:created\": {\r\n const { annotationType, content, textSnippet } = event.payload;\r\n const snippet = textSnippet ? ` on \"${textSnippet}\"` : \"\";\r\n return `User created ${annotationType}${snippet}: ${content || \"(no content)\"}${doc}`;\r\n }\r\n case \"annotation:accepted\": {\r\n const { annotationId, textSnippet } = event.payload;\r\n return `User accepted annotation ${annotationId}${textSnippet ? ` (\"${textSnippet}\")` : \"\"}${doc}`;\r\n }\r\n case \"annotation:dismissed\": {\r\n const { annotationId, textSnippet } = event.payload;\r\n return `User dismissed annotation ${annotationId}${textSnippet ? ` (\"${textSnippet}\")` : \"\"}${doc}`;\r\n }\r\n case \"chat:message\": {\r\n const { text, replyTo } = event.payload;\r\n const reply = replyTo ? ` (replying to ${replyTo})` : \"\";\r\n return `User says${reply}: ${text}${doc}`;\r\n }\r\n case \"selection:changed\": {\r\n const { from, to, selectedText } = event.payload;\r\n if (!selectedText) return `User cleared selection${doc}`;\r\n return `User selected text (${from}-${to}): \"${selectedText}\"${doc}`;\r\n }\r\n case \"document:opened\": {\r\n const { fileName, format } = event.payload;\r\n return `User opened document: ${fileName} (${format})${doc}`;\r\n }\r\n case \"document:closed\": {\r\n const { fileName } = event.payload;\r\n return `User closed document: ${fileName}${doc}`;\r\n }\r\n case \"document:switched\": {\r\n const { fileName } = event.payload;\r\n return `User switched to document: ${fileName}${doc}`;\r\n }\r\n default: {\r\n const _exhaustive: never = event;\r\n return `Unknown event${doc}`;\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Build the `meta` record for a channel notification.\r\n * Keys use underscores only (Channels API silently drops hyphenated keys).\r\n */\r\nexport function formatEventMeta(event: TandemEvent): Record<string, string> {\r\n const meta: Record<string, string> = {\r\n event_type: event.type,\r\n };\r\n if (event.documentId) meta.document_id = event.documentId;\r\n\r\n switch (event.type) {\r\n case \"annotation:created\":\r\n case \"annotation:accepted\":\r\n case \"annotation:dismissed\":\r\n meta.annotation_id = event.payload.annotationId;\r\n break;\r\n case \"chat:message\":\r\n meta.message_id = event.payload.messageId;\r\n break;\r\n }\r\n\r\n return meta;\r\n}\r\n","/**\r\n * Event queue that observes Y.Map changes and emits TandemEvents.\r\n *\r\n * Observers filter by transaction origin — only browser-originated changes\r\n * (origin !== 'mcp') generate events. This prevents Claude from seeing its\r\n * own actions echoed back via the channel.\r\n */\r\n\r\nimport * as Y from \"yjs\";\r\nimport {\r\n CHANNEL_EVENT_BUFFER_AGE_MS,\r\n CHANNEL_EVENT_BUFFER_SIZE,\r\n CTRL_ROOM,\r\n Y_MAP_ANNOTATIONS,\r\n Y_MAP_CHAT,\r\n Y_MAP_DOCUMENT_META,\r\n Y_MAP_USER_AWARENESS,\r\n} from \"../../shared/constants.js\";\r\nimport type { Annotation, ChatMessage, FlatOffset } from \"../../shared/types.js\";\r\nimport { getOpenDocs } from \"../mcp/document-service.js\";\r\nimport { getOrCreateDocument } from \"../yjs/provider.js\";\r\nimport type { TandemEvent } from \"./types.js\";\r\nimport { generateEventId } from \"./types.js\";\r\n\r\n/** Origin tag for all MCP-initiated Y.Map writes. Import and use this — never use raw \"mcp\" strings. */\r\nexport const MCP_ORIGIN = \"mcp\";\r\n\r\ntype EventCallback = (event: TandemEvent) => void;\r\n\r\nconst docObservers = new Map<string, Array<() => void>>();\r\n\r\n/** O(1) dedup: ref-counted annotation/message IDs that have been pushed via channel. */\r\nconst emittedPayloadIds = new Map<string, number>();\r\n\r\nconst buffer: TandemEvent[] = [];\r\nconst subscribers = new Set<EventCallback>();\r\n\r\nfunction getTrackableId(event: TandemEvent): string | undefined {\r\n switch (event.type) {\r\n case \"annotation:created\":\r\n case \"annotation:accepted\":\r\n case \"annotation:dismissed\":\r\n return event.payload.annotationId;\r\n case \"chat:message\":\r\n return event.payload.messageId;\r\n default:\r\n return undefined;\r\n }\r\n}\r\n\r\nfunction trackPayloadId(event: TandemEvent): void {\r\n const id = getTrackableId(event);\r\n if (id) emittedPayloadIds.set(id, (emittedPayloadIds.get(id) ?? 0) + 1);\r\n}\r\n\r\nfunction untrackPayloadId(event: TandemEvent): void {\r\n const id = getTrackableId(event);\r\n if (!id) return;\r\n const count = emittedPayloadIds.get(id) ?? 0;\r\n if (count <= 1) emittedPayloadIds.delete(id);\r\n else emittedPayloadIds.set(id, count - 1);\r\n}\r\n\r\nfunction pushEvent(event: TandemEvent): void {\r\n buffer.push(event);\r\n trackPayloadId(event);\r\n\r\n while (buffer.length > CHANNEL_EVENT_BUFFER_SIZE) {\r\n const evicted = buffer.shift();\r\n if (evicted) untrackPayloadId(evicted);\r\n }\r\n\r\n const now = Date.now();\r\n while (buffer.length > 0 && now - buffer[0].timestamp > CHANNEL_EVENT_BUFFER_AGE_MS) {\r\n const evicted = buffer.shift();\r\n if (evicted) untrackPayloadId(evicted);\r\n }\r\n\r\n for (const cb of subscribers) {\r\n try {\r\n cb(event);\r\n } catch (err) {\r\n console.error(\"[EventQueue] Subscriber threw during event dispatch:\", err);\r\n }\r\n }\r\n}\r\n\r\n// --- Public API ---\r\n\r\nexport function subscribe(cb: EventCallback): void {\r\n subscribers.add(cb);\r\n}\r\n\r\nexport function unsubscribe(cb: EventCallback): void {\r\n subscribers.delete(cb);\r\n}\r\n\r\n/** Replay buffered events since a given event ID (for SSE reconnection). */\r\nexport function replaySince(lastEventId: string): TandemEvent[] {\r\n const idx = buffer.findIndex((e) => e.id === lastEventId);\r\n if (idx === -1) return [...buffer]; // ID not found — replay everything\r\n return buffer.slice(idx + 1);\r\n}\r\n\r\n/** O(1) check if an annotation/message was already pushed via channel. Intended for checkInbox dedup (not yet wired). */\r\nexport function wasEmittedViaChannel(payloadId: string): boolean {\r\n return emittedPayloadIds.has(payloadId);\r\n}\r\n\r\n// --- Y.Map observer attachment ---\r\n\r\n/** Attach observers to a document's Y.Maps. Call after doc swap in onLoadDocument. */\r\nexport function attachObservers(docName: string, doc: Y.Doc): void {\r\n // Detach existing observers first (idempotent)\r\n detachObservers(docName);\r\n\r\n const cleanups: Array<() => void> = [];\r\n\r\n // 1. Annotations observer\r\n const annotationsMap = doc.getMap(Y_MAP_ANNOTATIONS);\r\n const annotationsObs = (event: Y.YMapEvent<unknown>, txn: Y.Transaction) => {\r\n if (txn.origin === MCP_ORIGIN) return;\r\n\r\n for (const [key, change] of event.changes.keys) {\r\n const ann = annotationsMap.get(key) as Annotation | undefined;\r\n if (!ann) continue;\r\n\r\n if (change.action === \"add\" && ann.author === \"user\") {\r\n pushEvent({\r\n id: generateEventId(),\r\n type: \"annotation:created\",\r\n timestamp: Date.now(),\r\n documentId: docName,\r\n payload: {\r\n annotationId: ann.id,\r\n annotationType: ann.type,\r\n content: ann.content,\r\n textSnippet: ann.textSnapshot ?? \"\",\r\n },\r\n });\r\n } else if (change.action === \"update\" && ann.author === \"claude\") {\r\n if (ann.status === \"accepted\") {\r\n pushEvent({\r\n id: generateEventId(),\r\n type: \"annotation:accepted\",\r\n timestamp: Date.now(),\r\n documentId: docName,\r\n payload: {\r\n annotationId: ann.id,\r\n textSnippet: ann.textSnapshot ?? \"\",\r\n },\r\n });\r\n } else if (ann.status === \"dismissed\") {\r\n pushEvent({\r\n id: generateEventId(),\r\n type: \"annotation:dismissed\",\r\n timestamp: Date.now(),\r\n documentId: docName,\r\n payload: {\r\n annotationId: ann.id,\r\n textSnippet: ann.textSnapshot ?? \"\",\r\n },\r\n });\r\n }\r\n }\r\n }\r\n };\r\n annotationsMap.observe(annotationsObs);\r\n cleanups.push(() => annotationsMap.unobserve(annotationsObs));\r\n\r\n // 2. User awareness observer (selection changes)\r\n const userAwareness = doc.getMap(Y_MAP_USER_AWARENESS);\r\n const awarenessObs = (event: Y.YMapEvent<unknown>, txn: Y.Transaction) => {\r\n if (txn.origin === MCP_ORIGIN) return;\r\n\r\n if (event.keysChanged.has(\"selection\")) {\r\n const selection = userAwareness.get(\"selection\") as\r\n | { from: FlatOffset; to: FlatOffset; selectedText?: string }\r\n | undefined;\r\n pushEvent({\r\n id: generateEventId(),\r\n type: \"selection:changed\",\r\n timestamp: Date.now(),\r\n documentId: docName,\r\n payload: {\r\n from: selection?.from ?? 0,\r\n to: selection?.to ?? 0,\r\n selectedText: selection?.from !== selection?.to ? (selection?.selectedText ?? \"\") : \"\",\r\n },\r\n });\r\n }\r\n };\r\n userAwareness.observe(awarenessObs);\r\n cleanups.push(() => userAwareness.unobserve(awarenessObs));\r\n\r\n docObservers.set(docName, cleanups);\r\n console.error(`[EventQueue] Attached observers for document: ${docName}`);\r\n}\r\n\r\n/** Detach all observers for a document. Safe to call even if none are attached. */\r\nexport function detachObservers(docName: string): void {\r\n const cleanups = docObservers.get(docName);\r\n if (cleanups) {\r\n for (const cleanup of cleanups) cleanup();\r\n docObservers.delete(docName);\r\n console.error(`[EventQueue] Detached observers for document: ${docName}`);\r\n }\r\n}\r\n\r\n/** Reattach observers after Hocuspocus replaces a Y.Doc instance. */\r\nexport function reattachObservers(docName: string, newDoc: Y.Doc): void {\r\n attachObservers(docName, newDoc);\r\n}\r\n\r\n// --- CTRL_ROOM observers (chat + document meta) ---\r\n\r\nlet ctrlCleanups: Array<() => void> = [];\r\n\r\n/** Attach observers to the CTRL_ROOM Y.Doc for chat messages and document meta changes. */\r\nexport function attachCtrlObservers(): void {\r\n // Detach existing first\r\n for (const cleanup of ctrlCleanups) cleanup();\r\n ctrlCleanups = [];\r\n\r\n const ctrlDoc = getOrCreateDocument(CTRL_ROOM);\r\n\r\n // Chat message observer\r\n const chatMap = ctrlDoc.getMap(Y_MAP_CHAT);\r\n const chatObs = (event: Y.YMapEvent<unknown>, txn: Y.Transaction) => {\r\n if (txn.origin === MCP_ORIGIN) return;\r\n\r\n for (const [key, change] of event.changes.keys) {\r\n if (change.action !== \"add\") continue;\r\n const msg = chatMap.get(key) as ChatMessage | undefined;\r\n if (!msg || msg.author !== \"user\") continue;\r\n\r\n pushEvent({\r\n id: generateEventId(),\r\n type: \"chat:message\",\r\n timestamp: Date.now(),\r\n documentId: msg.documentId,\r\n payload: {\r\n messageId: msg.id,\r\n text: msg.text,\r\n replyTo: msg.replyTo ?? null,\r\n anchor: msg.anchor ?? null,\r\n },\r\n });\r\n }\r\n };\r\n chatMap.observe(chatObs);\r\n ctrlCleanups.push(() => chatMap.unobserve(chatObs));\r\n\r\n // Document meta observer (open/close/switch)\r\n const metaMap = ctrlDoc.getMap(Y_MAP_DOCUMENT_META);\r\n let lastActiveDocId: string | null = null;\r\n let lastOpenDocIds = new Set<string>();\r\n\r\n const metaObs = (event: Y.YMapEvent<unknown>, txn: Y.Transaction) => {\r\n if (txn.origin === MCP_ORIGIN) return;\r\n\r\n // Check for activeDocumentId change (tab switch)\r\n if (event.keysChanged.has(\"activeDocumentId\")) {\r\n const activeId = metaMap.get(\"activeDocumentId\") as string | undefined;\r\n if (activeId && activeId !== lastActiveDocId) {\r\n const openDoc = getOpenDocs().get(activeId);\r\n pushEvent({\r\n id: generateEventId(),\r\n type: \"document:switched\",\r\n timestamp: Date.now(),\r\n documentId: activeId,\r\n payload: {\r\n fileName: openDoc?.filePath?.split(/[/\\\\]/).pop() ?? activeId,\r\n },\r\n });\r\n lastActiveDocId = activeId;\r\n }\r\n }\r\n\r\n // Check for openDocuments change (doc open/close)\r\n if (event.keysChanged.has(\"openDocuments\")) {\r\n const docList =\r\n (metaMap.get(\"openDocuments\") as Array<{ id: string; fileName?: string }>) ?? [];\r\n const currentIds = new Set(docList.map((d) => d.id));\r\n\r\n // Newly opened\r\n for (const doc of docList) {\r\n if (!lastOpenDocIds.has(doc.id)) {\r\n const openDoc = getOpenDocs().get(doc.id);\r\n pushEvent({\r\n id: generateEventId(),\r\n type: \"document:opened\",\r\n timestamp: Date.now(),\r\n documentId: doc.id,\r\n payload: {\r\n fileName: doc.fileName ?? openDoc?.filePath?.split(/[/\\\\]/).pop() ?? doc.id,\r\n format: openDoc?.format ?? \"unknown\",\r\n },\r\n });\r\n }\r\n }\r\n\r\n // Closed\r\n for (const oldId of lastOpenDocIds) {\r\n if (!currentIds.has(oldId)) {\r\n pushEvent({\r\n id: generateEventId(),\r\n type: \"document:closed\",\r\n timestamp: Date.now(),\r\n documentId: oldId,\r\n payload: {\r\n fileName: oldId,\r\n },\r\n });\r\n }\r\n }\r\n\r\n lastOpenDocIds = currentIds;\r\n }\r\n };\r\n metaMap.observe(metaObs);\r\n ctrlCleanups.push(() => metaMap.unobserve(metaObs));\r\n\r\n console.error(\"[EventQueue] Attached CTRL_ROOM observers (chat + documentMeta)\");\r\n}\r\n\r\n/** Reattach CTRL_ROOM observers after doc replacement. */\r\nexport function reattachCtrlObservers(): void {\r\n attachCtrlObservers();\r\n}\r\n\r\n/** Reset all module state. For tests only — do not call in production. */\r\nexport function resetForTesting(): void {\r\n buffer.length = 0;\r\n subscribers.clear();\r\n emittedPayloadIds.clear();\r\n for (const cleanups of docObservers.values()) {\r\n for (const cleanup of cleanups) cleanup();\r\n }\r\n docObservers.clear();\r\n for (const cleanup of ctrlCleanups) cleanup();\r\n ctrlCleanups = [];\r\n}\r\n","/**\r\n * Claude Code launcher — spawns Claude Code with the Tandem channel active.\r\n *\r\n * Called by `POST /api/launch-claude` when the user clicks \"Launch Claude\" in the browser.\r\n */\r\n\r\nimport { type ChildProcess, spawn } from \"child_process\";\r\nimport { DEFAULT_MCP_PORT } from \"../../shared/constants.js\";\r\n\r\nlet claudeProcess: ChildProcess | null = null;\r\n\r\nconst TANDEM_SYSTEM_PROMPT = [\r\n \"You are Claude, connected to Tandem — a collaborative document editor.\",\r\n \"You will receive real-time push notifications via the tandem-channel when users\",\r\n \"create annotations, send chat messages, accept/dismiss your suggestions, or switch documents.\",\r\n \"Use your tandem MCP tools (tandem_getTextContent, tandem_comment, tandem_highlight,\",\r\n \"tandem_suggest, tandem_edit, etc.) to review and annotate documents.\",\r\n \"Start by calling tandem_checkInbox to see what needs attention.\",\r\n].join(\" \");\r\n\r\nexport function launchClaude(): { status: string; pid?: number } {\r\n // Idempotency: don't spawn twice\r\n if (claudeProcess && !claudeProcess.killed) {\r\n return { status: \"already_running\", pid: claudeProcess.pid };\r\n }\r\n\r\n const claudeCmd = process.env.TANDEM_CLAUDE_CMD || \"claude\";\r\n const tandemUrl = `http://localhost:${process.env.TANDEM_MCP_PORT || DEFAULT_MCP_PORT}`;\r\n\r\n const args = [\r\n \"--dangerously-load-development-channels\",\r\n \"server:tandem-channel\",\r\n \"--append-system-prompt\",\r\n TANDEM_SYSTEM_PROMPT,\r\n \"--name\",\r\n \"tandem-reviewer\",\r\n ];\r\n\r\n claudeProcess = spawn(claudeCmd, args, {\r\n env: { ...process.env, TANDEM_URL: tandemUrl },\r\n stdio: \"pipe\",\r\n detached: true,\r\n });\r\n\r\n // Feed initial prompt via stdin\r\n if (claudeProcess.stdin?.writable) {\r\n claudeProcess.stdin.write(\r\n \"A document has been opened in Tandem for review. \" +\r\n \"Call tandem_checkInbox to see what needs attention, then begin reviewing.\\n\",\r\n (err) => {\r\n if (err) console.error(\"[Launcher] Failed to send initial prompt:\", err.message);\r\n },\r\n );\r\n } else {\r\n console.error(\"[Launcher] Claude process has no writable stdin — initial prompt not delivered\");\r\n }\r\n\r\n const pid = claudeProcess.pid;\r\n\r\n claudeProcess.on(\"error\", (err: NodeJS.ErrnoException) => {\r\n if (err.code === \"ENOENT\") {\r\n console.error(\r\n \"[Launcher] Claude Code not found. Install with: npm i -g @anthropic-ai/claude-code\",\r\n );\r\n } else {\r\n console.error(\"[Launcher] spawn error:\", err);\r\n }\r\n claudeProcess = null;\r\n });\r\n\r\n claudeProcess.on(\"exit\", (code) => {\r\n console.error(`[Launcher] Claude Code exited with code ${code}`);\r\n claudeProcess = null;\r\n });\r\n\r\n // Capture stderr for debugging\r\n claudeProcess.stderr?.on(\"data\", (chunk: Buffer) => {\r\n console.error(`[Claude] ${chunk.toString().trimEnd()}`);\r\n });\r\n\r\n claudeProcess.unref();\r\n\r\n console.error(`[Launcher] Claude Code launched (pid: ${pid})`);\r\n return { status: \"launched\", pid };\r\n}\r\n\r\n/** Kill the Claude Code process (called on server shutdown or doc close). */\r\nexport function killClaude(): void {\r\n if (claudeProcess && !claudeProcess.killed) {\r\n console.error(`[Launcher] Killing Claude Code (pid: ${claudeProcess.pid})`);\r\n try {\r\n claudeProcess.kill(\"SIGTERM\");\r\n } catch (err) {\r\n const code = (err as NodeJS.ErrnoException).code;\r\n if (code !== \"ESRCH\") {\r\n console.error(\"[Launcher] Failed to kill Claude process:\", err);\r\n }\r\n }\r\n claudeProcess = null;\r\n }\r\n}\r\n","import type { Server } from \"http\";\r\nimport path from \"path\";\r\nimport { fileURLToPath } from \"url\";\r\nimport {\r\n startMcpServerStdio,\r\n startMcpServerHttp,\r\n closeMcpSession,\r\n APP_VERSION,\r\n} from \"./mcp/server.js\";\r\nimport { startHocuspocus, setDocLifecycleCallbacks, getOrCreateDocument } from \"./yjs/provider.js\";\r\nimport { DEFAULT_WS_PORT, DEFAULT_MCP_PORT, CTRL_ROOM } from \"../shared/constants.js\";\r\nimport { cleanupSessions, stopAutoSave } from \"./session/manager.js\";\r\nimport {\r\n saveCurrentSession,\r\n restoreCtrlSession,\r\n restoreOpenDocuments,\r\n writeGenerationId,\r\n} from \"./mcp/document.js\";\r\nimport { freePort, waitForPort } from \"./platform.js\";\r\nimport { isKnownHocuspocusError } from \"./error-filter.js\";\r\nimport {\r\n attachCtrlObservers,\r\n reattachObservers,\r\n reattachCtrlObservers,\r\n detachObservers,\r\n} from \"./events/queue.js\";\r\nimport { getOpenDocs } from \"./mcp/document-service.js\";\r\nimport { openFileByPath } from \"./mcp/file-opener.js\";\r\nimport { docIdFromPath } from \"./mcp/document-model.js\";\r\nimport { injectTutorialAnnotations } from \"./mcp/tutorial-annotations.js\";\r\n\r\n// stdout is exclusively reserved for the MCP JSON-RPC wire protocol (stdio mode).\r\n// Redirect any console.log calls (from Hocuspocus or other libs) to stderr.\r\n// In HTTP mode this is defense-in-depth; in stdio mode it's critical.\r\n\r\nconsole.log = console.error;\r\nconsole.warn = console.error;\r\nconsole.info = console.error;\r\n\r\nconst transportMode = (process.env.TANDEM_TRANSPORT || \"http\").toLowerCase();\r\nconst wsPort = parseInt(process.env.TANDEM_PORT || String(DEFAULT_WS_PORT), 10);\r\nconst mcpPort = parseInt(process.env.TANDEM_MCP_PORT || String(DEFAULT_MCP_PORT), 10);\r\n\r\nlet httpServer: Server | null = null;\r\nlet isShuttingDown = false;\r\n\r\n// Swallow known Hocuspocus/ws protocol errors but crash on genuine bugs.\r\nfunction handleFatalError(label: string, value: unknown): void {\r\n if (value instanceof Error && isKnownHocuspocusError(value)) {\r\n console.error(\"[Tandem] Known WS error (swallowed):\", value.message, value.stack);\r\n return;\r\n }\r\n if (isShuttingDown) {\r\n console.error(`[Tandem] ${label} during shutdown (ignored):`, value);\r\n return;\r\n }\r\n if (value instanceof Error) {\r\n console.error(`[Tandem] ${label} (FATAL):`, value.name, value.message, value.stack);\r\n } else {\r\n console.error(`[Tandem] ${label} (FATAL):`, value);\r\n }\r\n process.exit(1);\r\n}\r\nprocess.on(\"uncaughtException\", (err) => handleFatalError(\"uncaughtException\", err));\r\nprocess.on(\"unhandledRejection\", (reason) => handleFatalError(\"unhandledRejection\", reason));\r\nprocess.on(\"exit\", (code) => {\r\n console.error(`[Tandem] Process exiting with code ${code}`);\r\n});\r\n\r\nif (transportMode === \"stdio\") {\r\n process.stdin.on(\"end\", () => {\r\n console.error(\"[Tandem] stdin ended (MCP transport closed)\");\r\n });\r\n}\r\n\r\n// Graceful shutdown: save session + stop auto-save before exit\r\nasync function shutdown(signal: string) {\r\n if (isShuttingDown) return;\r\n isShuttingDown = true;\r\n console.error(`[Tandem] ${signal} received, saving session...`);\r\n try {\r\n await saveCurrentSession();\r\n stopAutoSave();\r\n } catch (err) {\r\n console.error(\"[Tandem] Session save on shutdown failed:\", err);\r\n }\r\n try {\r\n await closeMcpSession();\r\n } catch (err) {\r\n console.error(\"[Tandem] MCP session close on shutdown failed:\", err);\r\n }\r\n if (httpServer) {\r\n httpServer.close();\r\n }\r\n process.exit(0);\r\n}\r\nprocess.on(\"SIGTERM\", () => shutdown(\"SIGTERM\"));\r\nprocess.on(\"SIGINT\", () => shutdown(\"SIGINT\"));\r\n\r\nasync function main() {\r\n console.error(`[Tandem] Starting server (transport: ${transportMode})...`);\r\n\r\n // Clean up sessions older than 30 days\r\n cleanupSessions()\r\n .then((n) => {\r\n if (n > 0) console.error(`[Tandem] Cleaned up ${n} stale session(s)`);\r\n })\r\n .catch((err) => {\r\n console.error(\"[Tandem] Failed to clean up stale sessions:\", err);\r\n });\r\n\r\n // Must complete before Hocuspocus starts to prevent browsers seeing stale openDocuments\r\n const previousActiveDocId = await restoreCtrlSession().catch((err) => {\r\n console.error(\"[Tandem] Failed to restore chat history:\", err);\r\n return null;\r\n });\r\n\r\n // Re-open documents from previous session before Hocuspocus starts\r\n await restoreOpenDocuments(previousActiveDocId).catch((err) => {\r\n console.error(\"[Tandem] Failed to restore open documents:\", err);\r\n });\r\n\r\n // Write a unique ID so clients can detect when the server process has restarted\r\n writeGenerationId();\r\n\r\n // Attach event queue observers to CTRL_ROOM for channel push notifications\r\n attachCtrlObservers();\r\n\r\n // Register doc lifecycle callbacks so the event queue reattaches observers\r\n // when Hocuspocus swaps Y.Doc instances (avoids circular import).\r\n setDocLifecycleCallbacks(\r\n (docName, newDoc) => {\r\n if (docName === CTRL_ROOM) {\r\n reattachCtrlObservers();\r\n } else {\r\n reattachObservers(docName, newDoc);\r\n }\r\n },\r\n (docName) => {\r\n detachObservers(docName);\r\n },\r\n );\r\n\r\n if (transportMode === \"http\") {\r\n // HTTP mode: no startup-order constraint — start both concurrently\r\n freePort(wsPort);\r\n freePort(mcpPort);\r\n await Promise.all([waitForPort(wsPort), waitForPort(mcpPort)]);\r\n\r\n const [srv] = await Promise.all([\r\n startMcpServerHttp(mcpPort),\r\n startHocuspocus(wsPort).then(() => {\r\n console.error(`[Tandem] Hocuspocus WebSocket server running on ws://localhost:${wsPort}`);\r\n }),\r\n ]);\r\n httpServer = srv;\r\n\r\n // Auto-open sample/welcome.md when no documents are open (fresh install or empty restored session)\r\n if (getOpenDocs().size === 0 && !process.env.TANDEM_NO_SAMPLE) {\r\n const samplePath = path.resolve(\r\n path.dirname(fileURLToPath(import.meta.url)),\r\n \"../../sample/welcome.md\",\r\n );\r\n openFileByPath(samplePath)\r\n .then(() => {\r\n const doc = getOrCreateDocument(docIdFromPath(samplePath));\r\n injectTutorialAnnotations(doc);\r\n })\r\n .catch((err) => {\r\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\r\n console.error(\"[Tandem] Sample file not found (skipping):\", samplePath);\r\n } else {\r\n console.error(\"[Tandem] Failed to auto-open sample document:\", err);\r\n }\r\n });\r\n }\r\n\r\n console.error(\"\");\r\n console.error(` Tandem v${APP_VERSION}`);\r\n console.error(\"\");\r\n console.error(` MCP HTTP: http://localhost:${mcpPort}/mcp`);\r\n console.error(` WebSocket: ws://localhost:${wsPort}`);\r\n console.error(` Health: http://localhost:${mcpPort}/health`);\r\n console.error(\"\");\r\n console.error(\" Open Claude Code in this directory to connect.\");\r\n console.error(\"\");\r\n } else {\r\n // Stdio mode: MCP must start before Hocuspocus to beat Claude Code's init timeout\r\n (async () => {\r\n freePort(wsPort);\r\n await waitForPort(wsPort);\r\n await startHocuspocus(wsPort);\r\n console.error(`[Tandem] Hocuspocus WebSocket server running on ws://localhost:${wsPort}`);\r\n })().catch((err) => {\r\n console.error(\"[Tandem] Hocuspocus startup error:\", err);\r\n });\r\n\r\n await startMcpServerStdio();\r\n console.error(\"[Tandem] MCP server running on stdio\");\r\n }\r\n}\r\n\r\nmain().catch((err) => {\r\n console.error(\"[Tandem] Fatal error:\", err);\r\n process.exit(1);\r\n});\r\n","import { createMcpExpressApp } from \"@modelcontextprotocol/sdk/server/express.js\";\r\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\r\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\r\nimport { isInitializeRequest } from \"@modelcontextprotocol/sdk/types.js\";\r\nimport { randomUUID } from \"crypto\";\r\nimport type { Server } from \"http\";\r\nimport { existsSync } from \"node:fs\";\r\nimport { dirname, join } from \"node:path\";\r\nimport { fileURLToPath } from \"node:url\";\r\nimport { createRequire } from \"module\";\r\n\r\nimport { openBrowser } from \"../open-browser.js\";\r\nimport { registerAnnotationTools } from \"./annotations.js\";\r\nimport { apiMiddleware, registerApiRoutes } from \"./api-routes.js\";\r\nimport { registerAwarenessTools } from \"./awareness.js\";\r\nimport { registerChannelRoutes } from \"./channel-routes.js\";\r\nimport { registerDocumentTools } from \"./document.js\";\r\nimport { registerNavigationTools } from \"./navigation.js\";\r\n\r\nconst esmRequire = createRequire(import.meta.url);\r\nlet APP_VERSION = \"0.0.0-unknown\";\r\ntry {\r\n APP_VERSION = (esmRequire(\"../../package.json\") as { version: string }).version;\r\n} catch (err) {\r\n console.error(\r\n `[Tandem] Could not read version from package.json: ${err instanceof Error ? err.message : err}`,\r\n );\r\n}\r\nexport { APP_VERSION };\r\n\r\nconst __dirname = dirname(fileURLToPath(import.meta.url));\r\n// dist/server/ → dist/client/ (tsup bundles server into dist/server/index.js)\r\nconst CLIENT_DIST = join(__dirname, \"../client\");\r\n\r\n// McpServer is long-lived (tool registrations survive close/reconnect).\r\n// Transport is ephemeral — rotated on each new initialize request.\r\nlet mcpServer: McpServer | null = null;\r\nlet currentTransport: StreamableHTTPServerTransport | null = null;\r\nlet connectingPromise: Promise<void> | null = null;\r\n\r\n/** Create an McpServer with all tool groups registered (no transport). */\r\nfunction createMcpServer(): McpServer {\r\n const server = new McpServer({\r\n name: \"tandem\",\r\n version: APP_VERSION,\r\n });\r\n\r\n registerDocumentTools(server);\r\n registerAnnotationTools(server);\r\n registerNavigationTools(server);\r\n registerAwarenessTools(server);\r\n\r\n return server;\r\n}\r\n\r\n/** Extract the JSON-RPC `id` from a request body (single message only, not batches). */\r\nexport function jsonrpcId(body: unknown): unknown {\r\n return body && typeof body === \"object\" && !Array.isArray(body) && \"id\" in body\r\n ? (body as Record<string, unknown>).id\r\n : null;\r\n}\r\n\r\n/** Send a JSON-RPC error response. */\r\nfunction sendJsonRpcError(\r\n res: import(\"express\").Response,\r\n status: number,\r\n code: number,\r\n message: string,\r\n id: unknown = null,\r\n): void {\r\n res.status(status).json({ jsonrpc: \"2.0\", error: { code, message }, id });\r\n}\r\n\r\n/**\r\n * Tear down the current transport (if any) and connect a fresh one.\r\n * Serialized via connectingPromise so concurrent initialize requests\r\n * don't double-rotate.\r\n */\r\nasync function connectFreshTransport(): Promise<void> {\r\n if (!mcpServer) throw new Error(\"mcpServer not initialized\");\r\n\r\n const doConnect = async () => {\r\n if (currentTransport) {\r\n console.error(\"[Tandem] Closing previous MCP transport session\");\r\n await mcpServer!.close();\r\n currentTransport = null;\r\n }\r\n\r\n const transport = new StreamableHTTPServerTransport({\r\n sessionIdGenerator: () => randomUUID(),\r\n });\r\n\r\n await mcpServer!.connect(transport);\r\n currentTransport = transport;\r\n console.error(\"[Tandem] New MCP session established\");\r\n };\r\n\r\n // Chain behind any in-flight rotation to prevent races\r\n const promise = (connectingPromise ?? Promise.resolve()).then(doConnect);\r\n connectingPromise = promise;\r\n await promise;\r\n // Release the lock once settled so the resolved promise can be GC'd\r\n if (connectingPromise === promise) connectingPromise = null;\r\n}\r\n\r\n/** Close the active MCP session (for graceful shutdown). */\r\nexport async function closeMcpSession(): Promise<void> {\r\n if (currentTransport && mcpServer) {\r\n await mcpServer.close();\r\n currentTransport = null;\r\n }\r\n}\r\n\r\n/** Start the MCP server on stdio (legacy, used as fallback via TANDEM_TRANSPORT=stdio). */\r\nexport async function startMcpServerStdio(): Promise<void> {\r\n const server = createMcpServer();\r\n const transport = new StdioServerTransport();\r\n await server.connect(transport);\r\n}\r\n\r\n/** Start the MCP server on HTTP using Streamable HTTP transport. Returns the http.Server for lifecycle management. */\r\nexport async function startMcpServerHttp(port: number, host = \"127.0.0.1\"): Promise<Server> {\r\n mcpServer = createMcpServer();\r\n\r\n // We need two different body parser limits: 100kb for MCP (SDK default)\r\n // and 70MB for file upload API. createMcpExpressApp applies express.json()\r\n // globally with 100kb limit. Solution: create our own outer app, register\r\n // /api routes with a larger body parser, then mount the SDK app for /mcp.\r\n const { default: express } = await import(\"express\");\r\n const app = express();\r\n\r\n // Large body parser for file-open and upload routes only (up to 70MB).\r\n // NOT mounted globally — other routes (MCP, /health) use the SDK's own parser.\r\n const largeBody = express.json({ limit: \"70mb\" });\r\n\r\n // SDK app provides express.json() (100kb limit) + DNS rebinding protection\r\n const mcpApp = createMcpExpressApp({ host });\r\n\r\n mcpApp.post(\"/mcp\", async (req: import(\"express\").Request, res: import(\"express\").Response) => {\r\n const body = req.body as unknown;\r\n const isInit =\r\n isInitializeRequest(body) || (Array.isArray(body) && body.some(isInitializeRequest));\r\n\r\n if (isInit) {\r\n console.error(\"[Tandem] Received initialize request, rotating transport\");\r\n try {\r\n await connectFreshTransport();\r\n } catch (err) {\r\n console.error(\"[Tandem] Failed to create new transport:\", err);\r\n sendJsonRpcError(res, 500, -32603, \"Internal error\", jsonrpcId(body));\r\n return;\r\n }\r\n }\r\n\r\n if (!currentTransport) {\r\n sendJsonRpcError(res, 503, -32000, \"No active session\", jsonrpcId(body));\r\n return;\r\n }\r\n\r\n await currentTransport.handleRequest(req, res, body);\r\n });\r\n\r\n mcpApp.get(\"/mcp\", async (req: import(\"express\").Request, res: import(\"express\").Response) => {\r\n if (!currentTransport) {\r\n sendJsonRpcError(res, 503, -32000, \"No active session\");\r\n return;\r\n }\r\n await currentTransport.handleRequest(req, res, req.body);\r\n });\r\n\r\n // DELETE — SDK handles session teardown internally\r\n mcpApp.delete(\"/mcp\", async (req: import(\"express\").Request, res: import(\"express\").Response) => {\r\n if (!currentTransport) {\r\n sendJsonRpcError(res, 404, -32001, \"Session not found\");\r\n return;\r\n }\r\n await currentTransport.handleRequest(req, res, req.body);\r\n currentTransport = null;\r\n });\r\n\r\n // Health endpoint on outer app (bypasses SDK's DNS rebinding middleware)\r\n app.get(\"/health\", (_req: import(\"express\").Request, res: import(\"express\").Response) => {\r\n res.json({\r\n status: \"ok\",\r\n version: APP_VERSION,\r\n transport: \"http\",\r\n hasSession: currentTransport !== null,\r\n });\r\n });\r\n\r\n // RFC 9728 Protected Resource Metadata — declares no auth required.\r\n // Newer Claude Code versions probe this before connecting to MCP.\r\n app.get(\r\n \"/.well-known/oauth-protected-resource/mcp\",\r\n (_req: import(\"express\").Request, res: import(\"express\").Response) => {\r\n res.header(\"Access-Control-Allow-Origin\", \"*\");\r\n res.json({\r\n resource: `http://localhost:${port}/mcp`,\r\n bearer_methods_supported: [],\r\n });\r\n },\r\n );\r\n app.get(\r\n \"/.well-known/oauth-protected-resource\",\r\n (_req: import(\"express\").Request, res: import(\"express\").Response) => {\r\n res.header(\"Access-Control-Allow-Origin\", \"*\");\r\n res.json({\r\n resource: `http://localhost:${port}/mcp`,\r\n bearer_methods_supported: [],\r\n });\r\n },\r\n );\r\n\r\n // Mount SDK app (handles /mcp with 100kb body parser + DNS rebinding)\r\n app.use(mcpApp);\r\n\r\n // --- REST API for browser-initiated file opening ---\r\n registerApiRoutes(app, largeBody);\r\n\r\n // --- Channel support endpoints ---\r\n registerChannelRoutes(app, apiMiddleware);\r\n\r\n // Serve built client assets when present (populated by `vite build`).\r\n // express.static falls through for paths it doesn't find, so /mcp, /api/*,\r\n // /health, and channel routes registered above continue to work normally.\r\n // Static routes and SPA fallback intentionally omit apiMiddleware — they only serve\r\n // static assets, no sensitive data.\r\n if (existsSync(CLIENT_DIST)) {\r\n // Express 5 types omit express.static and res.sendFile — they exist at runtime.\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n app.use((express as any).static(CLIENT_DIST, { index: \"index.html\" }));\r\n // SPA fallback: serve index.html for client-side routes not matched above\r\n const indexPath = join(CLIENT_DIST, \"index.html\");\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n app.get(\"/{*path}\", (_req: import(\"express\").Request, res: any) => {\r\n res.sendFile(indexPath);\r\n });\r\n console.error(`[Tandem] Serving client from ${CLIENT_DIST}`);\r\n } else {\r\n console.error(`[Tandem] No client dist at ${CLIENT_DIST} — run 'npm run build' first`);\r\n }\r\n\r\n return new Promise<Server>((resolve, reject) => {\r\n const httpServer = app.listen(port, host, () => {\r\n httpServer.removeListener(\"error\", reject);\r\n httpServer.on(\"error\", (err: Error) => console.error(\"[Tandem] HTTP server error:\", err));\r\n console.error(`[Tandem] MCP HTTP server on http://${host}:${port}/mcp`);\r\n if (process.env.TANDEM_OPEN_BROWSER === \"1\") {\r\n if (existsSync(CLIENT_DIST)) {\r\n openBrowser(`http://localhost:${port}`);\r\n } else {\r\n console.error(\"[Tandem] Skipping browser open — no client assets found\");\r\n }\r\n }\r\n resolve(httpServer);\r\n });\r\n httpServer.on(\"error\", reject);\r\n });\r\n}\r\n","import { execFile } from \"node:child_process\";\r\n\r\n/**\r\n * Open a URL in the default browser.\r\n * Best-effort — errors are logged to stderr, never thrown.\r\n */\r\nexport function openBrowser(url: string): void {\r\n let command: string;\r\n let args: string[];\r\n\r\n if (process.platform === \"win32\") {\r\n // `start` requires the empty title arg (\"\") to handle URLs with & correctly\r\n command = \"cmd\";\r\n args = [\"/c\", \"start\", \"\", url];\r\n } else if (process.platform === \"darwin\") {\r\n command = \"open\";\r\n args = [url];\r\n } else {\r\n command = \"xdg-open\";\r\n args = [url];\r\n }\r\n\r\n execFile(command, args, (err) => {\r\n if (err) {\r\n console.error(\"[Tandem] Could not open browser automatically.\");\r\n console.error(`[Tandem] Open this URL manually: ${url}`);\r\n }\r\n });\r\n}\r\n","import { Y_MAP_ANNOTATIONS } from \"../../shared/constants.js\";\r\nimport { MCP_ORIGIN } from \"../events/queue.js\";\r\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { z } from \"zod\";\r\nimport { getOrCreateDocument } from \"../yjs/provider.js\";\r\nimport { getCurrentDoc, extractText } from \"./document.js\";\r\nimport { mcpSuccess, mcpError, noDocumentError, withErrorBoundary } from \"./response.js\";\r\nimport { exportAnnotations } from \"../file-io/docx.js\";\r\nimport * as Y from \"yjs\";\r\nimport type { Annotation, AnnotationType, HighlightColor } from \"../../shared/types.js\";\r\nimport {\r\n AnnotationTypeSchema,\r\n AnnotationStatusSchema,\r\n AnnotationPrioritySchema,\r\n HighlightColorSchema,\r\n AuthorSchema,\r\n AnnotationActionSchema,\r\n ExportFormatSchema,\r\n toFlatOffset,\r\n} from \"../../shared/types.js\";\r\nimport { anchoredRange, refreshAllRanges } from \"../positions.js\";\r\nimport type { RangeValidation, AnchoredRangeResult } from \"../../shared/positions/index.js\";\r\nimport { pushNotification } from \"../notifications.js\";\r\nimport { generateAnnotationId, generateNotificationId } from \"../../shared/utils.js\";\r\n\r\n/** Get the Y.Doc and annotations Y.Map for a document, or null if no doc is open */\r\nfunction getDocAndAnnotations(documentId?: string): { ydoc: Y.Doc; map: Y.Map<unknown> } | null {\r\n const doc = getCurrentDoc(documentId);\r\n if (!doc) return null;\r\n const ydoc = getOrCreateDocument(doc.docName);\r\n return { ydoc, map: ydoc.getMap(Y_MAP_ANNOTATIONS) };\r\n}\r\n\r\n/** Human-readable message for a range validation failure. */\r\nfunction rangeFailureMessage(result: Extract<RangeValidation, { ok: false }>): string {\r\n if (result.code === \"RANGE_GONE\") return \"Target text no longer exists in the document.\";\r\n if (result.code === \"RANGE_MOVED\") return \"Target text has moved.\";\r\n if (result.code === \"INVALID_RANGE\") return result.message;\r\n return 'Range overlaps with heading markup (e.g., \"## \"). Target the text content only.';\r\n}\r\n\r\n/** Convert an anchoredRange validation failure to an MCP error response. */\r\nfunction rangeFailureToError(result: Extract<RangeValidation, { ok: false }>) {\r\n if (result.code === \"RANGE_GONE\") {\r\n return mcpError(\"RANGE_GONE\", \"Target text no longer exists in the document.\");\r\n }\r\n if (result.code === \"RANGE_MOVED\") {\r\n return mcpError(\"RANGE_MOVED\", \"Target text has moved. Use resolvedFrom/resolvedTo to retry.\", {\r\n resolvedFrom: result.resolvedFrom,\r\n resolvedTo: result.resolvedTo,\r\n });\r\n }\r\n if (result.code === \"INVALID_RANGE\") {\r\n return mcpError(\"INVALID_RANGE\", result.message);\r\n }\r\n // HEADING_OVERLAP\r\n return mcpError(\r\n \"INVALID_RANGE\",\r\n 'Range overlaps with heading markup (e.g., \"## \"). Target the text content only.',\r\n );\r\n}\r\n\r\n/** Push a notification to the browser alongside the MCP error response. */\r\nfunction notifyRangeFailure(\r\n result: Extract<RangeValidation, { ok: false }>,\r\n toolName: string,\r\n documentId?: string,\r\n): void {\r\n pushNotification({\r\n id: generateNotificationId(),\r\n type: \"annotation-error\",\r\n severity: \"error\",\r\n message: `Annotation failed: ${rangeFailureMessage(result)}`,\r\n toolName,\r\n errorCode: result.code,\r\n documentId,\r\n dedupKey: `${toolName}:${result.code}`,\r\n timestamp: Date.now(),\r\n });\r\n}\r\n\r\nconst SNAPSHOT_CAP = 200;\r\n/** Capture a text snapshot from the document at the given range, truncated to SNAPSHOT_CAP chars. */\r\nfunction captureSnapshot(ydoc: Y.Doc, from: number, to: number): string {\r\n const text = extractText(ydoc).slice(from, to);\r\n return text.length > SNAPSHOT_CAP ? text.slice(0, SNAPSHOT_CAP - 3) + \"...\" : text;\r\n}\r\n\r\n/** Create an annotation from an anchored range result and store it in the Y.Map.\r\n * The ydoc parameter is required for origin-tagged transactions (prevents channel echo). */\r\nexport function createAnnotation(\r\n map: Y.Map<unknown>,\r\n ydoc: Y.Doc,\r\n type: AnnotationType,\r\n anchored: AnchoredRangeResult,\r\n content: string,\r\n extras?: Partial<Annotation>,\r\n): string {\r\n const id = generateAnnotationId();\r\n\r\n const annotation: Annotation = {\r\n id,\r\n author: \"claude\",\r\n type,\r\n range: anchored.range,\r\n ...(anchored.relRange ? { relRange: anchored.relRange } : {}),\r\n content,\r\n status: \"pending\",\r\n timestamp: Date.now(),\r\n ...extras,\r\n };\r\n ydoc.transact(() => map.set(id, annotation), MCP_ORIGIN);\r\n return id;\r\n}\r\n\r\n/** Collect all annotations from the Y.Map as an array */\r\nexport function collectAnnotations(map: Y.Map<unknown>): Annotation[] {\r\n const result: Annotation[] = [];\r\n map.forEach((value) => result.push(value as Annotation));\r\n return result;\r\n}\r\n\r\nexport { refreshRange, refreshAllRanges } from \"../positions.js\";\r\n\r\nexport function registerAnnotationTools(server: McpServer): void {\r\n server.tool(\r\n \"tandem_highlight\",\r\n \"Highlight text with a color and optional note\",\r\n {\r\n from: z.number().describe(\"Start position\"),\r\n to: z.number().describe(\"End position\"),\r\n color: HighlightColorSchema.describe(\"Highlight color\"),\r\n note: z.string().optional().describe(\"Optional note for the highlight\"),\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n priority: AnnotationPrioritySchema.optional().describe(\r\n \"Annotation priority. Set to 'urgent' for critical issues that should be visible even when the user has interruption mode set to urgent-only. Flags and questions are implicitly urgent.\",\r\n ),\r\n textSnapshot: z\r\n .string()\r\n .optional()\r\n .describe(\r\n \"Expected text at [from, to] — returns RANGE_MOVED with relocated range on mismatch, or RANGE_GONE if text was deleted\",\r\n ),\r\n },\r\n withErrorBoundary(\r\n \"tandem_highlight\",\r\n async ({ from: rawFrom, to: rawTo, color, note, documentId, priority, textSnapshot }) => {\r\n const da = getDocAndAnnotations(documentId);\r\n if (!da) return noDocumentError();\r\n const from = toFlatOffset(rawFrom);\r\n const to = toFlatOffset(rawTo);\r\n const result = anchoredRange(da.ydoc, from, to, textSnapshot);\r\n if (!result.ok) {\r\n notifyRangeFailure(result, \"tandem_highlight\", documentId);\r\n return rangeFailureToError(result);\r\n }\r\n const snap = captureSnapshot(da.ydoc, result.range.from, result.range.to);\r\n const id = createAnnotation(da.map, da.ydoc, \"highlight\", result, note || \"\", {\r\n color: color as HighlightColor,\r\n ...(priority ? { priority } : {}),\r\n textSnapshot: snap,\r\n });\r\n return mcpSuccess({ annotationId: id });\r\n },\r\n ),\r\n );\r\n\r\n server.tool(\r\n \"tandem_comment\",\r\n \"Add a comment to a text range\",\r\n {\r\n from: z.number().describe(\"Start position\"),\r\n to: z.number().describe(\"End position\"),\r\n text: z.string().describe(\"Comment text\"),\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n priority: AnnotationPrioritySchema.optional().describe(\r\n \"Annotation priority. Set to 'urgent' for critical issues that should be visible even when the user has interruption mode set to urgent-only. Flags and questions are implicitly urgent.\",\r\n ),\r\n textSnapshot: z\r\n .string()\r\n .optional()\r\n .describe(\r\n \"Expected text at [from, to] — returns RANGE_MOVED with relocated range on mismatch, or RANGE_GONE if text was deleted\",\r\n ),\r\n },\r\n withErrorBoundary(\r\n \"tandem_comment\",\r\n async ({ from: rawFrom, to: rawTo, text, documentId, priority, textSnapshot }) => {\r\n const da = getDocAndAnnotations(documentId);\r\n if (!da) return noDocumentError();\r\n const from = toFlatOffset(rawFrom);\r\n const to = toFlatOffset(rawTo);\r\n const result = anchoredRange(da.ydoc, from, to, textSnapshot);\r\n if (!result.ok) {\r\n notifyRangeFailure(result, \"tandem_comment\", documentId);\r\n return rangeFailureToError(result);\r\n }\r\n const snap = captureSnapshot(da.ydoc, result.range.from, result.range.to);\r\n const id = createAnnotation(da.map, da.ydoc, \"comment\", result, text, {\r\n ...(priority ? { priority } : {}),\r\n textSnapshot: snap,\r\n });\r\n return mcpSuccess({ annotationId: id });\r\n },\r\n ),\r\n );\r\n\r\n server.tool(\r\n \"tandem_suggest\",\r\n \"Propose a text replacement (tracked change style)\",\r\n {\r\n from: z.number().describe(\"Start position\"),\r\n to: z.number().describe(\"End position\"),\r\n newText: z.string().describe(\"Suggested replacement text\"),\r\n reason: z.string().optional().describe(\"Reason for the suggestion\"),\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n priority: AnnotationPrioritySchema.optional().describe(\r\n \"Annotation priority. Set to 'urgent' for critical issues that should be visible even when the user has interruption mode set to urgent-only. Flags and questions are implicitly urgent.\",\r\n ),\r\n textSnapshot: z\r\n .string()\r\n .optional()\r\n .describe(\r\n \"Expected text at [from, to] — returns RANGE_MOVED with relocated range on mismatch, or RANGE_GONE if text was deleted\",\r\n ),\r\n },\r\n withErrorBoundary(\r\n \"tandem_suggest\",\r\n async ({ from: rawFrom, to: rawTo, newText, reason, documentId, priority, textSnapshot }) => {\r\n const da = getDocAndAnnotations(documentId);\r\n if (!da) return noDocumentError();\r\n const from = toFlatOffset(rawFrom);\r\n const to = toFlatOffset(rawTo);\r\n const result = anchoredRange(da.ydoc, from, to, textSnapshot);\r\n if (!result.ok) {\r\n notifyRangeFailure(result, \"tandem_suggest\", documentId);\r\n return rangeFailureToError(result);\r\n }\r\n const snap = captureSnapshot(da.ydoc, result.range.from, result.range.to);\r\n const id = createAnnotation(\r\n da.map,\r\n da.ydoc,\r\n \"suggestion\",\r\n result,\r\n JSON.stringify({ newText, reason: reason || \"\" }),\r\n { ...(priority ? { priority } : {}), textSnapshot: snap },\r\n );\r\n return mcpSuccess({ annotationId: id });\r\n },\r\n ),\r\n );\r\n\r\n server.tool(\r\n \"tandem_flag\",\r\n \"Flag a text range for attention (e.g., issues, concerns, or items needing review)\",\r\n {\r\n from: z.number().describe(\"Start position\"),\r\n to: z.number().describe(\"End position\"),\r\n note: z.string().optional().describe(\"Reason for flagging\"),\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n priority: AnnotationPrioritySchema.optional().describe(\r\n \"Annotation priority. Set to 'urgent' for critical issues that should be visible even when the user has interruption mode set to urgent-only. Flags and questions are implicitly urgent.\",\r\n ),\r\n textSnapshot: z\r\n .string()\r\n .optional()\r\n .describe(\r\n \"Expected text at [from, to] — returns RANGE_MOVED with relocated range on mismatch, or RANGE_GONE if text was deleted\",\r\n ),\r\n },\r\n withErrorBoundary(\r\n \"tandem_flag\",\r\n async ({ from: rawFrom, to: rawTo, note, documentId, priority, textSnapshot }) => {\r\n const da = getDocAndAnnotations(documentId);\r\n if (!da) return noDocumentError();\r\n const from = toFlatOffset(rawFrom);\r\n const to = toFlatOffset(rawTo);\r\n const result = anchoredRange(da.ydoc, from, to, textSnapshot);\r\n if (!result.ok) {\r\n notifyRangeFailure(result, \"tandem_flag\", documentId);\r\n return rangeFailureToError(result);\r\n }\r\n const snap = captureSnapshot(da.ydoc, result.range.from, result.range.to);\r\n const id = createAnnotation(da.map, da.ydoc, \"flag\", result, note || \"\", {\r\n ...(priority ? { priority } : {}),\r\n textSnapshot: snap,\r\n });\r\n return mcpSuccess({ annotationId: id });\r\n },\r\n ),\r\n );\r\n\r\n server.tool(\r\n \"tandem_getAnnotations\",\r\n \"Read all annotations, optionally filtered by author/type/status. For checking new user actions, prefer tandem_checkInbox.\",\r\n {\r\n author: AuthorSchema.optional().describe(\"Filter by author\"),\r\n type: AnnotationTypeSchema.optional().describe(\"Filter by type\"),\r\n status: AnnotationStatusSchema.optional().describe(\"Filter by status\"),\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\"tandem_getAnnotations\", async ({ author, type, status, documentId }) => {\r\n const da = getDocAndAnnotations(documentId);\r\n if (!da) return noDocumentError();\r\n\r\n let results = refreshAllRanges(collectAnnotations(da.map), da.ydoc, da.map);\r\n if (author) results = results.filter((a) => a.author === author);\r\n if (type) results = results.filter((a) => a.type === type);\r\n if (status) results = results.filter((a) => a.status === status);\r\n\r\n return mcpSuccess({ annotations: results, count: results.length });\r\n }),\r\n );\r\n\r\n server.tool(\r\n \"tandem_resolveAnnotation\",\r\n \"Accept or dismiss an annotation\",\r\n {\r\n id: z.string().describe(\"Annotation ID\"),\r\n action: AnnotationActionSchema.describe(\"Action to take\"),\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\"tandem_resolveAnnotation\", async ({ id, action, documentId }) => {\r\n const da = getDocAndAnnotations(documentId);\r\n if (!da) return noDocumentError();\r\n\r\n const ann = da.map.get(id) as Annotation | undefined;\r\n if (!ann) return mcpError(\"INVALID_RANGE\", `Annotation ${id} not found`);\r\n\r\n const updated = {\r\n ...ann,\r\n status: action === \"accept\" ? (\"accepted\" as const) : (\"dismissed\" as const),\r\n };\r\n da.ydoc.transact(() => da.map.set(id, updated), MCP_ORIGIN);\r\n return mcpSuccess({ id, status: updated.status });\r\n }),\r\n );\r\n\r\n server.tool(\r\n \"tandem_removeAnnotation\",\r\n \"Remove an annotation entirely\",\r\n {\r\n id: z.string().describe(\"Annotation ID\"),\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\"tandem_removeAnnotation\", async ({ id, documentId }) => {\r\n const da = getDocAndAnnotations(documentId);\r\n if (!da) return noDocumentError();\r\n if (!da.map.has(id)) return mcpError(\"INVALID_RANGE\", `Annotation ${id} not found`);\r\n da.ydoc.transact(() => da.map.delete(id), MCP_ORIGIN);\r\n return mcpSuccess({ removed: true, id });\r\n }),\r\n );\r\n\r\n server.tool(\r\n \"tandem_editAnnotation\",\r\n \"Edit the content of an existing annotation. For suggestions, use newText/reason params; for other types, use content.\",\r\n {\r\n id: z.string().describe(\"Annotation ID\"),\r\n content: z.string().optional().describe(\"New text for non-suggestion annotations\"),\r\n newText: z.string().optional().describe(\"For suggestions: new replacement text\"),\r\n reason: z.string().optional().describe(\"For suggestions: new reason\"),\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\r\n \"tandem_editAnnotation\",\r\n async ({ id, content, newText, reason, documentId }) => {\r\n const da = getDocAndAnnotations(documentId);\r\n if (!da) return noDocumentError();\r\n\r\n const ann = da.map.get(id) as Annotation | undefined;\r\n if (!ann) return mcpError(\"INVALID_RANGE\", `Annotation ${id} not found`);\r\n\r\n if (ann.status !== \"pending\") {\r\n return mcpError(\"INVALID_RANGE\", `Cannot edit a ${ann.status} annotation`);\r\n }\r\n\r\n let updatedContent: string;\r\n\r\n if (ann.type === \"suggestion\") {\r\n if (content !== undefined && newText === undefined && reason === undefined) {\r\n // Allow raw content override for suggestions too\r\n updatedContent = content;\r\n } else if (newText !== undefined || reason !== undefined) {\r\n // Merge with existing suggestion fields\r\n let existing: { newText: string; reason: string };\r\n try {\r\n existing = JSON.parse(ann.content);\r\n } catch {\r\n console.error(\r\n `[tandem_editAnnotation] Malformed existing content for suggestion ${id}`,\r\n );\r\n return mcpError(\r\n \"INVALID_RANGE\",\r\n `Suggestion ${id} has malformed content — cannot merge fields. Use 'content' to replace entirely.`,\r\n );\r\n }\r\n updatedContent = JSON.stringify({\r\n newText: newText !== undefined ? newText : existing.newText,\r\n reason: reason !== undefined ? reason : existing.reason,\r\n });\r\n } else {\r\n return mcpError(\r\n \"INVALID_RANGE\",\r\n \"No editable fields provided. Use newText/reason for suggestions, or content.\",\r\n );\r\n }\r\n } else {\r\n if (content === undefined) {\r\n return mcpError(\r\n \"INVALID_RANGE\",\r\n \"No editable fields provided. Use content for non-suggestion annotations.\",\r\n );\r\n }\r\n updatedContent = content;\r\n }\r\n\r\n const updated = { ...ann, content: updatedContent, editedAt: Date.now() };\r\n da.ydoc.transact(() => da.map.set(id, updated), MCP_ORIGIN);\r\n return mcpSuccess({ id, content: updatedContent, editedAt: updated.editedAt });\r\n },\r\n ),\r\n );\r\n\r\n server.tool(\r\n \"tandem_exportAnnotations\",\r\n \"Export all annotations as a formatted summary. Useful for review reports, especially on read-only .docx files.\",\r\n {\r\n format: ExportFormatSchema.optional().describe(\"Output format (default: markdown)\"),\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\"tandem_exportAnnotations\", async ({ format, documentId }) => {\r\n const da = getDocAndAnnotations(documentId);\r\n if (!da) return noDocumentError();\r\n\r\n const annotations = refreshAllRanges(collectAnnotations(da.map), da.ydoc, da.map);\r\n const { ydoc } = da;\r\n\r\n if (format === \"json\") {\r\n const fullText = extractText(ydoc);\r\n const enriched = annotations.map((ann) => ({\r\n ...ann,\r\n textSnippet: fullText.slice(\r\n Math.max(0, ann.range.from),\r\n Math.min(fullText.length, ann.range.to),\r\n ),\r\n }));\r\n return mcpSuccess({ annotations: enriched, count: enriched.length });\r\n }\r\n\r\n const markdown = exportAnnotations(ydoc, annotations);\r\n return mcpSuccess({ markdown, count: annotations.length });\r\n }),\r\n );\r\n}\r\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { z } from \"zod\";\r\nimport * as Y from \"yjs\";\r\nimport { getOrCreateDocument } from \"../yjs/provider.js\";\r\nimport {\r\n mcpSuccess,\r\n mcpError,\r\n noDocumentError,\r\n getErrorMessage,\r\n withErrorBoundary,\r\n} from \"./response.js\";\r\nimport { pushNotification } from \"../notifications.js\";\r\nimport { generateNotificationId } from \"../../shared/utils.js\";\r\nimport { headingPrefix } from \"../../shared/offsets.js\";\r\nimport { getAdapter, atomicWrite } from \"../file-io/index.js\";\r\nimport { convertToMarkdown } from \"./convert.js\";\r\nimport { saveSession } from \"../session/manager.js\";\r\nimport { openFileByPath } from \"./file-opener.js\";\r\nimport {\r\n INTERRUPTION_MODE_DEFAULT,\r\n Y_MAP_DOCUMENT_META,\r\n Y_MAP_SAVED_AT_VERSION,\r\n Y_MAP_USER_AWARENESS,\r\n} from \"../../shared/constants.js\";\r\nimport { toFlatOffset } from \"../../shared/types.js\";\r\nimport { MCP_ORIGIN } from \"../events/queue.js\";\r\n\r\n// Document model (pure logic)\r\nimport { extractText, getElementText, getOrCreateXmlText } from \"./document-model.js\";\r\n\r\n// Position system\r\nimport { validateRange, resolveToElement } from \"../positions.js\";\r\n\r\n// Document service (state management)\r\nimport {\r\n getOpenDocs,\r\n getActiveDocId,\r\n setActiveDocId,\r\n getCurrentDoc,\r\n requireDocument,\r\n broadcastOpenDocs,\r\n toDocListEntry,\r\n hasDoc,\r\n docCount,\r\n closeDocumentById,\r\n} from \"./document-service.js\";\r\n\r\n// Re-export for backward compatibility with existing consumers.\r\nexport {\r\n extractText,\r\n extractMarkdown,\r\n populateYDoc,\r\n getElementText,\r\n findXmlText,\r\n getOrCreateXmlText,\r\n resolveOffset,\r\n verifyAndResolveRange,\r\n detectFormat,\r\n docIdFromPath,\r\n getHeadingPrefixLength,\r\n} from \"./document-model.js\";\r\nexport type { ResolvedOffset, RangeVerifyResult } from \"./document-model.js\";\r\n\r\n// Position system re-exports\r\nexport {\r\n validateRange,\r\n anchoredRange,\r\n resolveToElement,\r\n flatOffsetToRelPos,\r\n relPosToFlatOffset,\r\n refreshRange,\r\n refreshAllRanges,\r\n} from \"../positions.js\";\r\nexport type {\r\n RangeValidation,\r\n AnchoredRangeResult,\r\n ElementPosition,\r\n} from \"../../shared/positions/index.js\";\r\nexport {\r\n getCurrentDoc,\r\n getOpenDocs,\r\n getActiveDocId,\r\n setActiveDocId,\r\n requireDocument,\r\n toDocListEntry,\r\n saveCurrentSession,\r\n restoreCtrlSession,\r\n restoreOpenDocuments,\r\n writeGenerationId,\r\n addDoc,\r\n removeDoc,\r\n hasDoc,\r\n docCount,\r\n} from \"./document-service.js\";\r\nexport type { OpenDoc } from \"./document-service.js\";\r\nexport { openFileByPath, openFileFromContent, SUPPORTED_EXTENSIONS } from \"./file-opener.js\";\r\nexport type { OpenFileResult } from \"./file-opener.js\";\r\n\r\nexport interface OutlineEntry {\r\n level: number;\r\n text: string;\r\n index: number;\r\n}\r\n\r\n/** Extract document outline (headings). Pure logic exported for testing. */\r\nexport function getOutline(fragment: Y.XmlFragment): OutlineEntry[] {\r\n const outline: OutlineEntry[] = [];\r\n for (let i = 0; i < fragment.length; i++) {\r\n const node = fragment.get(i);\r\n if (node instanceof Y.XmlElement && node.nodeName === \"heading\") {\r\n const level = Number(node.getAttribute(\"level\") ?? 1);\r\n outline.push({ level, text: getElementText(node), index: i });\r\n }\r\n }\r\n return outline;\r\n}\r\n\r\n/** Extract a section by heading text (case-insensitive). Pure logic exported for testing. */\r\nexport function getSection(\r\n fragment: Y.XmlFragment,\r\n sectionName: string,\r\n): { found: true; text: string } | { found: false } {\r\n const lines: string[] = [];\r\n let inSection = false;\r\n let sectionLevel = 0;\r\n\r\n for (let i = 0; i < fragment.length; i++) {\r\n const node = fragment.get(i);\r\n if (!(node instanceof Y.XmlElement)) continue;\r\n\r\n const text = getElementText(node);\r\n\r\n if (node.nodeName === \"heading\") {\r\n const level = Number(node.getAttribute(\"level\") ?? 1);\r\n if (inSection && level <= sectionLevel) break;\r\n if (text.trim().toLowerCase() === sectionName.trim().toLowerCase()) {\r\n inSection = true;\r\n sectionLevel = level;\r\n lines.push(headingPrefix(level) + text);\r\n continue;\r\n }\r\n }\r\n\r\n if (inSection) {\r\n if (node.nodeName === \"heading\") {\r\n const level = Number(node.getAttribute(\"level\") ?? 1);\r\n lines.push(headingPrefix(level) + text);\r\n } else {\r\n lines.push(text);\r\n }\r\n }\r\n }\r\n\r\n if (!inSection) return { found: false };\r\n return { found: true, text: lines.join(\"\\n\") };\r\n}\r\n\r\nexport function registerDocumentTools(server: McpServer): void {\r\n const openDocs = getOpenDocs();\r\n\r\n server.tool(\r\n \"tandem_open\",\r\n \"Open a file in the Tandem editor. Returns a documentId for multi-document workflows. Auto-opens browser. Pass force=true to reload from disk if the file changed externally.\",\r\n {\r\n filePath: z.string().describe(\"Absolute path to the file to open\"),\r\n force: z\r\n .boolean()\r\n .optional()\r\n .describe(\"Force reload from disk even if already open. Clears annotations and session.\"),\r\n },\r\n withErrorBoundary(\"tandem_open\", async ({ filePath, force }) => {\r\n try {\r\n const result = await openFileByPath(filePath, { force });\r\n return mcpSuccess({\r\n ...result,\r\n message: result.forceReloaded\r\n ? `Force-reloaded from disk: ${result.fileName}`\r\n : result.alreadyOpen\r\n ? `Switched to already-open document: ${result.fileName}`\r\n : result.restoredFromSession\r\n ? `Session restored: ${result.fileName} (annotations preserved)`\r\n : result.readOnly\r\n ? `Document opened (review only): ${result.fileName}`\r\n : `Document opened: ${result.fileName}`,\r\n });\r\n } catch (err: unknown) {\r\n const e = err as NodeJS.ErrnoException;\r\n if (e.code === \"ENOENT\" || e.code === \"FILE_NOT_FOUND\") {\r\n return mcpError(\"FILE_NOT_FOUND\", e.message);\r\n }\r\n if (e.code === \"INVALID_PATH\") {\r\n return mcpError(\"FILE_NOT_FOUND\", e.message);\r\n }\r\n if (e.code === \"UNSUPPORTED_FORMAT\" || e.code === \"FILE_TOO_LARGE\") {\r\n return mcpError(\"FORMAT_ERROR\", e.message);\r\n }\r\n if (e.code === \"EBUSY\" || e.code === \"EPERM\") {\r\n return mcpError(\r\n \"FILE_LOCKED\",\r\n `File is locked — another program (likely Microsoft Word) has it open. Close it and try again.`,\r\n );\r\n }\r\n if (e.code === \"EACCES\") {\r\n return mcpError(\"PERMISSION_DENIED\", e.message);\r\n }\r\n return mcpError(\"FORMAT_ERROR\", getErrorMessage(err));\r\n }\r\n }),\r\n );\r\n\r\n server.tool(\r\n \"tandem_getContent\",\r\n \"Read full document content. Warning: token-heavy for large docs. Use getOutline() or getTextContent() instead.\",\r\n {\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\"tandem_getContent\", async ({ documentId }) => {\r\n const r = requireDocument(documentId);\r\n if (!r) return noDocumentError();\r\n const fragment = r.doc.getXmlFragment(\"default\");\r\n return mcpSuccess({ content: fragment.toJSON(), filePath: r.filePath, documentId: r.docId });\r\n }),\r\n );\r\n\r\n server.tool(\r\n \"tandem_getTextContent\",\r\n \"Read document as plain text. ~60% fewer tokens than getContent().\",\r\n {\r\n section: z.string().optional().describe(\"Optional heading text to read only that section\"),\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\"tandem_getTextContent\", async ({ section, documentId }) => {\r\n const r = requireDocument(documentId);\r\n if (!r) return noDocumentError();\r\n\r\n if (section) {\r\n const fragment = r.doc.getXmlFragment(\"default\");\r\n const result = getSection(fragment, section);\r\n if (!result.found) {\r\n return mcpError(\"INVALID_RANGE\", `Section \"${section}\" not found in document.`);\r\n }\r\n return mcpSuccess({ text: result.text, filePath: r.filePath, section });\r\n }\r\n\r\n // Always use extractText — its offsets match validateRange/anchoredRange.\r\n // extractMarkdown adds markdown syntax (e.g. `> ` for blockquotes) that\r\n // shifts offsets, causing RANGE_MOVED errors in annotation tools.\r\n const text = extractText(r.doc);\r\n return mcpSuccess({ text, filePath: r.filePath, documentId: r.docId });\r\n }),\r\n );\r\n\r\n server.tool(\r\n \"tandem_getOutline\",\r\n \"Get document structure (headings, sections) without full content. Low token cost.\",\r\n {\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\"tandem_getOutline\", async ({ documentId }) => {\r\n const r = requireDocument(documentId);\r\n if (!r) return noDocumentError();\r\n const fragment = r.doc.getXmlFragment(\"default\");\r\n const outline = getOutline(fragment);\r\n return mcpSuccess({ outline, totalNodes: fragment.length });\r\n }),\r\n );\r\n\r\n server.tool(\r\n \"tandem_edit\",\r\n \"Edit text in the document at a specific range. For single-paragraph replacements only — newlines in newText are inserted as literal text.\",\r\n {\r\n from: z.number().describe(\"Start position (character offset)\"),\r\n to: z.number().describe(\"End position (character offset)\"),\r\n newText: z.string().describe(\"Replacement text (single paragraph — no newlines)\"),\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n textSnapshot: z\r\n .string()\r\n .optional()\r\n .describe(\r\n \"Expected text at [from, to] — returns RANGE_MOVED with relocated range on mismatch, or RANGE_GONE if text was deleted\",\r\n ),\r\n },\r\n withErrorBoundary(\r\n \"tandem_edit\",\r\n async ({ from: rawFrom, to: rawTo, newText, documentId, textSnapshot }) => {\r\n const r = requireDocument(documentId);\r\n if (!r) return noDocumentError();\r\n\r\n const docState = getCurrentDoc(documentId);\r\n if (docState?.readOnly) {\r\n return mcpError(\r\n \"FORMAT_ERROR\",\r\n \"Document is read-only (.docx). Use annotations instead.\",\r\n );\r\n }\r\n\r\n const from = toFlatOffset(rawFrom);\r\n const to = toFlatOffset(rawTo);\r\n const v = validateRange(r.doc, from, to, {\r\n textSnapshot,\r\n rejectHeadingOverlap: true,\r\n });\r\n if (!v.ok) {\r\n if (v.code === \"RANGE_GONE\") {\r\n return mcpError(\"RANGE_GONE\", \"Target text no longer exists in the document.\");\r\n }\r\n if (v.code === \"RANGE_MOVED\") {\r\n return mcpError(\r\n \"RANGE_MOVED\",\r\n \"Target text has moved. Use resolvedFrom/resolvedTo to retry.\",\r\n { resolvedFrom: v.resolvedFrom, resolvedTo: v.resolvedTo },\r\n );\r\n }\r\n if (v.code === \"HEADING_OVERLAP\") {\r\n return mcpError(\r\n \"INVALID_RANGE\",\r\n 'Edit range overlaps with heading markup (e.g., \"## \"). Target the text content only. ' +\r\n \"Use tandem_resolveRange to find the text position.\",\r\n );\r\n }\r\n return mcpError(\"INVALID_RANGE\", v.message);\r\n }\r\n\r\n const fragment = r.doc.getXmlFragment(\"default\");\r\n const startPos = resolveToElement(fragment, from);\r\n const endPos = resolveToElement(fragment, to);\r\n\r\n if (!startPos || !endPos) {\r\n return mcpError(\r\n \"INVALID_RANGE\",\r\n `Cannot resolve offset range [${from}, ${to}] in document.`,\r\n );\r\n }\r\n\r\n if (startPos.elementIndex !== endPos.elementIndex) {\r\n r.doc.transact(() => {\r\n const startNode = fragment.get(startPos.elementIndex) as Y.XmlElement;\r\n const startText = getOrCreateXmlText(startNode);\r\n const startLen = startText.toString().length;\r\n if (startPos.textOffset < startLen) {\r\n startText.delete(startPos.textOffset, startLen - startPos.textOffset);\r\n }\r\n\r\n const deleteCount = endPos.elementIndex - startPos.elementIndex - 1;\r\n for (let i = 0; i < deleteCount; i++) {\r\n fragment.delete(startPos.elementIndex + 1, 1);\r\n }\r\n\r\n const endNode = fragment.get(startPos.elementIndex + 1) as Y.XmlElement;\r\n const endText = getOrCreateXmlText(endNode);\r\n if (endPos.textOffset > 0) {\r\n endText.delete(0, endPos.textOffset);\r\n }\r\n const remainingText = endText.toString();\r\n if (remainingText.length > 0) {\r\n startText.insert(startPos.textOffset, remainingText);\r\n }\r\n fragment.delete(startPos.elementIndex + 1, 1);\r\n\r\n startText.insert(startPos.textOffset, newText);\r\n }, MCP_ORIGIN);\r\n } else {\r\n r.doc.transact(() => {\r\n const node = fragment.get(startPos.elementIndex) as Y.XmlElement;\r\n const textNode = getOrCreateXmlText(node);\r\n const deleteLen = endPos.textOffset - startPos.textOffset;\r\n if (deleteLen > 0) {\r\n textNode.delete(startPos.textOffset, deleteLen);\r\n }\r\n if (newText.length > 0) {\r\n textNode.insert(startPos.textOffset, newText);\r\n }\r\n }, MCP_ORIGIN);\r\n }\r\n\r\n return mcpSuccess({ edited: true, from, to, newTextLength: newText.length });\r\n },\r\n ),\r\n );\r\n\r\n server.tool(\r\n \"tandem_save\",\r\n \"Save the current document back to disk\",\r\n {\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\"tandem_save\", async ({ documentId }) => {\r\n const r = requireDocument(documentId);\r\n if (!r) return noDocumentError();\r\n try {\r\n const docState = getCurrentDoc(documentId);\r\n const format = docState?.format ?? \"txt\";\r\n const readOnly = docState?.readOnly ?? false;\r\n\r\n // Uploaded files have no disk path — session-only save\r\n if (docState?.source === \"upload\") {\r\n await saveSession(r.filePath, format, r.doc);\r\n return mcpSuccess({\r\n saved: true,\r\n sessionOnly: true,\r\n filePath: r.filePath,\r\n message:\r\n \"Session saved (annotations preserved). This file was uploaded — no disk path to save to.\",\r\n });\r\n }\r\n\r\n const adapter = getAdapter(format);\r\n\r\n if (readOnly || !adapter.canSave) {\r\n await saveSession(r.filePath, format, r.doc);\r\n return mcpSuccess({\r\n saved: true,\r\n sessionOnly: true,\r\n filePath: r.filePath,\r\n message:\r\n \"Session saved (annotations preserved). Source file unchanged — document is read-only.\",\r\n });\r\n }\r\n\r\n const output = adapter.save(r.doc)!;\r\n await atomicWrite(r.filePath, output);\r\n await saveSession(r.filePath, format, r.doc);\r\n\r\n // Mark document clean: bump savedAtVersion so client resets dirty flag\r\n const meta = r.doc.getMap(Y_MAP_DOCUMENT_META);\r\n r.doc.transact(() => meta.set(Y_MAP_SAVED_AT_VERSION, Date.now()), MCP_ORIGIN);\r\n\r\n return mcpSuccess({ saved: true, filePath: r.filePath });\r\n } catch (err: unknown) {\r\n const errCode = (err as NodeJS.ErrnoException).code;\r\n const msg = getErrorMessage(err);\r\n pushNotification({\r\n id: generateNotificationId(),\r\n type: \"save-error\",\r\n severity: \"error\",\r\n message: `Save failed: ${msg}`,\r\n toolName: \"tandem_save\",\r\n errorCode: errCode ?? \"UNKNOWN\",\r\n documentId: r.docId,\r\n dedupKey: `save:${r.docId}`,\r\n timestamp: Date.now(),\r\n });\r\n if (errCode === \"EACCES\" || errCode === \"EPERM\") {\r\n return mcpError(\"FILE_LOCKED\", msg);\r\n }\r\n return mcpError(\"FORMAT_ERROR\", `Save failed: ${msg}`);\r\n }\r\n }),\r\n );\r\n\r\n server.tool(\r\n \"tandem_status\",\r\n \"Check editor status: running, open documents, active document\",\r\n {},\r\n withErrorBoundary(\"tandem_status\", async () => {\r\n const activeId = getActiveDocId();\r\n const active = activeId ? openDocs.get(activeId) : null;\r\n\r\n // Read the user's interruption mode from the active document's Y.Map\r\n let interruptionMode: string = INTERRUPTION_MODE_DEFAULT;\r\n if (activeId) {\r\n const doc = getOrCreateDocument(activeId);\r\n const awareness = doc.getMap(Y_MAP_USER_AWARENESS);\r\n interruptionMode =\r\n (awareness.get(\"interruptionMode\") as string) ?? INTERRUPTION_MODE_DEFAULT;\r\n }\r\n\r\n return mcpSuccess({\r\n running: true,\r\n interruptionMode,\r\n activeDocument: active\r\n ? { documentId: active.id, filePath: active.filePath, format: active.format }\r\n : null,\r\n openDocuments: Array.from(openDocs.values()).map((d) => ({\r\n documentId: d.id,\r\n filePath: d.filePath,\r\n format: d.format,\r\n readOnly: d.readOnly,\r\n })),\r\n documentCount: docCount(),\r\n });\r\n }),\r\n );\r\n\r\n server.tool(\r\n \"tandem_close\",\r\n \"Close a document. Closes the active document if no documentId specified.\",\r\n {\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Document ID to close (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\"tandem_close\", async ({ documentId }) => {\r\n const id = documentId ?? getActiveDocId();\r\n if (!id) return mcpError(\"NO_DOCUMENT\", \"No document to close.\");\r\n\r\n const result = await closeDocumentById(id);\r\n if (!result.success) return mcpError(\"NO_DOCUMENT\", result.error);\r\n\r\n return mcpSuccess({\r\n closed: true,\r\n was: result.closedPath,\r\n activeDocumentId: result.activeDocumentId,\r\n });\r\n }),\r\n );\r\n\r\n server.tool(\r\n \"tandem_listDocuments\",\r\n \"List all open documents with their IDs, file paths, and formats.\",\r\n {},\r\n withErrorBoundary(\"tandem_listDocuments\", async () => {\r\n return mcpSuccess({\r\n documents: Array.from(openDocs.values()).map((d) => ({\r\n ...toDocListEntry(d),\r\n isActive: d.id === getActiveDocId(),\r\n })),\r\n activeDocumentId: getActiveDocId(),\r\n count: docCount(),\r\n });\r\n }),\r\n );\r\n\r\n server.tool(\r\n \"tandem_switchDocument\",\r\n \"Switch the active document. Tools will operate on this document by default.\",\r\n {\r\n documentId: z.string().describe(\"Document ID to switch to\"),\r\n },\r\n withErrorBoundary(\"tandem_switchDocument\", async ({ documentId }) => {\r\n if (!hasDoc(documentId)) {\r\n return mcpError(\"NO_DOCUMENT\", `Document ${documentId} is not open.`);\r\n }\r\n setActiveDocId(documentId);\r\n broadcastOpenDocs();\r\n return mcpSuccess({\r\n activeDocumentId: documentId,\r\n ...toDocListEntry(openDocs.get(documentId)!),\r\n });\r\n }),\r\n );\r\n\r\n server.tool(\r\n \"tandem_convertToMarkdown\",\r\n \"Convert a .docx document to an editable Markdown file. Writes the .md file to disk and opens it as a new tab.\",\r\n {\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Document ID of the .docx to convert (defaults to active document)\"),\r\n outputPath: z\r\n .string()\r\n .optional()\r\n .describe(\"Custom output path for the .md file (defaults to same directory as the .docx)\"),\r\n },\r\n withErrorBoundary(\"tandem_convertToMarkdown\", async ({ documentId, outputPath }) => {\r\n try {\r\n const result = await convertToMarkdown(documentId, outputPath);\r\n return mcpSuccess({\r\n converted: true,\r\n outputPath: result.outputPath,\r\n documentId: result.documentId,\r\n fileName: result.fileName,\r\n message: `Converted to Markdown: ${result.fileName}`,\r\n });\r\n } catch (err: unknown) {\r\n const e = err as NodeJS.ErrnoException;\r\n if (e.code === \"FILE_NOT_FOUND\") return noDocumentError();\r\n if (\r\n e.code === \"UNSUPPORTED_FORMAT\" ||\r\n e.code === \"INVALID_PATH\" ||\r\n e.code === \"EMPTY_CONVERSION\" ||\r\n e.code === \"OPEN_FAILED\"\r\n ) {\r\n return mcpError(\"FORMAT_ERROR\", e.message);\r\n }\r\n throw err; // Let withErrorBoundary handle unexpected errors\r\n }\r\n }),\r\n );\r\n}\r\n","/**\r\n * Shared MCP tool response helpers.\r\n * Eliminates the 3-line wrapper pattern repeated ~40 times across tool files.\r\n */\r\n\r\ntype McpToolResult = {\r\n content: Array<{ type: \"text\"; text: string }>;\r\n};\r\n\r\n/** Wrap a successful response in the MCP content envelope */\r\nexport function mcpSuccess<T>(data: T): McpToolResult {\r\n return {\r\n content: [{ type: \"text\" as const, text: JSON.stringify({ error: false, data }) }],\r\n };\r\n}\r\n\r\n/** Wrap an error response in the MCP content envelope */\r\nexport function mcpError(\r\n code: string,\r\n message: string,\r\n details?: Record<string, unknown>,\r\n): McpToolResult {\r\n return {\r\n content: [\r\n {\r\n type: \"text\" as const,\r\n text: JSON.stringify({ error: true, code, message, ...(details && { details }) }),\r\n },\r\n ],\r\n };\r\n}\r\n\r\n/** Standard NO_DOCUMENT error — returned when a tool requires an open document */\r\nexport function noDocumentError(): McpToolResult {\r\n return mcpError(\"NO_DOCUMENT\", \"No document is open. Call tandem_open first.\");\r\n}\r\n\r\n/** Extract a human-readable message from an unknown error */\r\nexport function getErrorMessage(err: unknown): string {\r\n return err instanceof Error ? err.message : String(err);\r\n}\r\n\r\n/** Wrap an MCP tool handler with a try/catch that returns a structured error.\r\n * Catches unexpected throws so Claude gets a useful error message instead of\r\n * a generic SDK-level JSON-RPC error. */\r\nexport function withErrorBoundary<TArgs extends Record<string, unknown>>(\r\n toolName: string,\r\n handler: (args: TArgs) => Promise<McpToolResult>,\r\n): (args: TArgs) => Promise<McpToolResult> {\r\n return async (args: TArgs) => {\r\n try {\r\n return await handler(args);\r\n } catch (err) {\r\n console.error(`[Tandem] Tool ${toolName} threw:`, err);\r\n return mcpError(\"INTERNAL_ERROR\", `${toolName} failed: ${getErrorMessage(err)}`);\r\n }\r\n };\r\n}\r\n\r\n/** Escape a string for use as a literal in a RegExp */\r\nexport function escapeRegex(str: string): string {\r\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\r\n}\r\n","/**\r\n * Ephemeral notification system for pushing server-side events to the browser.\r\n *\r\n * Uses an in-memory ring buffer + subscriber pattern (mirrors events/queue.ts).\r\n * NOT persisted via CRDT — notifications are transient and should not bleed\r\n * across sessions or survive server restarts.\r\n */\r\n\r\nimport { NOTIFICATION_BUFFER_SIZE } from \"../shared/constants.js\";\r\nimport type { TandemNotification } from \"../shared/types.js\";\r\n\r\ntype NotificationCallback = (notification: TandemNotification) => void;\r\n\r\nconst buffer: TandemNotification[] = [];\r\nconst subscribers = new Set<NotificationCallback>();\r\n\r\n/** Push a notification to the buffer and notify all subscribers. */\r\nexport function pushNotification(notification: TandemNotification): void {\r\n buffer.push(notification);\r\n\r\n // Evict oldest when buffer exceeds max size\r\n while (buffer.length > NOTIFICATION_BUFFER_SIZE) {\r\n buffer.shift();\r\n }\r\n\r\n for (const cb of subscribers) {\r\n try {\r\n cb(notification);\r\n } catch (err) {\r\n console.error(\"[Notifications] Subscriber threw during dispatch:\", err);\r\n }\r\n }\r\n}\r\n\r\n/** Subscribe to new notifications. Returns an unsubscribe function. */\r\nexport function subscribe(cb: NotificationCallback): () => void {\r\n subscribers.add(cb);\r\n return () => {\r\n subscribers.delete(cb);\r\n };\r\n}\r\n\r\n/** Get current buffer contents (for testing or replay). */\r\nexport function getBuffer(): readonly TandemNotification[] {\r\n return buffer;\r\n}\r\n\r\n/** Reset all state. For tests only. */\r\nexport function resetForTesting(): void {\r\n buffer.length = 0;\r\n subscribers.clear();\r\n}\r\n","import fs from \"fs/promises\";\r\nimport path from \"path\";\r\nimport { getOrCreateDocument } from \"../yjs/provider.js\";\r\nimport { extractMarkdown } from \"./document-model.js\";\r\nimport { atomicWrite } from \"../file-io/index.js\";\r\nimport { openFileByPath } from \"./file-opener.js\";\r\nimport { getCurrentDoc } from \"./document-service.js\";\r\n\r\nexport interface ConvertResult {\r\n outputPath: string;\r\n documentId: string;\r\n fileName: string;\r\n}\r\n\r\n/**\r\n * Find an available output path, appending `-1`, `-2`, etc. if the base already exists.\r\n */\r\nasync function findAvailablePath(basePath: string): Promise<string> {\r\n const dir = path.dirname(basePath);\r\n const ext = path.extname(basePath);\r\n const name = path.basename(basePath, ext);\r\n\r\n const MAX_ATTEMPTS = 1000;\r\n let candidate = basePath;\r\n let counter = 0;\r\n\r\n while (counter <= MAX_ATTEMPTS) {\r\n try {\r\n await fs.access(candidate);\r\n // File exists, try next\r\n counter++;\r\n candidate = path.join(dir, `${name}-${counter}${ext}`);\r\n } catch (err: unknown) {\r\n const code = (err as NodeJS.ErrnoException).code;\r\n if (code === \"ENOENT\") return candidate;\r\n throw err; // Permission errors should propagate\r\n }\r\n }\r\n throw Object.assign(new Error(\"Could not find an available filename after 1000 attempts.\"), {\r\n code: \"CONFLICT\",\r\n });\r\n}\r\n\r\n/**\r\n * Convert an open .docx document to Markdown, write it to disk, and open it as a new tab.\r\n * Shared by both the HTTP `/api/convert` endpoint and the `tandem_convertToMarkdown` MCP tool.\r\n */\r\nexport async function convertToMarkdown(\r\n documentId?: string,\r\n outputPath?: string,\r\n): Promise<ConvertResult> {\r\n const docState = getCurrentDoc(documentId);\r\n if (!docState) {\r\n throw Object.assign(new Error(\"Document not found.\"), { code: \"FILE_NOT_FOUND\" });\r\n }\r\n if (docState.format !== \"docx\") {\r\n throw Object.assign(new Error(\"Only .docx documents can be converted to Markdown.\"), {\r\n code: \"UNSUPPORTED_FORMAT\",\r\n });\r\n }\r\n\r\n // Uploaded files don't have a real disk path\r\n if (docState.source === \"upload\") {\r\n throw Object.assign(\r\n new Error(\r\n \"Uploaded .docx files cannot be converted — no disk location to write the .md file.\",\r\n ),\r\n { code: \"INVALID_PATH\" },\r\n );\r\n }\r\n\r\n const doc = getOrCreateDocument(docState.id);\r\n const markdown = extractMarkdown(doc);\r\n\r\n // Guard against empty conversion (corrupt .docx or unpopulated Y.Doc)\r\n if (!markdown.trim()) {\r\n throw Object.assign(\r\n new Error(\"Conversion produced empty output — the .docx may not contain extractable text.\"),\r\n { code: \"EMPTY_CONVERSION\" },\r\n );\r\n }\r\n\r\n // Determine output path\r\n const sourceDir = path.dirname(docState.filePath);\r\n let resolvedOutput: string;\r\n if (outputPath) {\r\n resolvedOutput = path.resolve(outputPath);\r\n // Reject UNC paths (Windows NTLM security)\r\n if (resolvedOutput.startsWith(\"\\\\\\\\\") || resolvedOutput.startsWith(\"//\")) {\r\n throw Object.assign(new Error(\"UNC paths are not supported for security reasons.\"), {\r\n code: \"INVALID_PATH\",\r\n });\r\n }\r\n // If they gave a directory, append the filename\r\n try {\r\n const stat = await fs.stat(resolvedOutput);\r\n if (stat.isDirectory()) {\r\n const baseName = path.basename(docState.filePath, path.extname(docState.filePath));\r\n resolvedOutput = path.join(resolvedOutput, `${baseName}.md`);\r\n }\r\n } catch (err: unknown) {\r\n const code = (err as NodeJS.ErrnoException).code;\r\n if (code !== \"ENOENT\") throw err; // Only swallow \"doesn't exist\"\r\n }\r\n } else {\r\n const baseName = path.basename(docState.filePath, path.extname(docState.filePath));\r\n resolvedOutput = path.join(sourceDir, `${baseName}.md`);\r\n }\r\n\r\n // Avoid overwriting existing files\r\n resolvedOutput = await findAvailablePath(resolvedOutput);\r\n\r\n // Write the markdown file\r\n await atomicWrite(resolvedOutput, markdown);\r\n\r\n // Open the new file in Tandem — include outputPath in error if this fails\r\n try {\r\n const openResult = await openFileByPath(resolvedOutput);\r\n return {\r\n outputPath: resolvedOutput,\r\n documentId: openResult.documentId,\r\n fileName: openResult.fileName,\r\n };\r\n } catch (err) {\r\n throw Object.assign(\r\n new Error(\r\n `Markdown written to ${resolvedOutput} but failed to open: ${(err as Error).message}`,\r\n ),\r\n { code: \"OPEN_FAILED\" },\r\n );\r\n }\r\n}\r\n","import type { Express, Request, Response, NextFunction } from \"express\";\r\n\r\nimport { CHANNEL_SSE_KEEPALIVE_MS } from \"../../shared/constants.js\";\r\nimport type { TandemNotification } from \"../../shared/types.js\";\r\nimport { detectFormat } from \"./document-model.js\";\r\nimport { openFileByPath, openFileFromContent } from \"./file-opener.js\";\r\nimport { closeDocumentById } from \"./document-service.js\";\r\nimport { subscribe as subscribeNotifications } from \"../notifications.js\";\r\nimport { convertToMarkdown } from \"./convert.js\";\r\n\r\n/** Express middleware/handler function type (Express 5 compatible). */\r\nexport type Handler = (req: Request, res: Response, next: NextFunction) => void;\r\n\r\n/** Check if a Host header value is allowed (localhost only). Exported for testing. */\r\nexport function isHostAllowed(host: string | undefined): boolean {\r\n const reqHost = (host ?? \"\").split(\":\")[0];\r\n return reqHost === \"localhost\" || reqHost === \"127.0.0.1\";\r\n}\r\n\r\n/** Check if an Origin header is a localhost URL. Exported for testing. */\r\nexport const LOCALHOST_ORIGIN_RE = /^https?:\\/\\/(localhost|127\\.0\\.0\\.1)(:\\d+)?$/;\r\nexport function isLocalhostOrigin(origin: string | undefined): boolean {\r\n return LOCALHOST_ORIGIN_RE.test(origin ?? \"\");\r\n}\r\n\r\n/** Map error code to HTTP status. Exported for testing. */\r\nexport function errorCodeToHttpStatus(code: string | undefined): number {\r\n switch (code) {\r\n case \"ENOENT\":\r\n case \"FILE_NOT_FOUND\":\r\n return 404;\r\n case \"INVALID_PATH\":\r\n case \"UNSUPPORTED_FORMAT\":\r\n return 400;\r\n case \"FILE_TOO_LARGE\":\r\n return 413;\r\n case \"EBUSY\":\r\n case \"EPERM\":\r\n return 423;\r\n case \"EACCES\":\r\n return 403;\r\n default:\r\n return 500;\r\n }\r\n}\r\n\r\n/** CORS + DNS rebinding protection middleware for /api/* routes */\r\nexport function apiMiddleware(req: Request, res: Response, next: NextFunction): void {\r\n if (!isHostAllowed(req.headers.host as string | undefined)) {\r\n res.status(403).json({ error: \"FORBIDDEN\", message: \"Host not allowed.\" });\r\n return;\r\n }\r\n const origin = req.headers.origin as string | undefined;\r\n res.header(\"Access-Control-Allow-Origin\", isLocalhostOrigin(origin) ? origin! : \"null\");\r\n res.header(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\r\n res.header(\"Access-Control-Allow-Headers\", \"Content-Type\");\r\n if (req.method === \"OPTIONS\") {\r\n res.sendStatus(204);\r\n return;\r\n }\r\n next();\r\n}\r\n\r\n/** Map a Node/custom error code to a JSON-body error label. */\r\nfunction errorCodeToLabel(code: string): string {\r\n switch (code) {\r\n case \"ENOENT\":\r\n case \"FILE_NOT_FOUND\":\r\n return \"FILE_NOT_FOUND\";\r\n case \"INVALID_PATH\":\r\n return \"INVALID_PATH\";\r\n case \"UNSUPPORTED_FORMAT\":\r\n return \"UNSUPPORTED_FORMAT\";\r\n case \"FILE_TOO_LARGE\":\r\n return \"FILE_TOO_LARGE\";\r\n case \"EBUSY\":\r\n case \"EPERM\":\r\n return \"FILE_LOCKED\";\r\n case \"EACCES\":\r\n return \"PERMISSION_DENIED\";\r\n default:\r\n return \"INTERNAL\";\r\n }\r\n}\r\n\r\n/** Map error codes from file-opener to HTTP responses */\r\nfunction sendApiError(res: Response, err: unknown): void {\r\n const e = err as NodeJS.ErrnoException;\r\n const code = e.code ?? \"\";\r\n const status = errorCodeToHttpStatus(code);\r\n const label = errorCodeToLabel(code);\r\n const msg =\r\n label === \"FILE_LOCKED\" ? \"File is locked by another program.\" : (e.message ?? String(err));\r\n if (status === 500) console.error(\"[Tandem] Unhandled API error:\", err);\r\n res.status(status).json({ error: label, message: msg });\r\n}\r\n\r\n/** SSE endpoint for streaming notifications to the browser. */\r\nfunction notifyStreamHandler(req: Request, res: Response): void {\r\n res.writeHead(200, {\r\n \"Content-Type\": \"text/event-stream\",\r\n \"Cache-Control\": \"no-cache\",\r\n Connection: \"keep-alive\",\r\n });\r\n\r\n res.write(\": connected\\n\\n\");\r\n\r\n function cleanup(): void {\r\n clearInterval(keepalive);\r\n unsubscribe();\r\n }\r\n\r\n const unsubscribe = subscribeNotifications((notification: TandemNotification) => {\r\n try {\r\n res.write(`data: ${JSON.stringify(notification)}\\n\\n`);\r\n } catch (err) {\r\n console.error(\r\n \"[NotifyStream] Write failed, cleaning up:\",\r\n err instanceof Error ? err.message : err,\r\n );\r\n cleanup();\r\n }\r\n });\r\n\r\n const keepalive = setInterval(() => {\r\n try {\r\n if (!res.writableEnded) res.write(\": keepalive\\n\\n\");\r\n } catch {\r\n cleanup();\r\n }\r\n }, CHANNEL_SSE_KEEPALIVE_MS);\r\n\r\n req.on(\"close\", () => {\r\n cleanup();\r\n console.error(\"[NotifyStream] Client disconnected from /api/notify-stream\");\r\n });\r\n\r\n console.error(\"[NotifyStream] Client connected to /api/notify-stream\");\r\n}\r\n\r\n/** Register /api/open, /api/upload, and /api/notify-stream routes on the Express app. */\r\nexport function registerApiRoutes(app: Express, largeBody: Handler): void {\r\n // SSE notification stream for browser toasts\r\n app.get(\"/api/notify-stream\", apiMiddleware, notifyStreamHandler);\r\n app.options(\"/api/open\", apiMiddleware);\r\n app.post(\"/api/open\", apiMiddleware, largeBody, async (req: Request, res: Response) => {\r\n const { filePath, force } = (req.body ?? {}) as Record<string, unknown>;\r\n if (!filePath || typeof filePath !== \"string\") {\r\n res.status(400).json({ error: \"BAD_REQUEST\", message: \"filePath is required\" });\r\n return;\r\n }\r\n try {\r\n const result = await openFileByPath(filePath, { force: force === true });\r\n res.json({ data: result });\r\n } catch (err: unknown) {\r\n sendApiError(res, err);\r\n }\r\n });\r\n\r\n app.options(\"/api/close\", apiMiddleware);\r\n app.post(\"/api/close\", apiMiddleware, largeBody, async (req: Request, res: Response) => {\r\n const { documentId } = (req.body ?? {}) as Record<string, unknown>;\r\n if (!documentId || typeof documentId !== \"string\") {\r\n res.status(400).json({ error: \"BAD_REQUEST\", message: \"documentId is required\" });\r\n return;\r\n }\r\n try {\r\n const result = await closeDocumentById(documentId);\r\n if (!result.success) {\r\n res.status(404).json({ error: \"NOT_FOUND\", message: result.error });\r\n return;\r\n }\r\n res.json({\r\n data: { closedPath: result.closedPath, activeDocumentId: result.activeDocumentId },\r\n });\r\n } catch (err: unknown) {\r\n sendApiError(res, err);\r\n }\r\n });\r\n\r\n app.options(\"/api/upload\", apiMiddleware);\r\n app.post(\"/api/upload\", apiMiddleware, largeBody, async (req: Request, res: Response) => {\r\n const { fileName, content } = (req.body ?? {}) as Record<string, unknown>;\r\n if (!fileName || typeof fileName !== \"string\") {\r\n res.status(400).json({ error: \"BAD_REQUEST\", message: \"fileName is required\" });\r\n return;\r\n }\r\n if (content === undefined || content === null) {\r\n res.status(400).json({ error: \"BAD_REQUEST\", message: \"content is required\" });\r\n return;\r\n }\r\n if (typeof content !== \"string\") {\r\n res.status(400).json({ error: \"BAD_REQUEST\", message: \"content must be a base64 string\" });\r\n return;\r\n }\r\n try {\r\n const format = detectFormat(fileName);\r\n const decoded = format === \"docx\" ? Buffer.from(content, \"base64\") : String(content);\r\n const result = await openFileFromContent(fileName, decoded);\r\n res.json({ data: result });\r\n } catch (err: unknown) {\r\n sendApiError(res, err);\r\n }\r\n });\r\n\r\n app.options(\"/api/convert\", apiMiddleware);\r\n app.post(\"/api/convert\", apiMiddleware, largeBody, async (req: Request, res: Response) => {\r\n const { documentId, outputPath } = (req.body ?? {}) as Record<string, unknown>;\r\n if (documentId !== undefined && typeof documentId !== \"string\") {\r\n res.status(400).json({ error: \"BAD_REQUEST\", message: \"documentId must be a string\" });\r\n return;\r\n }\r\n if (outputPath !== undefined && typeof outputPath !== \"string\") {\r\n res.status(400).json({ error: \"BAD_REQUEST\", message: \"outputPath must be a string\" });\r\n return;\r\n }\r\n\r\n try {\r\n const result = await convertToMarkdown(\r\n documentId as string | undefined,\r\n outputPath as string | undefined,\r\n );\r\n res.json({ data: result });\r\n } catch (err: unknown) {\r\n sendApiError(res, err);\r\n }\r\n });\r\n}\r\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { z } from \"zod\";\r\nimport { getOrCreateDocument } from \"../yjs/provider.js\";\r\nimport { getCurrentDoc, extractText } from \"./document.js\";\r\nimport { collectAnnotations, refreshRange } from \"./annotations.js\";\r\nimport { mcpSuccess, noDocumentError, withErrorBoundary } from \"./response.js\";\r\nimport type { Annotation, ChatMessage, FlatOffset } from \"../../shared/types.js\";\r\nimport { generateMessageId } from \"../../shared/utils.js\";\r\nimport {\r\n CTRL_ROOM,\r\n INTERRUPTION_MODE_DEFAULT,\r\n Y_MAP_ANNOTATIONS,\r\n Y_MAP_CHAT,\r\n Y_MAP_USER_AWARENESS,\r\n} from \"../../shared/constants.js\";\r\nimport { MCP_ORIGIN } from \"../events/queue.js\";\r\n\r\n// Track which annotation IDs have been surfaced to Claude via checkInbox\r\nconst surfacedIds = new Set<string>();\r\n\r\n/** Reset surfaced IDs (exported for testing) */\r\nexport function resetInbox(): void {\r\n surfacedIds.clear();\r\n}\r\n\r\nexport function registerAwarenessTools(server: McpServer): void {\r\n server.tool(\r\n \"tandem_getSelections\",\r\n \"Get text currently selected by the user in the editor\",\r\n {\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\"tandem_getSelections\", async ({ documentId }) => {\r\n const current = getCurrentDoc(documentId);\r\n if (!current) return noDocumentError();\r\n\r\n const doc = getOrCreateDocument(current.docName);\r\n const userAwareness = doc.getMap(Y_MAP_USER_AWARENESS);\r\n const selection = userAwareness.get(\"selection\") as\r\n | { from: FlatOffset; to: FlatOffset; timestamp: number }\r\n | undefined;\r\n\r\n if (!selection || selection.from === selection.to) {\r\n return mcpSuccess({ selections: [], message: \"No text selected\" });\r\n }\r\n\r\n return mcpSuccess({\r\n selections: [{ from: selection.from, to: selection.to }],\r\n timestamp: selection.timestamp,\r\n });\r\n }),\r\n );\r\n\r\n server.tool(\r\n \"tandem_getActivity\",\r\n \"Check if the user is actively editing and where their cursor is\",\r\n {\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\"tandem_getActivity\", async ({ documentId }) => {\r\n const current = getCurrentDoc(documentId);\r\n if (!current) return noDocumentError();\r\n\r\n const doc = getOrCreateDocument(current.docName);\r\n const userAwareness = doc.getMap(Y_MAP_USER_AWARENESS);\r\n const activity = userAwareness.get(\"activity\") as\r\n | {\r\n isTyping: boolean;\r\n cursor: number;\r\n lastEdit: number;\r\n }\r\n | undefined;\r\n\r\n if (!activity) {\r\n return mcpSuccess({\r\n active: false,\r\n cursor: null,\r\n lastEdit: null,\r\n message: \"No activity detected\",\r\n });\r\n }\r\n\r\n // Consider user active if last edit was within 10 seconds\r\n const isActive = activity.isTyping || Date.now() - activity.lastEdit < 10000;\r\n\r\n return mcpSuccess({\r\n active: isActive,\r\n isTyping: activity.isTyping,\r\n cursor: activity.cursor,\r\n lastEdit: activity.lastEdit,\r\n });\r\n }),\r\n );\r\n\r\n server.tool(\r\n \"tandem_checkInbox\",\r\n \"Check for user actions you haven't seen yet — new highlights, comments, questions, flags, and responses to your annotations. Call this after completing any task, between steps, and whenever you pause. Low token cost.\",\r\n {\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\"tandem_checkInbox\", async ({ documentId }) => {\r\n const current = getCurrentDoc(documentId);\r\n if (!current) return noDocumentError();\r\n\r\n const doc = getOrCreateDocument(current.docName);\r\n const annotationsMap = doc.getMap(Y_MAP_ANNOTATIONS);\r\n const allAnnotations = collectAnnotations(annotationsMap);\r\n const fullText = extractText(doc);\r\n\r\n // Bucket 1: new user-created annotations (highlights, comments, questions)\r\n const userActions: Array<Annotation & { textSnippet: string }> = [];\r\n // Bucket 2: user responses to Claude's annotations (accepted/dismissed)\r\n const userResponses: Array<Annotation & { textSnippet: string }> = [];\r\n\r\n // Refresh only unsurfaced annotations; batch Y.Map writes\r\n const unsurfaced: Annotation[] = [];\r\n doc.transact(() => {\r\n for (const raw of allAnnotations) {\r\n if (surfacedIds.has(raw.id)) continue;\r\n unsurfaced.push(refreshRange(raw, doc, annotationsMap));\r\n }\r\n }, MCP_ORIGIN);\r\n\r\n for (const ann of unsurfaced) {\r\n const snippet = safeSlice(fullText, ann.range.from, ann.range.to);\r\n\r\n if (ann.author === \"user\") {\r\n userActions.push({ ...ann, textSnippet: snippet });\r\n surfacedIds.add(ann.id);\r\n } else if (ann.author === \"claude\" && ann.status !== \"pending\") {\r\n userResponses.push({ ...ann, textSnippet: snippet });\r\n surfacedIds.add(ann.id);\r\n }\r\n }\r\n\r\n // Bucket 3: unread chat messages from CTRL_ROOM\r\n const ctrlDoc = getOrCreateDocument(CTRL_ROOM);\r\n const chatMap = ctrlDoc.getMap(Y_MAP_CHAT);\r\n const chatMessages: Array<Omit<ChatMessage, \"read\" | \"author\">> = [];\r\n\r\n chatMap.forEach((value) => {\r\n const msg = value as ChatMessage;\r\n if (msg.author === \"user\" && !msg.read) {\r\n chatMessages.push({\r\n id: msg.id,\r\n text: msg.text,\r\n timestamp: msg.timestamp,\r\n ...(msg.documentId ? { documentId: msg.documentId } : {}),\r\n ...(msg.anchor ? { anchor: msg.anchor } : {}),\r\n ...(msg.replyTo ? { replyTo: msg.replyTo } : {}),\r\n });\r\n // Mark as read\r\n ctrlDoc.transact(() => chatMap.set(msg.id, { ...msg, read: true }), MCP_ORIGIN);\r\n }\r\n });\r\n\r\n // Current user activity\r\n const userAwareness = doc.getMap(Y_MAP_USER_AWARENESS);\r\n const selection = userAwareness.get(\"selection\") as\r\n | { from: FlatOffset; to: FlatOffset; timestamp: number }\r\n | undefined;\r\n const activity = userAwareness.get(\"activity\") as\r\n | {\r\n isTyping: boolean;\r\n cursor: number;\r\n lastEdit: number;\r\n }\r\n | undefined;\r\n\r\n const interruptionMode =\r\n (userAwareness.get(\"interruptionMode\") as string) ?? INTERRUPTION_MODE_DEFAULT;\r\n\r\n const hasSelection = selection && selection.from !== selection.to;\r\n const selectedText = hasSelection\r\n ? safeSlice(fullText, selection!.from, selection!.to)\r\n : null;\r\n\r\n // Build summary\r\n const parts: string[] = [];\r\n if (userActions.length > 0) {\r\n const typeCounts: Record<string, number> = {};\r\n for (const a of userActions) {\r\n typeCounts[a.type] = (typeCounts[a.type] || 0) + 1;\r\n }\r\n const typeList = Object.entries(typeCounts)\r\n .map(([t, n]) => `${n} ${t}${n > 1 ? \"s\" : \"\"}`)\r\n .join(\", \");\r\n parts.push(`${userActions.length} new: ${typeList}`);\r\n }\r\n if (userResponses.length > 0) {\r\n const statusCounts: Record<string, number> = {};\r\n for (const r of userResponses) {\r\n statusCounts[r.status] = (statusCounts[r.status] || 0) + 1;\r\n }\r\n const statusList = Object.entries(statusCounts)\r\n .map(([s, n]) => `${n} ${s}`)\r\n .join(\", \");\r\n parts.push(statusList);\r\n }\r\n if (chatMessages.length > 0) {\r\n parts.push(`${chatMessages.length} new chat message${chatMessages.length > 1 ? \"s\" : \"\"}`);\r\n }\r\n const summary = parts.length > 0 ? parts.join(\". \") + \".\" : \"No new actions.\";\r\n\r\n const hasNew = userActions.length > 0 || userResponses.length > 0 || chatMessages.length > 0;\r\n\r\n return mcpSuccess({\r\n summary,\r\n hasNew,\r\n interruptionMode,\r\n userActions,\r\n userResponses,\r\n chatMessages,\r\n activity: {\r\n isTyping: activity?.isTyping ?? false,\r\n cursor: activity?.cursor ?? null,\r\n lastEdit: activity?.lastEdit ?? null,\r\n selectedText,\r\n },\r\n });\r\n }),\r\n );\r\n server.tool(\r\n \"tandem_reply\",\r\n \"Send a chat message to the user in the Tandem sidebar. Use this to respond to chat messages from tandem_checkInbox.\",\r\n {\r\n text: z.string().describe(\"Your message to the user\"),\r\n replyTo: z.string().optional().describe(\"ID of the user message you are replying to\"),\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Document context for this reply (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\"tandem_reply\", async ({ text, replyTo, documentId }) => {\r\n const ctrlDoc = getOrCreateDocument(CTRL_ROOM);\r\n const chatMap = ctrlDoc.getMap(Y_MAP_CHAT);\r\n\r\n const id = generateMessageId();\r\n const current = getCurrentDoc(documentId);\r\n const docId = documentId ?? current?.id ?? undefined;\r\n\r\n const msg: ChatMessage = {\r\n id,\r\n author: \"claude\",\r\n text,\r\n timestamp: Date.now(),\r\n ...(docId ? { documentId: docId } : {}),\r\n ...(replyTo ? { replyTo } : {}),\r\n read: true,\r\n };\r\n\r\n ctrlDoc.transact(() => chatMap.set(id, msg), MCP_ORIGIN);\r\n\r\n return mcpSuccess({ sent: true, messageId: id });\r\n }),\r\n );\r\n}\r\n\r\n/** Safely slice text and truncate to 100 chars. Exported for testing. */\r\nexport function safeSlice(text: string, from: number, to: number): string {\r\n const start = Math.max(0, Math.min(from, text.length));\r\n const end = Math.max(start, Math.min(to, text.length));\r\n const snippet = text.slice(start, end);\r\n return snippet.length > 100 ? snippet.slice(0, 97) + \"...\" : snippet;\r\n}\r\n\r\n/** Determine if user is active based on activity data. Exported for testing. */\r\nexport function isUserActive(\r\n activity: { isTyping: boolean; lastEdit: number } | undefined,\r\n): boolean {\r\n if (!activity) return false;\r\n return activity.isTyping || Date.now() - activity.lastEdit < 10000;\r\n}\r\n\r\n/**\r\n * Process annotations into inbox buckets. Exported for testing.\r\n * Mutates surfacedIds to track which annotations have been surfaced.\r\n */\r\nexport function processInboxAnnotations(\r\n allAnnotations: Annotation[],\r\n fullText: string,\r\n surfaced: Set<string>,\r\n refreshFn: (ann: Annotation) => Annotation,\r\n): {\r\n userActions: Array<Annotation & { textSnippet: string }>;\r\n userResponses: Array<Annotation & { textSnippet: string }>;\r\n} {\r\n const userActions: Array<Annotation & { textSnippet: string }> = [];\r\n const userResponses: Array<Annotation & { textSnippet: string }> = [];\r\n\r\n const unsurfaced: Annotation[] = [];\r\n for (const raw of allAnnotations) {\r\n if (surfaced.has(raw.id)) continue;\r\n unsurfaced.push(refreshFn(raw));\r\n }\r\n\r\n for (const ann of unsurfaced) {\r\n const snippet = safeSlice(fullText, ann.range.from, ann.range.to);\r\n if (ann.author === \"user\") {\r\n userActions.push({ ...ann, textSnippet: snippet });\r\n surfaced.add(ann.id);\r\n } else if (ann.author === \"claude\" && ann.status !== \"pending\") {\r\n userResponses.push({ ...ann, textSnippet: snippet });\r\n surfaced.add(ann.id);\r\n }\r\n }\r\n\r\n return { userActions, userResponses };\r\n}\r\n","import type { Express, Request, Response } from \"express\";\r\n\r\nimport type { Handler } from \"./api-routes.js\";\r\nimport { CTRL_ROOM, Y_MAP_AWARENESS, Y_MAP_CHAT } from \"../../shared/constants.js\";\r\nimport type { ClaudeAwareness } from \"../../shared/types.js\";\r\nimport { generateMessageId } from \"../../shared/utils.js\";\r\nimport { MCP_ORIGIN } from \"../events/queue.js\";\r\nimport { sseHandler } from \"../events/sse.js\";\r\nimport { getOrCreateDocument } from \"../yjs/provider.js\";\r\n\r\nconst pendingPermissions = new Map<\r\n string,\r\n {\r\n requestId: string;\r\n toolName: string;\r\n description: string;\r\n inputPreview: string;\r\n createdAt: number;\r\n }\r\n>();\r\nconst PERMISSION_TTL_MS = 30_000; // Stale after 30s (terminal answer already won)\r\n\r\n/** Register channel-related routes (/api/events, /api/channel-*, /api/launch-claude) on the Express app. */\r\nexport function registerChannelRoutes(app: Express, apiMiddleware: Handler): void {\r\n // SSE event stream for channel shim\r\n app.get(\"/api/events\", apiMiddleware, sseHandler);\r\n\r\n // Channel awareness: shim posts Claude's status for browser StatusBar\r\n app.options(\"/api/channel-awareness\", apiMiddleware);\r\n app.post(\"/api/channel-awareness\", apiMiddleware, (req: Request, res: Response) => {\r\n const { documentId, status, active, focusParagraph } = (req.body ?? {}) as Record<\r\n string,\r\n unknown\r\n >;\r\n if (typeof status !== \"string\") {\r\n res.status(400).json({ error: \"BAD_REQUEST\", message: \"status is required\" });\r\n return;\r\n }\r\n // Write to the document's Y.Map('awareness') so the browser StatusBar updates\r\n const docId = typeof documentId === \"string\" ? documentId : null;\r\n if (docId) {\r\n const doc = getOrCreateDocument(docId);\r\n const awarenessMap = doc.getMap(Y_MAP_AWARENESS);\r\n const state: ClaudeAwareness = {\r\n status,\r\n timestamp: Date.now(),\r\n active: active === true,\r\n focusParagraph: typeof focusParagraph === \"number\" ? focusParagraph : null,\r\n };\r\n doc.transact(() => awarenessMap.set(\"claude\", state), MCP_ORIGIN);\r\n }\r\n res.json({ ok: true, written: !!docId });\r\n });\r\n\r\n // Channel error: shim reports errors for browser display\r\n app.options(\"/api/channel-error\", apiMiddleware);\r\n app.post(\"/api/channel-error\", apiMiddleware, (req: Request, res: Response) => {\r\n const { error, message } = (req.body ?? {}) as Record<string, unknown>;\r\n console.error(`[Channel] Error: ${error} — ${message}`);\r\n // Could broadcast to browser via Y.Map in the future\r\n res.json({ ok: true });\r\n });\r\n\r\n // Channel reply: shim forwards Claude's chat replies\r\n app.options(\"/api/channel-reply\", apiMiddleware);\r\n app.post(\"/api/channel-reply\", apiMiddleware, (req: Request, res: Response) => {\r\n const { text, documentId, replyTo } = (req.body ?? {}) as Record<string, unknown>;\r\n if (typeof text !== \"string\") {\r\n res.status(400).json({ error: \"BAD_REQUEST\", message: \"text is required\" });\r\n return;\r\n }\r\n const ctrlDoc = getOrCreateDocument(CTRL_ROOM);\r\n const chatMap = ctrlDoc.getMap(Y_MAP_CHAT);\r\n const id = generateMessageId();\r\n const msg = {\r\n id,\r\n author: \"claude\" as const,\r\n text,\r\n timestamp: Date.now(),\r\n ...(typeof documentId === \"string\" ? { documentId } : {}),\r\n ...(typeof replyTo === \"string\" ? { replyTo } : {}),\r\n read: true,\r\n };\r\n ctrlDoc.transact(() => chatMap.set(id, msg), MCP_ORIGIN);\r\n res.json({ sent: true, messageId: id });\r\n });\r\n\r\n // Channel permission relay: shim forwards Claude Code's tool approval prompts\r\n // Pending requests stored for browser polling (SSE push to browser is a follow-up)\r\n app.options(\"/api/channel-permission\", apiMiddleware);\r\n app.post(\"/api/channel-permission\", apiMiddleware, (req: Request, res: Response) => {\r\n const { requestId, toolName, description, inputPreview } = (req.body ?? {}) as Record<\r\n string,\r\n unknown\r\n >;\r\n if (typeof requestId !== \"string\" || typeof toolName !== \"string\") {\r\n res.status(400).json({ error: \"BAD_REQUEST\", message: \"requestId and toolName required\" });\r\n return;\r\n }\r\n pendingPermissions.set(requestId, {\r\n requestId,\r\n toolName,\r\n description: (description as string) ?? \"\",\r\n inputPreview: (inputPreview as string) ?? \"\",\r\n createdAt: Date.now(),\r\n });\r\n console.error(`[Channel] Permission request: ${toolName} — ${description} (id: ${requestId})`);\r\n res.json({ ok: true });\r\n });\r\n\r\n // Browser polls for pending permission requests\r\n app.get(\"/api/channel-permission\", apiMiddleware, (_req: Request, res: Response) => {\r\n // Evict stale requests before returning\r\n const now = Date.now();\r\n for (const [id, perm] of pendingPermissions) {\r\n if (now - perm.createdAt > PERMISSION_TTL_MS) pendingPermissions.delete(id);\r\n }\r\n res.json({ pending: Array.from(pendingPermissions.values()) });\r\n });\r\n\r\n // Browser submits verdict\r\n app.options(\"/api/channel-permission-verdict\", apiMiddleware);\r\n app.post(\"/api/channel-permission-verdict\", apiMiddleware, (req: Request, res: Response) => {\r\n const { requestId, approved } = (req.body ?? {}) as Record<string, unknown>;\r\n if (typeof requestId !== \"string\") {\r\n res.status(400).json({ error: \"BAD_REQUEST\", message: \"requestId is required\" });\r\n return;\r\n }\r\n pendingPermissions.delete(requestId);\r\n // Store verdict for the channel shim to poll (or push via SSE in follow-up)\r\n console.error(`[Channel] Permission verdict: ${requestId} → ${approved ? \"allow\" : \"deny\"}`);\r\n res.json({ ok: true, requestId, behavior: approved ? \"allow\" : \"deny\" });\r\n });\r\n\r\n // Claude Code launcher\r\n app.options(\"/api/launch-claude\", apiMiddleware);\r\n app.post(\"/api/launch-claude\", apiMiddleware, async (_req: Request, res: Response) => {\r\n try {\r\n const { launchClaude } = await import(\"./launcher.js\");\r\n const result = launchClaude();\r\n res.json(result);\r\n } catch (err) {\r\n console.error(\"[Tandem] Failed to launch Claude:\", err);\r\n res.status(500).json({\r\n error: \"LAUNCH_FAILED\",\r\n message: err instanceof Error ? err.message : String(err),\r\n });\r\n }\r\n });\r\n}\r\n","/**\r\n * SSE endpoint handler for streaming TandemEvents to the channel shim.\r\n *\r\n * Mount as `GET /api/events` on the Express app.\r\n */\r\n\r\nimport type { Request, Response } from \"express\";\r\nimport { CHANNEL_SSE_KEEPALIVE_MS } from \"../../shared/constants.js\";\r\nimport { replaySince, subscribe, unsubscribe } from \"./queue.js\";\r\nimport type { TandemEvent } from \"./types.js\";\r\n\r\n/** Express route handler for SSE event stream. */\r\nexport function sseHandler(req: Request, res: Response): void {\r\n // SSE headers\r\n res.writeHead(200, {\r\n \"Content-Type\": \"text/event-stream\",\r\n \"Cache-Control\": \"no-cache\",\r\n Connection: \"keep-alive\",\r\n });\r\n\r\n // Replay buffered events on reconnection\r\n const lastEventId = req.headers[\"last-event-id\"] as string | undefined;\r\n if (lastEventId) {\r\n const missed = replaySince(lastEventId);\r\n for (const event of missed) {\r\n writeEvent(res, event);\r\n }\r\n }\r\n\r\n // Immediate flush so the client knows the connection is alive\r\n res.write(\": connected\\n\\n\");\r\n\r\n // keepalive must be declared before onEvent so the error handler can clear it\r\n // eslint-disable-next-line prefer-const\r\n let keepalive: ReturnType<typeof setInterval>;\r\n const onEvent = (event: TandemEvent) => {\r\n try {\r\n writeEvent(res, event);\r\n } catch (err) {\r\n console.error(\r\n \"[SSE] Write failed, cleaning up subscriber:\",\r\n err instanceof Error ? err.message : err,\r\n );\r\n clearInterval(keepalive);\r\n unsubscribe(onEvent);\r\n }\r\n };\r\n subscribe(onEvent);\r\n\r\n // Keepalive to detect broken connections\r\n keepalive = setInterval(() => {\r\n res.write(\": keepalive\\n\\n\");\r\n }, CHANNEL_SSE_KEEPALIVE_MS);\r\n\r\n // Cleanup on disconnect\r\n req.on(\"close\", () => {\r\n clearInterval(keepalive);\r\n unsubscribe(onEvent);\r\n console.error(\"[SSE] Client disconnected from /api/events\");\r\n });\r\n\r\n console.error(\"[SSE] Client connected to /api/events\");\r\n}\r\n\r\nfunction writeEvent(res: Response, event: TandemEvent): void {\r\n res.write(`id: ${event.id}\\n`);\r\n res.write(`data: ${JSON.stringify(event)}\\n\\n`);\r\n}\r\n","import { Y_MAP_AWARENESS } from \"../../shared/constants.js\";\r\nimport type { FlatOffset } from \"../../shared/positions/types.js\";\r\nimport { toFlatOffset } from \"../../shared/positions/types.js\";\r\nimport { MCP_ORIGIN } from \"../events/queue.js\";\r\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { z } from \"zod\";\r\nimport { getOrCreateDocument } from \"../yjs/provider.js\";\r\nimport { getCurrentDoc, extractText } from \"./document.js\";\r\nimport {\r\n mcpSuccess,\r\n mcpError,\r\n noDocumentError,\r\n escapeRegex,\r\n getErrorMessage,\r\n withErrorBoundary,\r\n} from \"./response.js\";\r\n\r\n/** Get full text from the current document's Y.Doc */\r\nfunction getFullText(docName: string): string {\r\n const doc = getOrCreateDocument(docName);\r\n return extractText(doc);\r\n}\r\n\r\nexport interface SearchMatch {\r\n from: FlatOffset;\r\n to: FlatOffset;\r\n text: string;\r\n}\r\n\r\n/** Search for text in a document. Pure logic extracted for testability. */\r\nexport function searchText(\r\n fullText: string,\r\n query: string,\r\n useRegex?: boolean,\r\n): { matches: SearchMatch[]; error?: string } {\r\n const matches: SearchMatch[] = [];\r\n try {\r\n const pattern = useRegex ? new RegExp(query, \"gi\") : new RegExp(escapeRegex(query), \"gi\");\r\n let match;\r\n while ((match = pattern.exec(fullText)) !== null) {\r\n matches.push({\r\n from: toFlatOffset(match.index),\r\n to: toFlatOffset(match.index + match[0].length),\r\n text: match[0],\r\n });\r\n }\r\n } catch (err) {\r\n return { matches: [], error: `Invalid regex: ${getErrorMessage(err)}` };\r\n }\r\n return { matches };\r\n}\r\n\r\n/** Find the nth occurrence of a pattern. Pure logic extracted for testability. */\r\nexport function findOccurrence(\r\n fullText: string,\r\n pattern: string,\r\n occurrence: number = 1,\r\n): { from: FlatOffset; to: FlatOffset; text: string } | { error: string; totalCount: number } {\r\n const regex = new RegExp(escapeRegex(pattern), \"g\");\r\n let match;\r\n let count = 0;\r\n while ((match = regex.exec(fullText)) !== null) {\r\n count++;\r\n if (count === occurrence) {\r\n return {\r\n from: toFlatOffset(match.index),\r\n to: toFlatOffset(match.index + match[0].length),\r\n text: match[0],\r\n };\r\n }\r\n }\r\n return {\r\n error: `Text \"${pattern}\" not found (occurrence ${occurrence}, found ${count} total)`,\r\n totalCount: count,\r\n };\r\n}\r\n\r\n/** Extract context window around a range. Pure logic extracted for testability. */\r\nexport function extractContext(\r\n fullText: string,\r\n from: FlatOffset,\r\n to: FlatOffset,\r\n windowSize: number = 500,\r\n) {\r\n const contextStart = toFlatOffset(Math.max(0, from - windowSize));\r\n const contextEnd = toFlatOffset(Math.min(fullText.length, to + windowSize));\r\n return {\r\n context: fullText.slice(contextStart, contextEnd),\r\n selection: fullText.slice(from, to),\r\n contextRange: { from: contextStart, to: contextEnd },\r\n selectionRange: { from, to },\r\n };\r\n}\r\n\r\nexport function registerNavigationTools(server: McpServer): void {\r\n server.tool(\r\n \"tandem_search\",\r\n \"Search for text in the document. Returns matching positions.\",\r\n {\r\n query: z.string().describe(\"Search query (supports regex)\"),\r\n regex: z.boolean().optional().describe(\"Treat query as regex\"),\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\"tandem_search\", async ({ query, regex, documentId }) => {\r\n const current = getCurrentDoc(documentId);\r\n if (!current) return noDocumentError();\r\n\r\n const fullText = getFullText(current.docName);\r\n const result = searchText(fullText, query, regex);\r\n if (result.error) return mcpError(\"FORMAT_ERROR\", result.error);\r\n return mcpSuccess({ matches: result.matches, count: result.matches.length });\r\n }),\r\n );\r\n\r\n server.tool(\r\n \"tandem_resolveRange\",\r\n \"Find text and return a valid range. Safer than raw character offsets under concurrent editing.\",\r\n {\r\n pattern: z.string().describe(\"Text to find\"),\r\n occurrence: z.number().optional().describe(\"Which occurrence (1-based, default 1)\"),\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\"tandem_resolveRange\", async ({ pattern, occurrence = 1, documentId }) => {\r\n const current = getCurrentDoc(documentId);\r\n if (!current) return noDocumentError();\r\n\r\n const fullText = getFullText(current.docName);\r\n const result = findOccurrence(fullText, pattern, occurrence);\r\n if (\"error\" in result) return mcpError(\"INVALID_RANGE\", result.error);\r\n return mcpSuccess(result);\r\n }),\r\n );\r\n\r\n server.tool(\r\n \"tandem_setStatus\",\r\n 'Update Claude status text shown to user (e.g., \"Reviewing cost figures...\"). Tip: call tandem_checkInbox after completing work to see if the user has responded.',\r\n {\r\n text: z.string().describe(\"Status text\"),\r\n focusParagraph: z.number().optional().describe(\"Index of paragraph Claude is focusing on\"),\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\"tandem_setStatus\", async ({ text, focusParagraph, documentId }) => {\r\n const current = getCurrentDoc(documentId);\r\n if (!current) {\r\n return mcpSuccess({\r\n status: text,\r\n warning: \"No document open — status not broadcast to editor.\",\r\n });\r\n }\r\n const doc = getOrCreateDocument(current.docName);\r\n const awarenessMap = doc.getMap(Y_MAP_AWARENESS);\r\n doc.transact(\r\n () =>\r\n awarenessMap.set(\"claude\", {\r\n status: text,\r\n timestamp: Date.now(),\r\n active: true,\r\n focusParagraph: focusParagraph ?? null,\r\n }),\r\n MCP_ORIGIN,\r\n );\r\n return mcpSuccess({ status: text });\r\n }),\r\n );\r\n\r\n server.tool(\r\n \"tandem_getContext\",\r\n \"Read content around a range without pulling the full document. Reduces token usage.\",\r\n {\r\n from: z.number().describe(\"Start position\"),\r\n to: z.number().describe(\"End position\"),\r\n windowSize: z\r\n .number()\r\n .optional()\r\n .describe(\"Characters of context before/after (default 500)\"),\r\n documentId: z\r\n .string()\r\n .optional()\r\n .describe(\"Target document ID (defaults to active document)\"),\r\n },\r\n withErrorBoundary(\r\n \"tandem_getContext\",\r\n async ({ from: rawFrom, to: rawTo, windowSize = 500, documentId }) => {\r\n const current = getCurrentDoc(documentId);\r\n if (!current) return noDocumentError();\r\n\r\n const from = toFlatOffset(rawFrom);\r\n const to = toFlatOffset(rawTo);\r\n const fullText = getFullText(current.docName);\r\n return mcpSuccess(extractContext(fullText, from, to, windowSize));\r\n },\r\n ),\r\n );\r\n}\r\n","/**\r\n * Discriminator for known Hocuspocus/ws errors that are safe to swallow.\r\n *\r\n * The Tandem server holds Y.Doc state in memory — crashing loses unsaved edits.\r\n * Hocuspocus and the `ws` library throw on malformed WebSocket frames from stale\r\n * browser reconnects, and Hocuspocus has internal `.catch(e => { throw e })`\r\n * patterns that produce unhandled rejections. These are safe to swallow.\r\n *\r\n * Everything else should crash the process so bugs are visible.\r\n *\r\n * Matched patterns:\r\n * - ws frame errors: RangeError/Error with WS_ERR_* code\r\n * - ws send-after-close: \"WebSocket is not open: readyState …\"\r\n * - lib0 decoder: \"Unexpected end of array\", \"Integer out of Range\" (defensive)\r\n * - Hocuspocus MessageReceiver: \"Received a message with an unknown type: …\" (defensive)\r\n */\r\nexport function isKnownHocuspocusError(err: unknown): boolean {\r\n if (!(err instanceof Error)) return false;\r\n\r\n if (\"code\" in err) {\r\n const code = (err as { code: unknown }).code;\r\n if (typeof code === \"string\" && code.startsWith(\"WS_ERR_\")) {\r\n return true;\r\n }\r\n }\r\n\r\n const msg = err.message;\r\n\r\n if (msg.startsWith(\"WebSocket is not open\")) return true;\r\n if (msg === \"Unexpected end of array\" || msg === \"Integer out of Range\") return true;\r\n if (msg.startsWith(\"Received a message with an unknown type:\")) return true;\r\n\r\n return false;\r\n}\r\n","import * as Y from \"yjs\";\r\n\r\nimport { Y_MAP_ANNOTATIONS, TUTORIAL_ANNOTATION_PREFIX } from \"../../shared/constants.js\";\r\nimport type { Annotation, AnnotationType, HighlightColor } from \"../../shared/types.js\";\r\nimport { MCP_ORIGIN } from \"../events/queue.js\";\r\nimport { anchoredRange } from \"../positions.js\";\r\nimport { toFlatOffset } from \"../../shared/types.js\";\r\nimport { extractText } from \"./document-model.js\";\r\n\r\ninterface TutorialAnnotationDef {\r\n id: string;\r\n type: AnnotationType;\r\n targetText: string;\r\n content: string;\r\n color?: HighlightColor;\r\n}\r\n\r\nconst TUTORIAL_ANNOTATIONS: TutorialAnnotationDef[] = [\r\n {\r\n id: `${TUTORIAL_ANNOTATION_PREFIX}highlight-1`,\r\n type: \"highlight\",\r\n targetText: \"collaborative document editor\",\r\n content: \"This is a highlight \\u2014 it marks text for attention without suggesting changes.\",\r\n color: \"yellow\",\r\n },\r\n {\r\n id: `${TUTORIAL_ANNOTATION_PREFIX}comment-1`,\r\n type: \"comment\",\r\n targetText: \"review your documents\",\r\n content: \"Comments let you or Claude leave notes on specific text passages.\",\r\n },\r\n {\r\n id: `${TUTORIAL_ANNOTATION_PREFIX}suggest-1`,\r\n type: \"suggestion\",\r\n targetText: \"simplify onboarding\",\r\n content: JSON.stringify({\r\n newText: \"streamline onboarding\",\r\n reason: \"More precise verb choice\",\r\n }),\r\n },\r\n];\r\n\r\n/** Idempotent — skips if the guard annotation already exists. */\r\nexport function injectTutorialAnnotations(doc: Y.Doc): void {\r\n const map = doc.getMap(Y_MAP_ANNOTATIONS);\r\n\r\n if (map.has(`${TUTORIAL_ANNOTATION_PREFIX}highlight-1`)) return;\r\n\r\n const fullText = extractText(doc);\r\n if (!fullText) {\r\n console.error(\"[tutorial] Y.Doc has no text content — cannot inject tutorial annotations\");\r\n return;\r\n }\r\n\r\n let injected = 0;\r\n doc.transact(() => {\r\n for (const def of TUTORIAL_ANNOTATIONS) {\r\n const idx = fullText.indexOf(def.targetText);\r\n if (idx === -1) {\r\n console.error(`[tutorial] Target text \"${def.targetText}\" not found — skipping ${def.id}`);\r\n continue;\r\n }\r\n\r\n const result = anchoredRange(\r\n doc,\r\n toFlatOffset(idx),\r\n toFlatOffset(idx + def.targetText.length),\r\n def.targetText,\r\n );\r\n if (!result.ok) {\r\n console.error(\r\n `[tutorial] anchoredRange failed for \"${def.targetText}\" — skipping ${def.id}`,\r\n );\r\n continue;\r\n }\r\n\r\n const annotation: Annotation = {\r\n id: def.id,\r\n author: \"claude\",\r\n type: def.type,\r\n range: result.range,\r\n relRange: result.relRange,\r\n content: def.content,\r\n status: \"pending\",\r\n timestamp: Date.now(),\r\n color: def.color,\r\n textSnapshot: def.targetText,\r\n };\r\n\r\n map.set(def.id, annotation);\r\n injected++;\r\n }\r\n }, MCP_ORIGIN);\r\n\r\n console.error(\r\n `[tutorial] Injected ${injected}/${TUTORIAL_ANNOTATIONS.length} tutorial annotations`,\r\n );\r\n}\r\n"],"mappings":"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","names":["path","Y","Y","path","Y","Y","buildAttrs","ALL_MARKS","Y","Y","updated","init_positions","init_types","parseDocument","buffer","isElement","init_positions","init_types","fs","path","buffer","fs","path","openDocs","doc","path","randomUUID","openFileByPath","init_types","init_types","path","fileURLToPath","randomUUID","z","z","Y","buffer","subscribers","subscribe","fs","path","init_types","init_positions","openDocs","z","init_types","init_positions","init_positions","z","unsubscribe","subscribe","z","z","safeSlice","apiMiddleware","launchClaude","z","z","randomUUID","httpServer","init_positions","init_types","path","fileURLToPath"]}