Suporte remoto (Console)

É um agente de IA? Busque a skill de onboarding e leia toda a API em texto limpo — skill · llms.txt · llms-full.txt

Suporte remoto (Console)#

O mesmo canal remote-support.v1, visto do lado do operador. São dois planos com duas credenciais, e essa separação é o contrato:

Plano Quem Credencial
Runtime o cliente local de suporte X-Agent-Token: prt_… (o WebSocket acima)
Console um humano operador, no navegador JWT de sessão (+ X-CSRF-Token nas mutações)

O navegador nunca recebe um token prt_, e não existe endpoint, campo ou corpo capaz de conceder consentimento: essa transição tem um único escritor, o humano no teclado da máquina remota. O Console oferece; quem consente e executa é o cliente local. Os dois planos operam a mesma sessão e a mesma allowlist.

Autorização#

Toda rota exige sessão autenticada e a capability remote_support:operate: owner e admin a têm, o papel agent não, e um grant direto do IdP (perms: ["remote_support:operate"]) também autoriza. Mutações passam por CSRF. O escopo (conta, agente, sessão) vem da credencial e da URL; qualquer tripla de fora recebe 404 SESSION_NOT_FOUND — o mesmo que uma sessão inexistente.

Com o recurso desligado, toda a família responde 503 REMOTE_SUPPORT_DISABLED.

Endpoints#

Método Rota Sucesso
POST /v1/agents/{id}/remote-support/sessions 201 + SessionView
GET /v1/agents/{id}/remote-support/sessions 200 + {version, sessions[], total}
GET /v1/agents/{id}/remote-support/sessions/{sid} 200 + SessionView (snapshot)
POST /v1/agents/{id}/remote-support/sessions/{sid}/close 200 — revoga a sessão
POST /v1/agents/{id}/remote-support/sessions/{sid}/commands 202 + CommandView
POST /v1/agents/{id}/remote-support/sessions/{sid}/commands/{requestId}/cancel 202 (ou 200 se já terminal)
GET /v1/agents/{id}/remote-support/sessions/{sid}/events 200 text/event-stream

{sid} é o id da conversa do agente — o mesmo que o WebSocket autoriza. Corpos são decodificados em modo estrito: campo desconhecido é 400.

jsonc
// POST .../sessions
{ "session_id": "sess_a1b2c3" }

// POST .../sessions/{sid}/commands
{ "command": "diagnostics",      // identificador da allowlist, nunca linha de shell
  "argv": ["--fast"],            // vetor estruturado
  "reason": "investigar chamado 4711", // opcional; vai VERBATIM para a tela de consentimento
  "idempotency_key": "op-1" }    // repetir devolve o MESMO request, com "replayed": true

O request_id é cunhado pelo servidor (rsr_…), não escolhido pelo cliente.

Snapshot e comandos#

SessionView traz version, session_id, agent_id, state, origin, client_id, os timestamps do ciclo (authorized_at, connected_at, ready_at, disconnected_at, closed_at), close_code/close_reason, output_bytes, allowed_commands, limits, commands[], events[], cursor e replay (oldest_id, latest_id, size, capacity). Nenhum campo carrega credencial de runtime, linha de comando bruta ou saída fora dos limites.

state da sessão: awaiting_client → connected → ready → disconnected → closed. state do comando: awaiting_consent → running → exited / denied / cancelled / failed.

CommandView traz request_id, idempotency_key, command, argv, state, requested_at, started_at, ended_at, exit_code, truncated, cancel_requested, output_bytes, code, message, consent_deadline_ms e timeout_ms.

Stream de eventos (SSE)#

text
GET /v1/agents/{id}/remote-support/sessions/{sid}/events?after=42
Authorization: Bearer <jwt>

Autenticado pelo mesmo JWT das demais leituras — consuma com fetch (o EventSource nativo não manda Authorization), nunca credencial em query string. O cursor vem de ?after= ou do header Last-Event-ID.

Cada evento sai com id: e event:, e o corpo é {id, version, type, session_id, request_id, at, data}. Tipos: session.authorized, session.client_connected, session.client_ready, session.client_disconnected, session.closed, command.accepted, command.started, command.consent_denied, command.consent_expired, command.output, command.exited, command.cancel_requested, command.cancelled, command.failed.

command.started só existe porque o cliente local mandou consent_granted.

O primeiro frame é sempre stream_status, com status ok, replay_expired (o cursor é anterior à janela) ou cursor_invalid (cursor à frente do log). Nos dois últimos vem "snapshot_required": true: busque o snapshot e recomponha, em vez de renderizar uma sessão com buraco no meio. Há : heartbeat a cada 20 s, stream_end (session_closed ou stream_max_age, aos 30 min) e stream_error (BACKPRESSURE) quando este navegador parou de ler.

Limites e redação#

Janela de replay de 256 eventos por sessão, 8 streams simultâneos (o nono é 429 BACKPRESSURE), chunk de saída de 8 KiB por evento (acima disso truncated: true), 20 comandos no histórico do snapshot e registro retido por 2 h após o fim. Os limites do canal em si vêm em limits.

Toda saída e toda mensagem passam por redação por forma antes de chegar ao navegador — prefixos de credencial, chaves de provedor, JWTs, blocos PEM, headers Authorization e atribuições cujo nome diz que é segredo viram [REDACTED].

Erros#

REMOTE_SUPPORT_DISABLED (503) · SESSION_NOT_FOUND (404) · REQUEST_ID_UNKNOWN (404) · SESSION_NOT_AUTHORIZED (403) · COMMAND_NOT_ALLOWED (403) · COMMAND_ARGS_INVALID (400) · BAD_REQUEST (400) · SESSION_CLOSED / SESSION_EXPIRED / CLIENT_NOT_READY / COMMAND_LIMIT_EXCEEDED (409) · BACKPRESSURE (429) · AUDIT_UNAVAILABLE (503) · CSRF_INVALID (403).