/** * Stale-tools-cache self-heal (#51775). * * The command tree is built from the /tools manifest, cached on disk for * 24h. The server tailors the manifest per user (gatekeeper-gated tools are * omitted for non-qualified accounts), so when a gate flips ON the cached * tree keeps rejecting the newly enabled command — `unknown command * 'get_plan'` plus a multi-KB usage dump — until the TTL expires. At the 5% * SAS skill-line ramp that window silently dropped 28% of plan fetches * fleet-wide over three days. * * Fix shape: before registering commands, check whether the user's argv can * possibly resolve against the CACHED tree (plus the built-in commands * already registered on the program). On a clear miss, the caller bypasses * the TTL and refetches the manifest once, so a server-side enablement is * picked up on the first call instead of up to 24h later. False positives * only cost one extra manifest GET on a genuinely unknown command; false * negatives fall back to today's behavior (Commander's usage error). * * Server-side manifest hiding is deliberate per-tool product semantics * (e.g. get_plan must stay invisible while a user's plans are delivered * in-prompt), so the heal lives entirely client-side. */ import type { ToolSchema } from './types.js'; export interface CommandTokenIndex { /** Every name that can appear as the first command token. */ topLevel: Set; /** Group / path-head name -> valid second tokens. */ subsByTop: Map>; /** * Top-level names known to be leaf commands (they take positionals, so a * second token never means "unknown subcommand"). */ leafTops: Set; } /** * Derive the resolvable command tokens from a cached manifest, mirroring the * dynamic-registration loop in index.ts (phone_call skip, vd opt-in gate, * path/group/flat/legacy forms, aliases). */ export declare function collectToolCommandTokens(tools: ToolSchema[], vdEnabled: boolean): CommandTokenIndex; /** Structural view of a registered Commander command (testable without commander). */ export interface RegisteredCommandLike { name(): string; aliases(): string[]; commands: readonly RegisteredCommandLike[]; } /** * Fold the program's already-registered commands (built-ins like `login`, * `config`, and the seeded group containers like `telephony` / `task`) into * the index so known commands never trigger a refetch. * * Parents keep their level-2 subcommand sets: several static parents are * seeded into `_groupCommands` precisely so DYNAMIC tools attach under them, * which means a manifest refetch CAN extend their subcommand set — the index * unions the static subs (added here) with the cached dynamic subs (added by * `collectToolCommandTokens`), and an unknown second token still self-heals. * * `positionalParents` names the exceptions: parents whose DEFAULT subcommand * takes positionals (`gsk phone-call ` dials), where a second * token can never be proven a miss. Those are exempted from the level-2 * check entirely. */ export declare function addRegisteredCommands(index: CommandTokenIndex, commands: readonly RegisteredCommandLike[], positionalParents?: readonly string[]): void; /** * Return the command token(s) the cached tree cannot resolve, or null when * argv resolves (or is ambiguous — we only refetch on a CLEAR miss). * * `operands` is Commander's option-stripped positional view of argv * (`program.parseOptions(argv).operands`). */ export declare function staleCacheMissingToken(index: CommandTokenIndex, operands: string[]): string | null; //# sourceMappingURL=staleCache.d.ts.map