Merge pull request #903 from ozymandiashh/feat/899-proberoots-tier1

feat(doctor): probeRoots for 12 more providers (#899 Tier 1)
This commit is contained in:
Resham Joshi 2026-08-10 02:49:47 -07:00 committed by GitHub
commit 9df25fa066
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 198 additions and 10 deletions

View file

@ -8,7 +8,7 @@ import https from 'https'
import { calculateCost } from '../models.js'
import { isSqliteAvailable, isSqliteBusyError, openDatabase } from '../sqlite.js'
import type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js'
import type { ProbeRoot, Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js'
type AntigravityConversationRoot = {
dir: string
@ -1428,6 +1428,13 @@ export function createAntigravityProvider(): Provider {
return rawTool
},
async probeRoots(): Promise<ProbeRoot[]> {
return [
...conversationRoots().map(root => ({ path: root.dir, label: 'conversations' })),
{ path: getAntigravityStatusLineEventsPath(), label: 'statusline' },
]
},
async discoverSessions(): Promise<SessionSource[]> {
return discoverAntigravitySessionSources()
},

View file

@ -6,7 +6,7 @@ import { extractBashCommands } from '../bash-utils.js'
import { readSessionFile } from '../fs-utils.js'
import { calculateCost, getShortModelName } from '../models.js'
import type { ToolCall } from '../types.js'
import type { ParsedProviderCall, Provider, SessionParser, SessionSource } from './types.js'
import type { ProbeRoot, ParsedProviderCall, Provider, SessionParser, SessionSource } from './types.js'
const METADATA_PREFIX_BYTES = 64 * 1024
@ -457,6 +457,10 @@ export function createCodeWhaleProvider(overrideDirs?: string | string[]): Provi
return mapToolName(rawTool)
},
async probeRoots(): Promise<ProbeRoot[]> {
return (configuredDirs ?? defaultSessionDirs()).map(path => ({ path, label: 'sessions' }))
},
async discoverSessions(): Promise<SessionSource[]> {
const seenSessionIds = new Set<string>()
const sources: SessionSource[] = []

View file

@ -4,7 +4,7 @@ import { homedir, platform } from 'os'
import { calculateCost } from '../models.js'
import { isSqliteAvailable, getSqliteLoadError, openDatabase, type SqliteDatabase } from '../sqlite.js'
import type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js'
import type { ProbeRoot, Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js'
/// Crush stores per-project SQLite databases discovered through a JSON registry.
/// We only read both. Schema source: charmbracelet/crush
@ -236,6 +236,10 @@ export function createCrushProvider(): Provider {
return rawTool
},
async probeRoots(): Promise<ProbeRoot[]> {
return [{ path: getRegistryPath(), label: 'registry' }]
},
async discoverSessions(): Promise<SessionSource[]> {
if (!isSqliteAvailable()) return []
const registry = await loadRegistry(getRegistryPath())

View file

@ -13,6 +13,7 @@ import type {
SessionSource,
SessionParser,
ParsedProviderCall,
ProbeRoot,
} from './types.js'
type ConversationSummary = {
@ -513,6 +514,13 @@ export function createCursorAgentProvider(baseDirOverride?: string): Provider {
return rawTool
},
async probeRoots(): Promise<ProbeRoot[]> {
return [
{ path: projectsDir, label: 'projects' },
{ path: dbPath, label: 'db' },
]
},
async discoverSessions(): Promise<SessionSource[]> {
if (!existsSync(projectsDir)) return []

View file

@ -8,7 +8,7 @@ import { readCachedResults, writeCachedResults } from '../cursor-cache.js'
import { isSqliteAvailable, isSqliteBusyError, getSqliteLoadError, openDatabase, blobToText, type SqliteDatabase } from '../sqlite.js'
import { estimateTokensFromChars } from '../token-estimate.js'
import type { DateRange } from '../types.js'
import type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js'
import type { ProbeRoot, Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js'
/** Matches cli-date.ts "all" period cap (6 months). */
const CURSOR_MAX_LOOKBACK_MONTHS = 6
@ -1045,6 +1045,10 @@ export function createCursorProvider(dbPathOverride?: string): Provider {
return rawTool
},
async probeRoots(): Promise<ProbeRoot[]> {
return [{ path: dbPathOverride ?? getCursorDbPath(), label: 'db' }]
},
async discoverSessions(): Promise<SessionSource[]> {
if (!isSqliteAvailable()) return []

View file

@ -11,6 +11,7 @@ import type {
SessionSource,
SessionParser,
ParsedProviderCall,
ProbeRoot,
} from './types.js'
const toolNameMap: Record<string, string> = {
@ -391,6 +392,10 @@ export function createDroidProvider(factoryDir?: string): Provider {
return toolNameMap[rawTool] ?? rawTool
},
async probeRoots(): Promise<ProbeRoot[]> {
return [{ path: sessionsDir, label: 'sessions' }]
},
async discoverSessions(): Promise<SessionSource[]> {
return discoverSessionsInDir(sessionsDir, base)
},

View file

@ -5,7 +5,7 @@ import { calculateCost, getShortModelName } from '../models.js'
import { extractBashCommands } from '../bash-utils.js'
import { isSqliteAvailable, getSqliteLoadError, openDatabase, blobToText, type SqliteDatabase } from '../sqlite.js'
import type { ToolCall } from '../types.js'
import type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js'
import type { ProbeRoot, Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js'
type SessionRow = {
id: string
@ -275,6 +275,10 @@ export function createGooseProvider(): Provider {
return toolNameMap[rawTool] ?? rawTool
},
async probeRoots(): Promise<ProbeRoot[]> {
return [{ path: getDbPath(), label: 'db' }]
},
async discoverSessions(): Promise<SessionSource[]> {
if (!isSqliteAvailable()) return []
const dbPath = getDbPath()

View file

@ -4,7 +4,7 @@ import { homedir } from 'os'
import { calculateCost, getShortModelName } from '../models.js'
import { isSqliteAvailable, getSqliteLoadError, openDatabase, isSqliteBusyError, type SqliteDatabase } from '../sqlite.js'
import type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js'
import type { ProbeRoot, Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js'
import type { ToolCall } from '../types.js'
type HermesSessionRow = {
@ -462,6 +462,10 @@ export function createHermesProvider(hermesHomeOverride?: string): Provider {
return mapToolName(rawTool)
},
async probeRoots(): Promise<ProbeRoot[]> {
return [{ path: hermesHome, label: 'home' }]
},
async discoverSessions(): Promise<SessionSource[]> {
if (!isSqliteAvailable()) return []
const dbs = await findStateDbs(hermesHome)

View file

@ -3,7 +3,7 @@ import { homedir } from 'os'
import { getShortModelName } from '../models.js'
import { discoverClineTasksInBaseDirs, createClineParser } from './vscode-cline-parser.js'
import type { Provider, SessionSource, SessionParser } from './types.js'
import type { ProbeRoot, Provider, SessionSource, SessionParser } from './types.js'
const PROVIDER_NAME = 'ibm-bob'
const DISPLAY_NAME = 'IBM Bob'
@ -45,6 +45,10 @@ export function createIBMBobProvider(overrideDir?: string): Provider {
return rawTool
},
async probeRoots(): Promise<ProbeRoot[]> {
return (overrideDir ? [overrideDir] : getIBMBobGlobalStorageDirs()).map(path => ({ path, label: 'storage' }))
},
async discoverSessions(): Promise<SessionSource[]> {
const dirs = overrideDir ? [overrideDir] : getIBMBobGlobalStorageDirs()
return discoverClineTasksInBaseDirs(dirs, PROVIDER_NAME, DISPLAY_NAME)

View file

@ -4,7 +4,7 @@ import { homedir } from 'os'
import { readSessionLines } from '../fs-utils.js'
import { calculateCost, getShortModelName } from '../models.js'
import type { ParsedProviderCall, Provider, SessionParser, SessionSource } from './types.js'
import type { ProbeRoot, ParsedProviderCall, Provider, SessionParser, SessionSource } from './types.js'
type JsonObject = Record<string, unknown>
@ -418,6 +418,24 @@ export function createLingTaiTuiProvider(options?: string | LingTaiProviderOptio
return rawTool
},
async probeRoots(): Promise<ProbeRoot[]> {
// Deliberately pre-existence-filter: doctor's job is to show where
// discovery looks, so a missing default home reads as "not installed
// here" instead of vanishing (getLingTaiHomes drops non-existent
// candidates, which is right for discovery and wrong for doctor).
const explicit = splitPathList(providerOptions.lingtaiHomeOverride ?? process.env['LINGTAI_HOME'] ?? process.env['LINGTAI_TUI_HOME'])
const roots: ProbeRoot[] = explicit.length
? explicit.map(path => ({ path, label: 'sessions' }))
: [
{ path: getDefaultLingTaiHome(providerOptions), label: 'sessions' },
{ path: getLingTaiGlobalDir(providerOptions), label: 'registry' },
]
for (const home of await getLingTaiHomes(providerOptions)) {
if (!roots.some(root => root.path === home.path)) roots.push({ path: home.path, label: 'sessions' })
}
return roots
},
async discoverSessions(): Promise<SessionSource[]> {
return discoverLedgers(await getLingTaiHomes(providerOptions))
},

View file

@ -5,7 +5,7 @@ import { homedir } from 'os'
import { readSessionFile } from '../fs-utils.js'
import { calculateCost } from '../models.js'
import { extractBashCommands } from '../bash-utils.js'
import type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js'
import type { ProbeRoot, Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js'
const toolNameMap: Record<string, string> = {
read_file: 'Read',
@ -162,6 +162,10 @@ export function createQwenProvider(overrideDir?: string): Provider {
return toolNameMap[rawTool] ?? rawTool
},
async probeRoots(): Promise<ProbeRoot[]> {
return [{ path: projectsDir, label: 'projects' }]
},
async discoverSessions(): Promise<SessionSource[]> {
const sources: SessionSource[] = []

View file

@ -5,7 +5,7 @@ import { extractBashCommands } from '../bash-utils.js'
import { calculateCost, getShortModelName } from '../models.js'
import { blobToText, getSqliteLoadError, isSqliteAvailable, openDatabase, type SqliteDatabase } from '../sqlite.js'
import { estimateTokensFromChars } from '../token-estimate.js'
import type { ParsedProviderCall, Provider, SessionParser, SessionSource } from './types.js'
import type { ProbeRoot, ParsedProviderCall, Provider, SessionParser, SessionSource } from './types.js'
import { safeNumber } from '../parser.js'
const WARP_GROUP_CONTAINER = '2BBY89MBSN.dev.warp'
@ -479,6 +479,10 @@ export function createWarpProvider(dbPathOverride?: string): Provider {
return rawTool === 'run_command' ? 'Bash' : rawTool
},
async probeRoots(): Promise<ProbeRoot[]> {
return getDbCandidates(dbPathOverride).map(path => ({ path, label: 'db' }))
},
async discoverSessions(): Promise<SessionSource[]> {
if (!isSqliteAvailable()) return []