/** * Wire protocol shared between the daemon and the web app. * * NOTE: web/src/protocol.ts is a copy of this file. Keep the two in sync — they are * duplicated rather than shared because the server compiles under NodeNext while the web * app compiles under a bundler resolution, and a single rootDir cannot span both. */ export type EventKind = | "session_start" | "session_end" | "prompt" | "tool_start" | "tool_end" | "tool_fail" | "permission_denied" | "turn_end" | "turn_error" | "notification" | "subagent_start" | "subagent_end" | "compact" | "approval_request" | "approval_allowed" | "approval_denied" | "approval_expired"; export type SessionState = "working" | "idle" | "waiting" | "error" | "ended"; export interface GlanceEvent { id: number; ts: number; sessionId: string; kind: EventKind; /** Tool name, for tool-shaped events. */ tool?: string; /** One-line human summary, already truncated and redacted. */ title: string; /** Optional second line, e.g. a file path or an error message. */ detail?: string; durationMs?: number; } export interface SessionView { id: string; /** Basename of the workspace root — what you actually recognise on a phone. */ label: string; cwd: string; state: SessionState; startedAt: number; lastActivity: number; lastPrompt?: string; currentTool?: { name: string; title: string; startedAt: number }; counts: { tools: number; failures: number; denials: number }; } export interface PendingApproval { id: string; sessionId: string; sessionLabel: string; tool: string; title: string; detail?: string; createdAt: number; expiresAt: number; } export type ApprovalMode = "off" | "risky" | "all"; export interface ApprovalSettings { mode: ApprovalMode; riskyPattern: string; timeoutMs: number; /** Skip gating entirely when no browser is streaming, so an unwatched agent never stalls. */ requireWatcher: boolean; /** What to do when nobody answers in time. Allow keeps the agent moving; deny is stricter. */ onTimeout: "allow" | "deny"; } export interface Snapshot { now: number; version: string; sessions: SessionView[]; events: GlanceEvent[]; pending: PendingApproval[]; approval: ApprovalSettings; } export interface DeviceInfo { id: string; label: string; createdAt: number; lastUsedAt?: number; } /** Everything the app needs before it knows whether you are signed in. */ export interface GateInfo { authenticated: boolean; /** False when no passkey has been enrolled yet — the app then asks for an enrolment code. */ enrolled: boolean; /** True while a one-time enrolment code minted by `glance enroll` is still valid. */ enrollmentOpen: boolean; version: string; deviceLabel?: string; /** The WebAuthn RP ID in force. Shown so a hostname mismatch is diagnosable from the phone. */ rpId?: string; }