import EventEmitter from "node:events"; import type { AdminsResult, BlockResult } from "./device-admin.js"; import { type TokenVault } from "./session-store.js"; import type { ScreenShareState } from "@aicommander/protocol"; import type { CapturedScreenshot, ScreenshotOptions } from "./screenshot.js"; export type AgentStatus = "connecting" | "connected" | "disconnected" | "disabled"; /** * Emitted as the controller's "remote-connect" event when a remote operator's * command clears the rate limit (see RemoteNotifier). The desktop app turns this * into the "someone connected" notice; the headless agent simply has no listener. */ export interface RemoteConnectInfo { /** Stable operator key: an account userId, or "anon" for session-code callers. */ id: string; anonymous: boolean; } /** * Supplies the connection layer with the desktop screen-share grant state and a * way to capture the screen. Only the desktop app provides one; the headless * Linux agent leaves it undefined (so it reports `capable: false`). Implementers * emit "change" whenever the grant flips (user toggle or 24h auto-expiry) so the * live connection can push the new state to the relay without reconnecting. */ export interface ScreenShareProvider { getState(): ScreenShareState; /** * `options` is optional so a provider written before multi-display support * still satisfies this type; the connection layer always passes it, and any * provider that ignores it simply captures the primary display — which the * reply then reports honestly, because the metadata comes back from the * capture, never from the request. */ capture(options?: ScreenshotOptions): Promise; on(event: "change", listener: () => void): void; off(event: "change", listener: () => void): void; } export interface AgentControllerOptions { /** * Directory for the durable device identity + session store. The desktop app * passes its per-user data dir (e.g. Electron's `app.getPath("userData")`) so * the STABLE session code survives app updates and is removed only on full * uninstall. When omitted, the device/session-store defaults are used — the * Linux CLI behavior: AICOMMANDER_CONFIG_DIR when it is set (see config-dir.ts), * otherwise /etc with a ~/.config fallback. */ configDir?: string; /** Desktop-only OS-protected storage for the reusable agent token. */ tokenVault?: TokenVault; /** * Desktop-only screen-share provider. When supplied, the agent advertises * screen-share capability/state and can answer screenshot requests. */ screenShare?: ScreenShareProvider; /** Desktop-only signed native Windows exec launcher outside Electron ASAR. */ windowsExecLauncherPath?: string; } export declare class AgentController extends EventEmitter { private serverUrl; private configDir?; private tokenVault?; private screenShare?; private windowsExecLauncherPath?; private abortController; private _status; private _code; private _active; private _lastHeartbeatAt; private _jobs; private readonly _remote; constructor(serverUrl: string, opts?: AgentControllerOptions); get status(): AgentStatus; get sessionCode(): string | null; /** True while a remote command is actively executing on this machine. */ get active(): boolean; /** Epoch-ms of the last proof the link is alive (open or server ping), or 0. */ get lastHeartbeatAt(): number; private sessionStoreCtx; start(): void; private _runLoop; stop(): void; restart(): Promise; /** * Force a brand-new session code (change-code path). Clears the stored session * and writes the one-shot rotate marker, then restarts the connection so the * register loop mints a fresh code with `forceNew`. Emits the new `code`. */ changeCode(): Promise; /** * List the accounts ("admins") linked to THIS device, with masked emails. Uses * the controller's own device identity (configDir) so the relay sees the exact * registered device. For the desktop "Linked Accounts" UI. */ listAdmins(): Promise; /** Block one linked account by userId — refuses its access, keeps it listed. */ blockAdmin(userId: string): Promise; /** Unblock one account by userId — restores its access immediately. */ unblockAdmin(userId: string): Promise; private _setStatus; /** * Funnel an operator-initiated command through the connect-notice rate limiter; * emit "remote-connect" only when a fresh notice is due. Never throws into the * ws event handler that calls it. */ private _noteRemote; private _onHeartbeat; private _setActive; }