import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { resolveToken } from "../auth/token.js"; import type { ExtensionState } from "../state.js"; import { TickTickMcpClient } from "./client.js"; import { toTickTickAuthError } from "./errors.js"; import { registerMcpTools } from "./register-tools.js"; type ConnectUi = { setStatus: (key: string, value: string) => void; notify?: (message: string, level: "info" | "warning" | "error") => void; }; let mcpClient: TickTickMcpClient | null = null; let connectPromise: Promise | null = null; export function getMcpClient(): TickTickMcpClient | null { return mcpClient; } export async function closeMcpClient(): Promise { await mcpClient?.close(); mcpClient = null; connectPromise = null; } export async function ensureMcpConnected( pi: ExtensionAPI, state: ExtensionState, ui: ConnectUi, ): Promise { if (mcpClient?.isConnected()) { return mcpClient; } if (connectPromise) { return connectPromise; } connectPromise = connectInternal(pi, state, ui); try { return await connectPromise; } finally { connectPromise = null; } } async function connectInternal( pi: ExtensionAPI, state: ExtensionState, ui: ConnectUi, ): Promise { const token = await resolveToken(); if (!token) { ui.setStatus("ticktick", "not configured"); return null; } state.lastError = null; ui.setStatus("ticktick", "connecting…"); try { await mcpClient?.close(); mcpClient = new TickTickMcpClient(token); await mcpClient.connect(); if (!state.toolsRegistered) { state.toolCount = registerMcpTools(pi, mcpClient, () => ensureMcpConnected(pi, state, ui), ); state.toolsRegistered = true; } state.connected = true; ui.setStatus("ticktick", `connected (${state.toolCount} tools)`); return mcpClient; } catch (error) { const normalized = toTickTickAuthError(error); const message = normalized.message; state.lastError = message; state.connected = false; ui.setStatus("ticktick", "error"); ui.notify?.(`TickTick MCP connect failed: ${message}`, "error"); await mcpClient?.close(); mcpClient = null; return null; } }