import { type Send, type Signal } from '@llui/dom'; import type { AgentSession, AgentToken } from '../protocol.js'; import type { AgentEffect } from './effects.js'; export type AgentConnectStatus = 'idle' | 'minting' | 'pending-claude' | 'active' | 'reconnecting' | 'failed' | 'error'; export type AgentConnectPendingToken = { token: AgentToken; tid: string; lapUrl: string; /** * Natural-language connect instruction the user copies into Claude. * Includes URL, token, and the explicit `connect_session` tool * call. Works in any Claude client (Desktop, CC CLI, etc.) — the * Desktop-specific `/llui-connect` slash command is sugar over the * same tool call. */ connectSnippet: string; expiresAt: number; /** * Cached so the auto-reconnect path can re-open the WS without * re-minting. The MintSucceeded → AgentOpenWS path stores it; the * RestoreSession path also fills it in. Cleared by `Disconnect`. */ wsUrl: string; }; export type AgentConnectState = { status: AgentConnectStatus; pendingToken: AgentConnectPendingToken | null; sessions: AgentSession[]; resumable: AgentSession[]; error: { code: string; detail: string; } | null; /** * Reconnect attempt counter. Incremented on each WS-close that * triggers an auto-reconnect; reset on `WsOpened` and on user * actions (`Disconnect`, fresh `Mint`). Drives the backoff schedule * (1s, 2s, 4s, 8s, 16s, 30s, 30s, …) and surfaces to UI as * "reconnecting (attempt 3 / next in 4s)". */ reconnectAttempt: number; /** * Total cumulative ms spent in `reconnecting` for the current * outage. Compared against `reconnectGiveUpMs` (effect-side option, * default 5 min) to decide when to surface `failed` to the user. * Reset whenever a WS opens successfully. */ reconnectElapsedMs: number; }; export type AgentConnectMsg = /** @intent("Mint a new agent token and open the pairing WebSocket") */ { type: 'Mint'; } /** * @humanOnly — internal: dispatched by the AgentMintRequest effect * handler when the mint endpoint replies success. Carries the token * and connection URLs into state. */ | { type: 'MintSucceeded'; token: AgentToken; tid: string; lapUrl: string; wsUrl: string; expiresAt: number; } /** @humanOnly — internal: dispatched by the AgentMintRequest handler on failure. */ | { type: 'MintFailed'; error: { code: string; detail: string; }; } /** @humanOnly — internal: WS adapter signalled the pairing socket is open. */ | { type: 'WsOpened'; } /** @humanOnly — internal: WS adapter signalled the pairing socket is closed. */ | { type: 'WsClosed'; } /** @humanOnly — internal: Claude bound the session via /agent/claim. */ | { type: 'ActivatedByClaude'; } /** @intent("Check which previously-issued agent sessions can be resumed") */ | { type: 'ResumeList'; tids: string[]; } /** @humanOnly — internal: AgentResumeCheck effect handler returned the list. */ | { type: 'ResumeListLoaded'; sessions: AgentSession[]; } /** @intent("Resume an existing agent session by tid") */ | { type: 'Resume'; tid: string; } /** * @humanOnly — internal: dispatched by the AgentResumeClaim effect * handler when `/resume/claim` returns the ROTATED bearer. Mirrors * `MintSucceeded`: stores the new token + URLs and PERSISTS them, so a * refresh after a resume survives the same way a fresh mint does. * Without this the rotated token was never persisted and the next * refresh restored the stale (now-invalid) pre-rotation bearer. */ | { type: 'ResumeSucceeded'; token: AgentToken; tid: string; lapUrl: string; wsUrl: string; expiresAt: number; } /** @intent("Revoke an agent session by tid") */ | { type: 'Revoke'; tid: string; } /** @intent("Dismiss the current agent connect error") */ | { type: 'ClearError'; } /** @humanOnly — internal: AgentSessionsList effect handler returned the list. */ | { type: 'SessionsLoaded'; sessions: AgentSession[]; } /** @intent("Refresh the list of active agent sessions") */ | { type: 'RefreshSessions'; } /** * @intent("Copy the agent connect snippet to the clipboard") * Resolves the pendingToken's snippet in update() (state-reading is * what update() is for) and dispatches a clipboard-write effect. */ | { type: 'CopyConnectSnippet'; } /** * @humanOnly — internal: app boot dispatches this with credentials * read from sessionStorage to skip the mint round-trip after page * refresh. The agent's token (still alive on the server) keeps * working since we don't go through the rotate-on-resume path. The * reducer is idempotent against an in-flight Mint — only fires from * `idle`. */ | { type: 'RestoreSession'; token: AgentToken; tid: string; lapUrl: string; wsUrl: string; expiresAt: number; } /** * @intent("Disconnect the active agent session and clear all * persisted credentials. Stops any in-flight reconnect attempt; * subsequent WS closures stay in `idle` instead of triggering * auto-reconnect. Use when the user explicitly clicks Disconnect * in the panel — for transient drops, do nothing and let the * reconnect loop run.") */ | { type: 'Disconnect'; } /** * @humanOnly — internal: scheduler effect dispatched this when the * backoff timer fired. The reducer increments the attempt counter, * adds the just-elapsed delay to `reconnectElapsedMs`, and emits * `AgentOpenWS` with the cached pendingToken/wsUrl so the WS can * reattach without minting. */ | { type: 'ReconnectAttempt'; elapsedMs: number; } /** * @humanOnly — internal: scheduler effect dispatched this when the * give-up ceiling was reached without a successful WS open. * Reducer flips status to `failed` so the UI can surface a clear * error and offer a manual reconnect. */ | { type: 'ReconnectGaveUp'; }; /** * Options threaded through `init()` and `update()`. `mintUrl` is * optional — when omitted the agent effect handler derives it from * `EffectHandlerHost.agentBasePath` (default `/agent` → `/agent/mint`). * Set explicitly only when the mint endpoint lives outside the * configured base path. */ export type AgentConnectInitOpts = { mintUrl?: string; }; /** Component shape is [State, Effect[]] — consistent with @llui/components. */ export declare function init(_opts: AgentConnectInitOpts): [AgentConnectState, AgentEffect[]]; export declare function update(state: AgentConnectState, msg: AgentConnectMsg, opts?: AgentConnectInitOpts): [AgentConnectState, AgentEffect[]]; export type AgentConnectConnectOptions = { id?: string; }; /** * Static prop bag with reactive (Signal-handle) values. Mirrors the * @llui/components pattern (e.g. `dialog.connect`): callers spread bag * keys directly into element helpers, and handle-valued props re-evaluate * per binding-mask hit. The caller passes the `agent-connect` state slice * as a `Signal`; reactive props are derived from it via `state.map(...)`. */ export type ConnectBag = { root: { 'data-scope': 'agent-connect'; 'data-state': Signal; }; mintTrigger: { onClick: () => void; disabled: Signal; }; pendingTokenBox: { 'data-part': 'pending-token'; 'data-visible': Signal; }; copyConnectSnippetButton: { onClick: () => void; disabled: Signal; }; sessionsList: { 'data-part': 'sessions-list'; }; sessionItem: (tid: string) => { 'data-part': 'session-item'; 'data-tid': string; }; revokeButton: (tid: string) => { onClick: () => void; }; resumeBanner: { 'data-part': 'resume-banner'; 'data-visible': Signal; }; resumeItem: (tid: string) => { 'data-part': 'resume-item'; 'data-tid': string; }; resumeButton: (tid: string) => { onClick: () => void; }; dismissButton: (tid: string) => { onClick: () => void; }; error: { 'data-part': 'error'; 'data-visible': Signal; onClick: () => void; }; }; /** * Builds prop bags for the view. Static-bag-with-Signal-handles shape * (matches the @llui/components convention); spread directly into * element helpers. */ export declare function connect(state: Signal, send: Send, _opts?: AgentConnectConnectOptions): ConnectBag; //# sourceMappingURL=agentConnect.d.ts.map