| 1314 | * Other projects are opened on-demand and cached for performance. |
| 1315 | */ |
| 1316 | export class ToolHandler { |
| 1317 | // Cache of opened CodeGraph instances for cross-project queries |
| 1318 | private projectCache: Map<string, CodeGraph> = new Map(); |
| 1319 | // The directory the server last searched for a default project. Surfaced in |
| 1320 | // the "not initialized" error so users can see why detection missed. |
| 1321 | private defaultProjectHint: string | null = null; |
| 1322 | // Indexed sub-projects the engine's bounded down-scan saw below the search |
| 1323 | // base when no default project resolved (#1607). Listed in the "not |
| 1324 | // initialized" error so the fact is reachable through the protocol, not just |
| 1325 | // the host's stderr capture. Engine-maintained (initial resolve + throttled |
| 1326 | // retry) — tool calls themselves never scan. |
| 1327 | private knownSubprojects: string[] = []; |
| 1328 | private knownSubprojectsBase: string | null = null; |
| 1329 | // Per-start-path cache of the git worktree/index mismatch (issue #155). The |
| 1330 | // mismatch is a fixed property of (where the request came from → which |
| 1331 | // .codegraph/ it resolves to), so the up-to-two `git rev-parse` spawns run |
| 1332 | // once and every later tool call reuses the result — never shelling out to |
| 1333 | // git on the hot path. `undefined` = not computed yet; `null` = no mismatch. |
| 1334 | private worktreeMismatchCache: Map<string, WorktreeIndexMismatch | null> = new Map(); |
| 1335 | // Gate that the MCP engine pokes after `cg.open()` so the first tool call |
| 1336 | // blocks on the post-open filesystem reconcile (catch-up sync). Without |
| 1337 | // this, a tool call that races past `catchUpSync()` serves rows for files |
| 1338 | // that were deleted (or edited) while no MCP server was running — and the |
| 1339 | // per-file staleness banner can't help, because `getPendingFiles()` is |
| 1340 | // populated by the watcher, not by catch-up. The wait is time-boxed |
| 1341 | // (see {@link resolveCatchUpGateTimeoutMs}) so a minutes-long reconcile on a |
| 1342 | // huge repo can't hang the first call (#905); cleared on first await so |
| 1343 | // subsequent calls don't pay any cost. |
| 1344 | private catchUpGate: Promise<void> | null = null; |
| 1345 | // Optional worker-thread pool for off-loop read-tool dispatch (daemon mode). |
| 1346 | // When set + healthy, the heavy read tools run on a worker so the daemon's |
| 1347 | // main loop stays free for the MCP transport under concurrent load. Null in |
| 1348 | // direct/in-process mode (one client, no concurrency to parallelize). |
| 1349 | private queryPool: QueryPool | null = null; |
| 1350 | |
| 1351 | constructor(private cg: CodeGraph | null) {} |
| 1352 | |
| 1353 | /** |
| 1354 | * Engine-only: attach (or detach with null) the worker-thread query pool. The |
| 1355 | * shared daemon sets this once its default project is open; the workers each |
| 1356 | * hold their own WAL read connection and run {@link executeReadTool}. A |
| 1357 | * worker's own ToolHandler never has a pool, so there is no nested off-loading. |
| 1358 | */ |
| 1359 | setQueryPool(pool: QueryPool | null): void { |
| 1360 | this.queryPool = pool; |
| 1361 | } |
| 1362 | |
| 1363 | /** |
| 1364 | * Update the default CodeGraph instance (e.g. after lazy initialization) |
| 1365 | */ |
| 1366 | setDefaultCodeGraph(cg: CodeGraph): void { |
| 1367 | this.cg = cg; |
| 1368 | } |
| 1369 | |
| 1370 | /** |
| 1371 | * Engine-only: register the catch-up sync promise so the next `execute()` |
| 1372 | * call awaits it before serving. The handler swallows rejections (the |
| 1373 | * engine logs them) so a sync failure never propagates as a tool error; |
nothing calls this directly
no outgoing calls
no test coverage detected