Files
OmniRoute/src/lib/tokenHealthCheckCursor.ts
Will Gordon 41121078ab fix(cursor): addresses Phase 4/4.5 review findings
Restores the legacy stdout/stderr auth-pattern fallback in
checkCursorAgentAvailability() that the plan's Task 2 Step 4
required but the implementation had dropped. Threads an optional
deps parameter through checkCursorConnectionIfNeeded() so its
error branch is reachable in tests, and switches both it and the
manual-refresh route to exhaustive switch statements over the
renewal result. Adds a short-lived host-keyed dedup cache around
tryIdeAuth() so multiple due Cursor connections sharing a host
don't each open the same state.vscdb file in one sweep tick.
Adds opportunistic eviction to the manual-refresh cooldown map,
an outer try/catch to the availability route for defense-in-depth
consistency with the plan's other routes, and corrects a stale
JSDoc claim about the /login route's auth check. Documents the
now-empirically-confirmed agent-cli-state.json schema mismatch
found while validating against a real cursor-agent install.
2026-08-04 11:43:55 -03:00

90 lines
3.1 KiB
TypeScript

/**
* Cursor-specific sweep-glue for the proactive token health check. Cursor has
* no refresh_token by design — its ~24h import-token is renewed via a
* cursor-agent nudge + IDE/agent credential re-scrape (see
* src/lib/cursor/renewal.ts), not a standard OAuth refresh_token exchange.
*
* Sibling to tokenHealthCheckCopilot.ts (same injection-to-avoid-circular-
* import technique — tokenHealthCheck.ts-private helpers passed as params
* rather than imported, and updateProviderConnection imported directly from
* @/lib/localDb) but greenfield: type-checked normally, no @ts-nocheck.
*/
import { updateProviderConnection } from "@/lib/localDb";
import {
renewCursorConnection,
buildCursorRenewedUpdate,
runCursorRenewalExclusive,
} from "@/lib/cursor/renewal";
import type { buildRefreshFailureUpdate } from "@/lib/tokenHealthCheck";
export async function checkCursorConnectionIfNeeded(params: {
conn: any;
now: string;
buildRefreshFailureUpdate: typeof buildRefreshFailureUpdate;
log: (message: string, ...args: any[]) => void;
logWarn: (message: string, ...args: any[]) => void;
logError: (message: string, ...args: any[]) => void;
getConnectionLogLabel: (conn: { name?: string; email?: string; id?: string }) => string;
logPrefix: string;
/** Testability seam forwarded verbatim to renewCursorConnection() — mirrors
* the deps param Task 2 added there, so tests can force a {status:"error"}
* result without a mock.module() shim. */
deps?: Parameters<typeof renewCursorConnection>[1];
}): Promise<void> {
const {
conn,
now,
buildRefreshFailureUpdate,
log,
logWarn,
logError,
getConnectionLogLabel,
logPrefix,
deps,
} = params;
await runCursorRenewalExclusive(conn.id, async () => {
const result = await renewCursorConnection(
{
accessToken: conn.accessToken,
machineId: conn.providerSpecificData?.machineId ?? null,
},
deps
);
switch (result.status) {
case "renewed": {
await updateProviderConnection(conn.id, buildCursorRenewedUpdate(conn, result, now));
log(
`${logPrefix} ✓ Cursor session renewed for ${getConnectionLogLabel(conn)} (source: ${result.source})`
);
return;
}
case "unchanged":
case "error": {
const message =
result.status === "error"
? `Cursor session renewal failed: ${result.error}`
: "Cursor session unchanged — no newer token found on this host.";
await updateProviderConnection(
conn.id,
buildRefreshFailureUpdate(conn, now, {
errorCode: "cursor_session_stale",
lastErrorType: "cursor_session_stale",
lastError: message,
testStatus: "active",
})
);
const logFn = result.status === "error" ? logError : logWarn;
logFn(`${logPrefix} ✗ Cursor session stale for ${getConnectionLogLabel(conn)}: ${message}`);
return;
}
default: {
const _exhaustive: never = result;
throw new Error(`Unhandled CursorRenewalResult status: ${JSON.stringify(_exhaustive)}`);
}
}
});
}