export interface SetupFs { existsSync?: (path: string) => boolean; mkdirSync?: (path: string, options?: { recursive?: boolean; }) => unknown; readFileSync?: (path: string, encoding: "utf8") => string; writeFileSync?: (path: string, data: string) => void; } export interface SetupOptions { proxyUrl?: string; /** Legacy CLAUDE_CONFIG_DIR value removed when migrating an older llm-relay Desktop setup. */ configDir?: string; /** * Where `claude_desktop_config.json` is written. Defaults to the real per-platform path. * * The seam exists because there was none: `configDir` only ever redirected the value written * INTO the file, so there was no way to call `setupClaudeDesktop()` without overwriting the * caller's own real Claude Desktop config — and the test suite did exactly that on every run, * reformatting a live application's settings file on the developer's machine. */ targetPath?: string; /** Home directory used to resolve ~/.claude/agents/relay.md. Defaults to homedir(). */ homeDir?: string; /** Direct target path for the relay agent markdown file. Defaults to /.claude/agents/relay.md. */ agentPath?: string; /** * Model alias written to the relay agent's `model:` line. Defaults to * `DEFAULT_RELAY_AGENT_MODEL`. `inherit` is refused by name — see the marker comment below. */ model?: string; /** Injectable filesystem methods for testing. */ fs?: SetupFs; /** * Where human-readable setup output goes. Defaults to `console.log`. Used by the CLI helper; * the desktop writer prints nothing. */ out?: (line: string) => void; } /** * Ownership test for an existing `relay.md` on disk: ANY versioned marker, not just the current * one. `installRelayAgent` uses this (not the exact `RELAY_AGENT_MARKER`) to decide whether a * file is ours, so a file carrying an OLDER marker (e.g. v1) is recognised as our own and * upgraded in place rather than refused as foreign. */ export declare const RELAY_AGENT_MARKER_PREFIX = ""; /** * The alias `installRelayAgent` writes when the caller names none. The wrapper's work is * mechanical — load five tool schemas, call `dispatch` once, poll, return the answer verbatim — * so the cheapest alias that obeys the template is the right default; see the v8 record below. */ export declare const DEFAULT_RELAY_AGENT_MODEL = "haiku"; /** * DEFECT, measured live 2026-09-04: the three mcp__llm-relay__dispatch* tools are DEFERRED in * Claude Code — a subagent must call ToolSearch to load their schemas before it can call them. * v2's `tools:` line omitted ToolSearch, so the installed relay agent could never load those * schemas and made zero tool calls on every probe, answering the task itself instead — its own * rule 1 ("load the schema with ToolSearch") was impossible to follow with the tools it was * granted. v3 adds ToolSearch to the allow list and spells out the exact query. * * Also: Claude Code loads a custom agent definition ONCE when the file first appears and does * NOT re-read edits during a session — measured 2026-09-04, after `setup` rewrote this file to * v2 a running session still reported the v1 marker and the v1 tool list. A changed template * needs a new session (or the file deleted and recreated) before it takes effect. * * THE MODEL LINE — two owner directions, the later one wins (v8, 2026-09-16). * * v4 (2026-09-04) wrote `model: inherit`. The record: on the v1 template `model: haiku` answered a * trivial one-line echo task ITSELF (4s, 0 tool calls, no provenance line) while a realistic task * dispatched correctly; `sonnet` obeyed on both. The owner then directed "do not hard-code a model * name", and `inherit` was chosen over an OMITTED line because an omitted `model:` falls through * a four-rung resolution order whose third rung is the `CLAUDE_CODE_SUBAGENT_MODEL` environment * variable (https://code.claude.com/docs/en/sub-agents.md), while `inherit` selects the calling * session's model directly. * * v8 (2026-09-16) REVERSES that. Owner direction, verbatim: "there is absolutely no reason for * Fable to be running a dispatch like that." Under `inherit` the wrapper's poll loop runs on the * calling session's model; a session on the most expensive alias paid that rate for every poll, * and a Workflow that fans out N relay agents paid it N times — the opposite of the reason the * agent exists, which is to put the WORK on a lane and keep the session's quota out of it. The * 2026-09-04 rejection of `haiku` was measured against the v1 template, before rule 2 ("dispatch * every task, even a trivial one") and rule 8 (no provenance line without a real * `dispatch_result`) existed. Measured again 2026-09-16 on the v7 template: a `haiku` wrapper * given an echo task in `answer` mode made two tool calls (ToolSearch, dispatch), returned the * lane's word verbatim, and appended `provenance: job=job-0036 lane=free-pool spec=pool/medium * elapsed=2s` — 16s, 14,660 wrapper tokens. So v8 pins `DEFAULT_RELAY_AGENT_MODEL` (`haiku`), * lets `llm-relay setup --relay-model ` choose another, and REFUSES `inherit` by name so * the reversed default cannot be reinstated by accident. The Codex agent file * (`~/.codex/agents/relay.toml`, written by `scripts/install-skill.mjs`) has no model key and is * untouched: the "must also work from Codex" concern of v4 never applied to this file, which is * read by Claude Code alone. A caller may still pass `model` on a single Agent/`agent()` call. * * v9 (2026-09-19) makes the relay's published `walk-verdict` the wrapper's only liveness * authority while polling. The wrapper must not infer from elapsed time, stream silence, historical * duration or activity diagnostics — those were the circumstantial clues the status contract * was introduced to eliminate. */ /** * The two failure tokens a `relay` wrapper may return, and they are deliberately DIFFERENT * (2026-09-08, C:\Code\docs\backlog.md "Codex relay wrapper can report lane provenance for its own * inline review"): `RELAY_DISPATCH_FAILED` means a lane ran and did not answer, which the caller * answers by retrying a different tier; `RELAY_DISPATCH_UNAVAILABLE` means no lane was ever * reached, which the caller answers by doing the work itself. One token for both collapses the * distinction the whole feature rests on — a fabricated provenance line for a lane that never ran * reads identically to a real one. */ export declare const RELAY_DISPATCH_FAILED_TOKEN = "RELAY_DISPATCH_FAILED"; export declare const RELAY_DISPATCH_UNAVAILABLE_TOKEN = "RELAY_DISPATCH_UNAVAILABLE"; /** * Strip the caller's `[answer]` / `[agent]` mode tag from a task, returning the mode it forces. * * ⚠ This is a HELPER, not only a prompt instruction, because a tag the wrapper forgets to strip * travels to the lane as literal task text (the `@relay:` directive rule: request content must not * be quietly rewritten by prose the relay cannot see). Only a tag followed by whitespace counts — * `"A tag that is not followed by whitespace is not a tag."` */ export declare function stripRelayDispatchPrefix(task: string): { mode: "agent" | "answer" | undefined; task: string; }; /** * Why a refusal and not a silent substitution: `inherit` is the exact spelling v4 chose and v8 * reversed. A caller who types it is asking for the old behaviour, and the honest answer is to * say that it is gone and why, not to write `haiku` and report success. */ export declare function relayAgentModelRefusal(model: string): string | undefined; export declare function relayAgentTemplate(model?: string): string; /** The template as written when no model is named — the default install, byte for byte. */ export declare const RELAY_AGENT_TEMPLATE: string; export declare function getRelayAgentPath(opts?: SetupOptions): string; export declare function installRelayAgent(opts?: SetupOptions): { success: boolean; path: string; message: string; }; export declare function getClaudeDesktopConfigPath(): string; /** * The Claude Desktop entry name. It differs from "llm-relay", the name a Claude Code user config * carries, so that a Code tab session keeps its own engine's server (see `setupClaudeDesktop`). */ export declare const DESKTOP_MCP_SERVER_NAME = "llm-relay-desktop"; /** Configures Claude Desktop with the host-independent llm-relay MCP dispatch server. */ export declare function setupClaudeDesktop(opts?: SetupOptions): { success: boolean; path: string; message: string; }; /** * Print/verify instructions and wrapper script setup for Claude CLI. * * The lines are BUILT, then written through `opts.out` (default `console.log`) and also * returned. A caller that only wants to check what this says — a test, or anything embedding * the helper — can read `lines` and pass a silent sink instead of scraping stdout. */ export declare function setupClaudeCli(opts?: SetupOptions): { success: boolean; message: string; lines: string[]; };