/** * Cloudflare tunnel setup — deterministic orchestrator. * * the tunnel setup flow (login → create → enable → verify) has a * deterministic shape. Every branch is driven by a structured signal — * either `tunnel-status`'s `unhealthyReason` enum, or the operator's * literal input (domain name, subdomain names, "done"). * the flow was driven by the LLM reading SKILL.md prose and picking the * next `tunnel-*` tool; that allowed a dozen+ recurrences where the LLM * forked, dispatched Playwright to the Cloudflare dashboard, or fabricated * a `cp cert.pem` workaround. None of those deviations are reachable here * because the LLM no longer chooses — this module does. * * Contract: * - `runOrchestrator(input)` is the single entry point. One call advances * one deterministic step. The LLM's only job is to pass the operator's * literal input through (domain, "done", etc.) and relay the structured * result verbatim. * - Every transition emits a `[cloudflare:setup-run:phase-transition]` log * line. Every internal sub-tool call emits * `[cloudflare:setup-run:subtool-call]`. Every terminal state emits * `[cloudflare:setup-run:terminal]`. These give a single grep path * (`[cloudflare:setup-run:`) to the full transition chain post-hoc. * - State is persisted at `~/{configDir}/cloudflared/setup.state.json` * alongside `tunnel.state` and `login.state`. On corruption or absence * the orchestrator reconciles from live `tunnel-status` + operator * input (no "state file not found" crash). * - The tool-surface gate in `platform/ui/app/lib/tool-surface-filter.ts` * reads this state file every turn and removes the raw `tunnel-*`, * Playwright, Bash, Write, Edit tools from the LLM's menu whenever * `phase !== 'idle' && phase !== 'healthy'`. No LLM regression can * re-enter the loop because the tools that enabled it are literally * absent from the menu. */ export type OrchestratorPhase = "idle" | "awaiting-domain" | "awaiting-signin" | "awaiting-account-switch" | "awaiting-hostnames" | "enabling" | "healthy" | "unhealthy"; export interface OrchestratorState { phase: OrchestratorPhase; domain: string | null; adminSubdomain: string | null; publicSubdomain: string | null; /** The tunnelId once `tunnel-create` has run — used to short-circuit re-entry. */ tunnelId: string | null; /** ISO 8601 — when the current phase was entered. Diagnostics only. */ phaseEnteredAt: string; /** Set when phase is `unhealthy`; mirrors cloudflared.TunnelUnhealthyReason. */ unhealthyReason: string | null; } /** * The orchestrator state file path. Consumers OUTSIDE this module (e.g. the * tool-surface filter in `platform/ui/app/lib/tool-surface-filter.ts`) also * need this path to read the phase. Exported so the two sides cannot drift. * * Brand-aware via loadBrand(); falls back to `.maxy` when PLATFORM_ROOT is * unset. The fallback matters only for the tool-surface filter running * outside the cloudflare MCP server's process — the MCP server always has * PLATFORM_ROOT set by the claude-agent spawn. */ export declare function setupStatePath(configDirOverride?: string): string; export declare function readOrchestratorState(): OrchestratorState; /** * Whether the tool-surface filter should treat this device as "setup in * progress". Matches the gate condition named in the task file: the filter * lifts whenever phase is idle or terminal (healthy / unhealthy). */ export declare function isSetupActive(state: OrchestratorState): boolean; export interface OrchestratorInput { /** Bare domain, e.g. "maxy.bot". Required on the first call; ignored afterwards. */ domain?: string; /** Admin subdomain label (no dots), e.g. "admin" → admin.{domain}. Required at `awaiting-hostnames`. */ adminSubdomain?: string; /** Optional public subdomain label. */ publicSubdomain?: string; /** Operator signalled "done" — only meaningful at `awaiting-signin` / `awaiting-account-switch`. */ confirmed?: boolean; } export interface OrchestratorResult { /** * The agent-facing string. Includes a `maxy-device-url` fenced block when * a device-bound action is required; includes hostnames on terminal * success. The admin agent's only job is to relay this verbatim. */ text: string; /** Phase at the end of this call. Diagnostic only — the agent never reads it. */ phase: OrchestratorPhase; /** True at terminal success. */ healthy: boolean; } export declare function runOrchestrator(input: OrchestratorInput): Promise; export declare function resetOrchestrator(): void; //# sourceMappingURL=setup-orchestrator.d.ts.map