diff --git a/web/src/api/acp.ts b/web/src/api/acp.ts index 25d6b93..4039caa 100644 --- a/web/src/api/acp.ts +++ b/web/src/api/acp.ts @@ -47,6 +47,54 @@ export interface UpdateAgentKindReq { enabled?: boolean } +// AcpSession mirrors the backend session DTO returned by /api/v1/acp/sessions. +export interface AcpSession { + id: string + workspace_id: string + project_id: string + agent_kind_id: string + user_id: string + issue_id: string | null + requirement_id: string | null + agent_session_id: string | null + branch: string + cwd_path: string + is_main_worktree: boolean + status: 'starting' | 'running' | 'crashed' | 'exited' + pid: number | null + exit_code: number | null + last_error: string | null + started_at: string + ended_at: string | null +} + +export interface CreateSessionReq { + workspace_id: string + agent_kind_id: string + branch?: string + issue_number?: number + requirement_number?: number + initial_prompt?: string +} + +// AcpEvent is one frame from the session event stream (history REST + WS push). +export interface AcpEvent { + id: number + direction: 'in' | 'out' + kind: + | 'request' + | 'response' + | 'notification' + | 'error' + | 'state' + | 'session_terminated' + | 'slow_consumer_disconnect' + | 'client_error' + method?: string + payload: unknown + truncated: boolean +} + const enc = (v: string) => encodeURIComponent(v) export const acpApi = { @@ -62,4 +110,30 @@ export const acpApi = { remove: (id: string) => request(`/api/v1/acp/agent-kinds/${enc(id)}`, { method: 'DELETE' }), }, + sessions: { + list: (all?: boolean) => + request('/api/v1/acp/sessions' + (all ? '?all=true' : '')), + get: (id: string) => + request(`/api/v1/acp/sessions/${enc(id)}`), + create: (req: CreateSessionReq) => + request('/api/v1/acp/sessions', { method: 'POST', body: req }), + terminate: (id: string) => + request(`/api/v1/acp/sessions/${enc(id)}`, { method: 'DELETE' }), + events: (id: string, since: number) => + request(`/api/v1/acp/sessions/${enc(id)}/events?since=${since}`), + }, +} + +// openSessionWS opens a native WebSocket to /sessions/{id}/ws. +// +// Auth: browser WebSocket cannot send custom headers, so the token is sent +// via the ?token= query parameter. The backend auth middleware accepts it +// as a fallback to Authorization. Keep token lifetimes short. +export function openSessionWS(sessionId: string, since: number, token: string): WebSocket { + const proto = window.location.protocol === 'https:' ? 'wss' : 'ws' + const host = window.location.host + const url = + `${proto}://${host}/api/v1/acp/sessions/${enc(sessionId)}` + + `/ws?since=${since}&token=${enc(token)}` + return new WebSocket(url) }