Suporte remoto (WebSocket)

É 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 (WebSocket)#

Canal bidirecional remote-support.v1 para atendimento remoto assistido. Um cliente local autorizado abre um WebSocket e a API coordena a sessão: autentica, autoriza o escopo, oferece comandos de uma allowlist do servidor, exige consentimento explícito por comando e retransmite a saída dentro de limites publicados.

Não é shell remoto e não pode virar um. A API não executa nada — quem executa é o cliente local, e só depois do consentimento. Um comando é sempre um identificador da allowlist + argumentos estruturados; não existe campo no protocolo capaz de carregar uma linha de shell, pipe ou redirecionamento.

Desligado por padrão. Enquanto o recurso não estiver habilitado no deployment, a rota responde 503 REMOTE_SUPPORT_DISABLED e nunca faz upgrade. Habilitar não basta: sem allowlist configurada, nenhum comando é autorizado. O cliente desktop é um componente à parte e ainda não faz parte desta entrega — mas fala exatamente este contrato, provado por um teste de integração que roda o cliente real contra esta API.

GETAbrir a sessão#

text
GET /v1/agent-runtime/{id}/sessions/{sid}/remote-support/ws
X-Agent-Token: prt_...

Mesmas garantias do resto do plano runtime: o token prt_ é ligado ao seu agente, {sid} tem de ser uma sessão desse agente, e limites de plano e rate limit continuam valendo. O token vai no header — nunca em query string. O escopo (conta, agente, sessão) vem da credencial e da URL; nada que o cliente envie o amplia, e endereçar outra tripla devolve SESSION_NOT_FOUND.

POSTHandshake#

O primeiro frame tem de ser hello; nada é aceito antes do hello_ack.

json
{"type":"hello","protocol":"remote-support.v1","session_id":"sess_a1b2c3",
 "client_id":"desk-9","client_version":"1.4.2"}

O session_id apenas confirma o que a credencial já autorizou — divergência é SESSION_NOT_AUTHORIZED. Todo frame, nas duas direções, carrega type e protocol; versão diferente é PROTOCOL_UNSUPPORTED em qualquer frame, não só no handshake, e não há downgrade. O hello_ack devolve o contrato explícito: limites, heartbeat, política de reconexão e a allowlist exata de comandos (sempre presente, mesmo vazia).

json
{"type":"hello_ack","protocol":"remote-support.v1","session_id":"sess_a1b2c3",
 "agent_id":"AGENT_ID","resume":"unsupported","commands":["collect_logs"],
 "limits":{"max_message_bytes":65536,"max_output_bytes_per_request":1048576,
           "max_output_bytes_per_session":8388608,"max_concurrent_commands":1,
           "consent_timeout_ms":60000,"command_timeout_ms":300000,
           "idle_timeout_ms":900000,"session_ttl_ms":3600000},
 "heartbeat":{"ping_interval_ms":20000,"pong_timeout_ms":10000}}

POSTCiclo de um comando#

text
cliente → hello                    servidor → hello_ack
cliente → ready
                                   servidor → command_request (request_id)
cliente → consent_granted | consent_denied
                                   servidor → command_start   (só se granted)
cliente → stdout / stderr / exit
qualquer lado → cancel             servidor → error | close
  • command_request carrega command:{name,args}, origin:{operator,reason}, consent_deadline_ms e timeout_ms. origin é obrigatório: consentimento sem quem pede e por quê não é consentimento informado.
  • command_start é a autorização de partida — o cliente reporta a decisão, mas só executa depois desse frame.
  • exit carrega status (completed, failed, timeout, cancelled), exit_code e truncated.
  • cancel tem a mesma forma nas duas direções: {request_id, reason}.
  • Saída antes do consentimento é violação de protocolo, não dado: os bytes são descartados e o cliente recebe CONSENT_REQUIRED.
  • consent_granted repetido é idempotente e nunca inicia o comando duas vezes.
  • request_id é obrigatório em consent_granted, consent_denied, stdout, stderr, exit e cancel.
  • Tipos aceitos do cliente: hello, ready, consent_granted, consent_denied, stdout, stderr, exit, cancel. Os demais são só do servidor. Campo desconhecido ou JSON inválido encerra a conexão com MESSAGE_MALFORMED. Como não há error do cliente, uma recusa local vira consent_denied com reason, e uma falha em execução vira exit com status:"failed".

