fix(doctor): resolve CLIs via searchDirs, single-flight runs, admin-gate the group (#536 review)

- doctor probes each CLI's discovery.searchDirs when which misses and runs --version on the resolved path, so a service with a minimal PATH no longer reports installed CLIs as missing
- GET /api/doctor shares one in-flight run per category
- Diagnostics group hidden from non-admins in multi-user mode (_applyDoctorAdminGate)
- 500 uses INTERNAL_ERROR; a killed child reports 'timed out after 30 s'
- browser test blocks service workers so page.route() is reliable
- wiki: Diagnostics sentence

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JrzFKEdBLwVfu6ev2ZscJS
This commit is contained in:
Devvyn
2026-10-05 09:11:10 +08:00
co-authored by Claude Sonnet 5.5
parent 1b89d7a387
commit db9a39405b
8 changed files with 143 additions and 9 deletions
+17
View File
@@ -7,6 +7,8 @@
* @module config/dependency-registry
*/
import { homedir } from 'node:os';
import { join } from 'node:path';
import { enabledClis } from './cli-registry/registry.js';
import { compileVersionRegex } from './cli-registry/patterns.js';
@@ -30,6 +32,20 @@ export interface PathResolver {
* there and a false "installed" contradicts the run mode's own resolver.
*/
requireVersionMatch?: boolean;
/**
* Absolute directories to probe (`<dir>/<bin>`) when `which` misses. A service (systemd,
* launchd) runs with a minimal PATH, so a CLI installed under `~/.local/bin` or an npm/nvm
* prefix is invisible to `which` while the run mode, which falls back to the registry's
* `discovery.searchDirs`, still finds it. Carries those dirs so the doctor agrees.
*/
searchDirs?: string[];
}
/** Expand a leading `~` (the only form registry `searchDirs` use). */
function expandSearchDir(dir: string): string {
if (dir === '~') return homedir();
if (dir.startsWith('~/')) return join(homedir(), dir.slice(2));
return dir;
}
/** Resolve a Windows-installed app reachable from win32 or WSL. */
@@ -131,6 +147,7 @@ function cliDependencyEntries(): ToolDependency[] {
// (pi, grok, dsh): a bare `which` hit there is not evidence of the right
// program, so a version mismatch means MISSING rather than unknown-version.
requireVersionMatch: version?.requireVersionMatch,
searchDirs: cli.discovery.searchDirs.map(expandSearchDir),
},
},
],