/** * `/doctor`: one pass over the deployment's composition, dependencies, and * compatibility, each verdict carrying the row, line, or path behind it. * * `/diag` answers "why can't I get in?" about one session's channel, lock, and * Host. `/doctor` answers the other half — "is this install composed the way * the plugin needs?" — which is where 0.6.3's regression lived: 0.5.1 dropped * the roster rows to pass a store check, and the only symptom was `/mode` * reporting a missing service months later. Every check therefore reports the * concrete row (with its line) or path, plus the command that acts on it. * * The verdicts are a pure function of collected facts, so they are testable * without a Host, a profile, or a filesystem; collection lives in * `collectDoctor` and every probe is best-effort. Nothing leaves the machine. * @module dsh-ssh-tui/doctor */ import { type HostGeneration, type PatchAnalysis, type PatchRowRef, type RosterRow } from './preset-rows.js'; export type DoctorStatus = 'ok' | 'warn' | 'fail'; export interface DoctorCheck { id: string; status: DoctorStatus; /** One line: the conclusion. */ summary: string; /** The rows, lines, and paths behind the conclusion. */ details: string[]; /** Whether `/doctor --fix` (or `/fix `) can act on this check. */ fixable?: boolean; } /** One route's model list, for the routing self-consistency check. */ export interface DoctorRouting { provider?: string; model?: string; /** * Every route exposed by the live adapter directory or configured under * `llm-pi-ai.providers`. Presence is registration/configuration evidence, * not a network probe. */ routes: readonly string[]; /** Model ids the selected provider's route lists. */ routeModels: readonly string[]; subProvider?: string; subModel?: string; } export interface DoctorFacts { pluginVersion: string; hostVersion: string; nodeVersion: string; profile: string; dshHome: string; patchPath: string; /** Services the running composition actually registered. */ services: { /** `agentPresets` — the roster `/mode` lists. */ roster: boolean; /** `codeRuntime` — what the PTC preset mounts. */ codeRuntime: boolean; /** * `tools.get('ask_user_question')` — the actual registration, not the row * text. Absent on snapshots taken before this field existed, where the * textual check remains the fallback. */ askUserTool?: boolean; /** `tools.get('present')` — the other half of the 0.1.7 agent plane. */ presentTool?: boolean; }; /** The profile patch. `readable` is false when it does not exist yet. */ patch: { readable: boolean; text: string; analysis: PatchAnalysis; }; /** Rows the plugin's own bundle patch mounts. */ bundleRows: readonly PatchRowRef[]; /** The manifest's compatibility declaration, when the package is readable. */ compatibility?: { range?: string; releases: Record; }; /** * Which settings protocol the host speaks; decides which rows a terminal * profile has to mount itself. Absent means the 0.1.5 line, so a snapshot * built before this field existed keeps its verdicts. */ generation?: HostGeneration; /** Distinct installs of `@deepseek-ai/dsh-scope` found from the anchors. */ scopeCopies: readonly string[]; /** Absent when routing could not be read (no settings service). */ routing?: DoctorRouting; } /** * Every check, in report order. Pure: same facts in, same verdicts out. * @param facts - one collected snapshot. * @returns the checks, each with its conclusion and evidence. */ export declare function doctorChecks(facts: DoctorFacts): DoctorCheck[]; /** The whole report as transcript lines. Pure. */ export declare function formatDoctorReport(facts: DoctorFacts, checks: readonly DoctorCheck[]): string[]; /** * Distinct installs of `@deepseek-ai/dsh-scope` reachable from the anchors. * * Anchors are the running CLI entry and the plugin's module base, the same two * the provider catalog walks: a nested npm install puts a second copy under one * of them, and preset composition then fails with "refusing to compose an * unscoped context". * @param anchors - file or directory paths that plausibly sit inside the install. * @returns real paths, deduplicated; empty when none was found. */ export declare function findScopeCopies(anchors: ReadonlyArray): string[]; /** * Gather one snapshot. Every probe is best-effort: a failure is reported by the * check that needed it, never thrown. * @param options - identity, services, and the already-read routing facts. * @returns the facts {@link doctorChecks} turns into verdicts. */ export declare function collectDoctor(options: { profile: string; dshHome: string; hostVersion: string; services: DoctorFacts['services']; anchors: ReadonlyArray; routing?: DoctorRouting; generation?: HostGeneration; }): Promise; /** Rows `/doctor --fix` would mount, given the services the composition registered. */ export declare function rowsToRepair(facts: DoctorFacts): RosterRow[]; /** * What to tell a user whose boot died on the module-copy trap. * * `/doctor` cannot answer this one: the composition fails before the TUI exists, * so the launcher's own failure report is the only place the hint can land. The * signature is `agent-presets` refusing an unscoped context, which happens when * the plugin and the host resolve different `@deepseek-ai/dsh-scope` instances * (a profile that links the plugin into a checkout is the usual shape). * @param detail - the host failure text the launcher is about to print. * @returns the hint, or `undefined` for every other failure. */ export declare function copyTrapHint(detail: string): string | undefined;