Formato de um comando:

json
{"type":"command_request","protocol":"remote-support.v1","request_id":"rsr_...",
 "command":{"name":"collect_logs","args":["--since","1h"]},
 "origin":{"operator":"suporte@catcher.one","reason":"investigar chamado 4711"},
 "consent_deadline_ms":60000,"timeout_ms":300000}

Máximo de 8 argumentos, 256 bytes cada, 1024 bytes no total; caracteres permitidos A-Za-z0-9._:/=+@,- e .. é rejeitado. Nome fora da allowlist é COMMAND_NOT_ALLOWED; argumento inválido é COMMAND_ARGS_INVALID.

GETLimites e reconexão#

Limite Padrão
Frame de entrada 64 KiB (acima disso a conexão fecha com WebSocket status 1009, MESSAGE_TOO_LARGE)
Saída por comando 1 MiB
Saída por sessão 8 MiB
Comandos simultâneos 1
Fila servidor→cliente 32 frames
Consentimento 60 s
Execução 5 min
Ocioso 15 min
Vida da sessão 60 min
Ping / pong 20 s / 10 s

Um cliente que para de ler encerra a sessão com BACKPRESSURE — o servidor não enfileira sem limite. O heartbeat é liveness de transporte: um pong prova que o socket está vivo, não renova o relógio de ocioso.

Não há resume ("resume":"unsupported"). Só um cliente vivo por sessão — um segundo recebe SESSION_ALREADY_ACTIVE. Ao cair, o comando em voo é cancelado, nunca herdado; reconectar autentica de novo e cria uma sessão nova, sem reexecutar nada.

GETErros e fechamento#

json
{"type":"error","protocol":"remote-support.v1","request_id":"rsr_...",
 "code":"CONSENT_REQUIRED","message":"this command has not been consented to",
 "retryable":false}
json
{"type":"close","protocol":"remote-support.v1","code":"SESSION_EXPIRED",
 "message":"the session was idle for too long","retryable":true,
 "last_request_id":"rsr_..."}

Códigos: MESSAGE_MALFORMED, MESSAGE_TOO_LARGE, UNKNOWN_MESSAGE_TYPE, REQUEST_ID_REQUIRED, REQUEST_ID_UNKNOWN, HANDSHAKE_REQUIRED, HANDSHAKE_TIMEOUT, PROTOCOL_UNSUPPORTED, SESSION_NOT_AUTHORIZED, SESSION_ALREADY_ACTIVE, SESSION_NOT_FOUND, SESSION_EXPIRED, SESSION_CLOSED, CLIENT_NOT_READY, CONSENT_REQUIRED, CONSENT_DENIED, CONSENT_TIMEOUT, COMMAND_NOT_ALLOWED, COMMAND_ARGS_INVALID, COMMAND_LIMIT_EXCEEDED, COMMAND_TIMEOUT, COMMAND_CANCELLED, OUTPUT_LIMIT_EXCEEDED, BACKPRESSURE, AUDIT_UNAVAILABLE, REMOTE_SUPPORT_DISABLED, INTERNAL_ERROR. As mensagens são EN, estáveis e sanitizadas: nunca carregam token, segredo, argumento ou saída.

Cada sessão e cada comando ficam auditados com apenas metadados — conta, agente, sessão, request_id, identificador do comando, contagem de argumentos, resultado e exit code. Valores de argumento e stdout/stderr nunca entram em log nem na auditoria. Se a auditoria falhar, o comando é recusado com AUDIT_UNAVAILABLE em vez de executar fora do registro.