import { type AuthState } from "./claude-auth.js"; /** * The lead agent an instance starts life with. * * A fresh cockpit shows "no agent open" and leaves the newcomer to guess. This * creates one channel — `general`, running the `Shadok-Boss` profile, which is * written to be the one the user talks to first. * * It spawns through a WebSocket to our OWN server, like the Telegram bridge, * the cron driver and `pilotctl`. That is not a stylistic choice: there is no * server-side path that opens a session without a client, and inventing one * would be a second way to start an agent, drifting from the first. */ /** The name every instance's first channel carries. */ export declare const FIRST_AGENT_NAME = "general"; export interface FirstAgentPlan { spawn: boolean; reason: "first-boot" | "channels-exist" | "not-signed-in"; name?: string; profile?: string; } /** * Pure: should this instance start its lead agent? * * The channel check comes first on purpose. A cockpit in use must never be * skipped "because it is not signed in" — this decision is only ever visible in * a log line, so the reason has to be the true one. */ export declare function firstAgentPlan(input: { channelCount: number; authState: AuthState; }): FirstAgentPlan; /** * What the cockpit is allowed to SAY while it has no agent to show. * * The browser cannot work this out on its own: "a first agent is on its way" * and "the user closed their last tab" are the same zero channels (invariant * 18, which is why `active` can be null). Guessing picks one of two wrong * answers — either a returning user is told forever that their first agent is * starting, or a brand-new one is invited to create a SECOND lead agent while * the first is being born. So the side doing the spawning says which it is. * * The state is deliberately hard to leave switched on. Every exit from * `ensureFirstAgent` goes through `settleFirstAgent`, including the ones that * spawn nothing (`not-signed-in`, `channels-exist`), the socket erroring and * the 60s guard — because a flag that could stick would leave a signed-out * cockpit reading "starting your first agent…" forever, which is a worse first * impression than the empty state this replaces. */ export interface FirstAgentStatus { /** Is a lead agent genuinely being started right now? */ pending: boolean; /** Why — the plan's reason once one has been computed, before that "starting". */ reason: FirstAgentPlan["reason"] | "starting" | "idle"; } /** A copy, so no caller can hold a handle on the state and edit it. */ export declare function firstAgentStatus(): FirstAgentStatus; /** * Say a spawn is coming, BEFORE the attempt itself. * * The boot path defers `ensureFirstAgent` a beat (so it dials a server already * accepting) and opens the browser first — so without this the very page this * status exists for loads during that gap and is told "idle", which is the bug. * The auth probe inside `ensureFirstAgent` costs another ~850ms on top. * * Authoritative, not sticky: called with channels present it CLEARS the flag * rather than leaving a stale one — an instance that has agents has nothing * pending by definition. */ export declare function announceFirstAgent(channelCount: number): void; /** The one way out of `pending`. Never conditional: every reason ends the wait. */ export declare function settleFirstAgent(reason: FirstAgentPlan["reason"]): void; /** * Start the lead agent if this instance has none. Idempotent by construction: * the condition is "no channel at all", so both callers can call it freely. * * Never throws and never blocks its caller's own work — a cockpit that starts * without its lead agent is a smaller problem than a boot that fails. */ export declare function ensureFirstAgent(deps: { port: number; cookie?: string; cwd: string; /** Called with the new session id once the server reports it ready. */ onReady: (sessionId: string, name: string) => void; }): Promise;