A professional workspace for AI coding agents with: - Real-time streaming chat with SSE - Session management (fork, clone, tree navigation) - MCP tools browser & metadata panel - Skills browser (global, project, package sources) - Voice input & STT provider configuration - Integrated terminal & file explorer - Model selector with capability metadata - Keyboard-first UI with command palette - 8 languages (en, pt-BR, es, ja, zh-CN, ar, bn, hi) - Tauri 2 desktop app - React 19 + TypeScript + Vite 8 + Tailwind CSS 4 - Pi Agent extension runtime Originally forked from OpenCodeUI, now an independent project. License: GPL-3.0-only
19 KiB
Spec — STT, acesso LAN/QR, sessões reais e histórico por projeto
Data: 2026-05-30
Status: proposta técnica pronta para revisão do usuário antes de implementação
Produto: PiAgentUI
Escopo: implementar, com mudanças mínimas e TDD, quatro melhorias conectadas ao uso real do PiAgentUI:
- sistema de áudio/STT no composer;
- exposição controlada do PiAgentUI na rede local com IP e QR Code;
- criação de novas sessões que realmente criam novos chats no Pi Agent;
- histórico lateral agrupado por pastas/projetos, expansível e recolhível.
Contexto observado no projeto
Arquitetura atual relevante:
- Backend local da UI fica em
extensions/piagentui-server.ts. - Núcleo de helpers seguros fica em
extensions/piagentui-server-core.ts. - O servidor HTTP atualmente faz bind em
127.0.0.1e validaHost/Origincomo loopback-only. serveIndexHtml()injetawindow.PI_BOOT_DATAcombaseUrl: http://127.0.0.1:<port>e bearer token local.- O discovery local é salvo em
~/.pi/agent/piagentui-port.jsoncom{ port, token }. POST /api/sessionsatualmente não cria sessão real: ele apenas retornagetSessionInfo(getCurrentSessionId()).GET /api/sessionslê arquivos.jsonldo diretório de sessões e deriva metadados viareadSessionMetadata().readSessionMetadata()já extraiheader.cwd, título porsession_infoou primeira mensagem de usuário, timestamps e contagem básica.sendUserMessageToRequestedSession()já consegue trocar rapidamente para uma sessão solicitada vialatestCtx.switchSession(sessionFile, { withSession })e enviar mensagem nela.- A UI de sessões está em
src/features/sessions/SessionList.tsx. - O estado de sessões vem de
src/contexts/SessionContext.tsxesrc/api/session.ts. - O input/composer está em
src/features/chat/InputBox.tsxesrc/features/chat/input/InputToolbar.tsx. - Já existem anexos multimodais, incluindo áudio como arquivo quando o modelo suporta.
- A store de servidores fica em
src/store/serverStore.tse já suporta múltiplos backends, bearer token, Basic Auth e health checks.
Objetivos
Objetivo 1 — STT no composer
Adicionar entrada por voz sem substituir o fluxo de texto:
- botão de microfone no composer;
- gravação de áudio pelo navegador/Tauri webview;
- transcrição por provider configurável;
- inserção do texto transcrito no input;
- fallback por arquivo via OpenAI-compatible
/audio/transcriptions; - arquitetura preparada para realtime OpenAI depois, sem bloquear o MVP.
Objetivo 2 — acesso LAN com QR Code
Permitir que o usuário acesse o PiAgentUI por outro dispositivo na mesma rede local:
- manter acesso LAN desligado por padrão;
- quando ligado, mostrar IP local do computador, URL de acesso e QR Code;
- não remover autenticação existente;
- não expor sem aviso que o controle do agente fica disponível na rede local;
- manter loopback funcionando normalmente.
Objetivo 3 — novas sessões reais no Pi Agent
Corrigir o comportamento de “novo chat”:
New chatdeve criar um novo arquivo de sessão Pi.jsonl;- deve alternar rapidamente para essa sessão real;
- enviar a primeira mensagem nessa sessão deve persistir no novo arquivo, não apenas simular um chat novo na UI;
- o retorno de
POST /api/sessionsdeve conter o id real, diretório real e metadata real.
Objetivo 4 — histórico por pastas/projetos
Agrupar sessões no histórico lateral por projeto:
- cada pasta representa um
cwd/diretório de projeto vindo do header da sessão; - nome visual da pasta = basename do diretório, com fallback legível;
- pastas podem expandir/recolher;
- sessões ficam dentro da pasta do projeto;
- busca continua funcionando;
- seleção e drag/drop de sessão continuam funcionando;
- sem reorganizar o backend inteiro de sessões.
Não objetivos
- Não implementar TTS/voz falada nesta fase.
- Não criar sistema multiusuário, login ou permissões finas.
- Não abrir o servidor LAN por padrão.
- Não publicar automaticamente a extensão.
- Não renomear todo o legado
opencode/OpenCodeUI agora. - Não refatorar
SessionList.tsxinteiro além do necessário para agrupamento por projeto. - Não alterar arquitetura de pane layout, terminal, file explorer ou MCP além dos pontos necessários.
Decisões recomendadas
A. STT em duas fases, mas com base única
Recomendado: implementar primeiro file transcription, com tipos e UI já preparados para realtime.
Motivo:
- é mais rápido e testável;
- funciona com OpenAI e provedores OpenAI-compatible;
- evita acoplar a primeira entrega a WebSocket realtime;
- mantém o caminho aberto para
gpt-realtime-whisper.
Configuração inicial:
export type SttMode = 'file' | 'realtime'
export type SttProviderKind = 'openai' | 'openai-compatible'
export interface SttSettings {
enabled: boolean
providerKind: SttProviderKind
mode: SttMode
baseUrl: string
apiKeyConfigured: boolean
transcriptionEndpoint: string
transcriptionModel: string
language?: string
insertMode: 'append' | 'replace'
}
O segredo real da API key não deve ser retornado ao frontend depois de salvo. A UI recebe apenas apiKeyConfigured: true/false.
B. Settings STT server-side local
Recomendado: salvar configuração STT em arquivo local do usuário:
~/.pi/agent/piagentui-settings.json
Estrutura mínima:
{
"stt": {
"enabled": true,
"providerKind": "openai-compatible",
"mode": "file",
"baseUrl": "https://api.openai.com/v1",
"apiKey": "...",
"transcriptionEndpoint": "/audio/transcriptions",
"transcriptionModel": "gpt-4o-mini-transcribe",
"language": "pt",
"insertMode": "append"
},
"network": {
"lanAccessEnabled": false
}
}
Motivo:
- evita persistir API key no localStorage do browser;
- permite LAN sem expor secrets no
window.PI_BOOT_DATA; - mantém tudo local e simples;
- evita banco de dados ou integração de credenciais complexa.
C. LAN por bind amplo + validação dinâmica
Recomendado: o servidor pode escutar em 0.0.0.0, mas responder a clientes não-loopback apenas quando network.lanAccessEnabled === true.
Mudanças necessárias:
listenOnAvailablePort(server, DEFAULT_PORT, '0.0.0.0');- substituir validação loopback-only por função configurável:
- loopback sempre permitido;
- LAN permitido apenas se
lanAccessEnabled; Origindeve bater com host loopback ou LAN permitido;
serveIndexHtml(req, res)deve montarbaseUrla partir dereq.headers.host, não sempre127.0.0.1;- endpoint
GET /api/network/accessretorna estado, IPs e URLs; - endpoint
POST /api/network/accessliga/desliga LAN.
Acesso LAN expõe controle do agente para quem estiver na rede e conseguir acessar a URL. A UI deve mostrar warning explícito.
D. QR Code no frontend
Recomendado: gerar o QR Code no frontend a partir da URL LAN ativa.
Opções:
- usar dependência pequena
qrcode; - implementar QR manualmente;
- usar serviço externo.
Decisão: usar qrcode se não houver lib existente. É a solução mais simples, testável e offline. Evitar serviço externo por privacidade.
E. Criar sessão real sem mexer no core do Pi
O contexto regular da extensão tem switchSession, mas newSession() fica restrito ao command context do Pi. Para não mexer no core do Pi agora, o backend do PiAgentUI pode criar o arquivo .jsonl compatível com SessionManager.newSession() e depois chamar latestCtx.switchSession(newSessionFile, { withSession }).
Header da sessão compatível:
{
"type": "session",
"version": 3,
"id": "<novo-id>",
"timestamp": "<iso-date>",
"cwd": "<diretorio-do-projeto>",
"parentSession": "<opcional>"
}
Fluxo:
- receber
POST /api/sessionscom{ directory?, title?, parentID? }; - resolver
cwd = directory || latestCtx.cwd || process.cwd(); - gerar id válido e filename com timestamp;
- escrever arquivo
.jsonlcom header; - se
titleexistir, opcionalmente adicionar entrysession_infocompatível com o parser atual; - trocar rapidamente para o novo arquivo com
latestCtx.switchSession(); - atualizar
latestCtxnowithSession; - retornar metadata real lida de
readSessionMetadata().
Se switchSession não estiver disponível, retornar 501 em vez de fingir sucesso.
F. Histórico por projeto no frontend
Recomendado: agrupar no frontend usando session.directory já retornado pelo backend.
Criar helper puro:
src/features/sessions/sessionProjectGroups.ts
Responsabilidades:
- normalizar paths Windows/Linux;
- extrair nome do projeto pelo basename;
- criar chave estável por diretório normalizado;
- ordenar grupos por
max(session.time.updated)desc; - ordenar sessões dentro do grupo por
time.updateddesc; - manter busca como modo flat ou grupos filtrados.
A UI pode adicionar modo groupedByProject?: boolean em SessionList, preservando o agrupamento temporal antigo se necessário.
Estado de expansão:
- store local leve em
localStorage, por exemplopiagentui-session-project-folders; - padrão: pasta do projeto atual expandida; demais recolhidas;
- se busca ativa, expandir todos os grupos com match.
Endpoints propostos
STT
GET /api/settings/stt
Retorna settings redigidos:
{
"enabled": true,
"providerKind": "openai-compatible",
"mode": "file",
"baseUrl": "https://api.openai.com/v1",
"apiKeyConfigured": true,
"transcriptionEndpoint": "/audio/transcriptions",
"transcriptionModel": "gpt-4o-mini-transcribe",
"language": "pt",
"insertMode": "append"
}
POST /api/settings/stt
Atualiza settings. Se apiKey vier vazia/ausente, mantém chave anterior; se vier null, remove.
POST /api/stt/transcriptions
Recebe áudio como multipart/form-data, chama provider configurado e retorna:
{
"text": "texto transcrito",
"durationMs": 1234,
"provider": "openai-compatible",
"model": "gpt-4o-mini-transcribe"
}
LAN/QR
GET /api/network/access
{
"lanAccessEnabled": false,
"port": 58785,
"localUrl": "http://127.0.0.1:58785",
"lanUrls": ["http://192.168.0.10:58785"],
"primaryLanUrl": "http://192.168.0.10:58785"
}
POST /api/network/access
{ "lanAccessEnabled": true }
Retorna o mesmo shape do GET.
Sessões
POST /api/sessions
Novo comportamento: cria sessão real.
Body:
{
"directory": "D:/Documents/Github/PiAgentUI/PiAgentUi",
"title": "Nova conversa",
"parentID": "opcional"
}
Response:
{
"id": "019...",
"title": "Nova conversa",
"directory": "D:/Documents/Github/PiAgentUI/PiAgentUi",
"time": { "created": 123, "updated": 123 },
"status": { "type": "idle" },
"summary": { "deletions": 0, "files": 0, "additions": 0 }
}
Componentes/frontend propostos
STT
Criar:
src/api/stt.ts— chamadas de settings e transcrição;src/types/api/stt.ts— tipos de settings/resposta;src/hooks/useVoiceTranscription.ts— MediaRecorder, estados e envio para backend;src/features/chat/input/VoiceInputButton.tsx— botão mic/stop/loading/error;- alterações pontuais em
InputBox.tsxeInputToolbar.tsxpara inserir texto transcrito.
Estados mínimos:
type VoiceState =
| 'idle'
| 'requesting-permission'
| 'recording'
| 'transcribing'
| 'error'
LAN/QR
Criar:
src/api/network.ts;src/types/api/network.ts;src/features/settings/components/NetworkAccessSettings.tsxou adicionar seção pequena emServersSettings.tsx;- QR gerado a partir de
primaryLanUrl.
Como o app já tem ServersSettings.tsx, a mudança mínima é adicionar uma seção “Local network access” dentro dele, sem criar novo painel de settings se não for necessário.
Sessões por projeto
Criar:
src/features/sessions/sessionProjectGroups.ts;- testes unitários para grouping;
- alterar
SessionList.tsxpara renderizar project folders quandogroupedByProjectestiver ativo; - adicionar traduções apenas nos namespaces já usados (
chat/commands/common) conforme necessário.
Segurança
- LAN fica desligado por padrão.
- Loopback continua sempre permitido.
- API continua exigindo bearer token.
- O HTML servido injeta o token apenas para o cliente que abriu a UI; isso significa que qualquer pessoa com acesso à URL LAN durante LAN enabled pode controlar o agente. A UI deve deixar isso claro.
- STT API key não deve aparecer em resposta de settings.
- Não logar API key.
- Não aceitar
baseUrlsemhttp:ouhttps:. - Limitar upload STT por tamanho. Sugestão inicial: 25 MB.
- Reusar
isRequestBodySizeAllowedou criar limite específico para multipart. - Não permitir path traversal ou file reads novos.
Plano de testes obrigatório
Backend extension
Arquivo: extensions/piagentui-server.test.ts
Casos:
POST /api/sessionscria arquivo.jsonlcom headertype=session,version=3,cwdcorreto e id novo.POST /api/sessionschamaswitchSession()comwithSessione retorna metadata da sessão nova.POST /api/sessionsretorna501quandoswitchSessionindisponível, sem fingir sucesso.GET /api/network/accessretorna loopback URL e lista de IPs LAN mockável.- LAN disabled rejeita host não-loopback.
- LAN enabled aceita host LAN com bearer token.
serveIndexHtml()injetabaseUrlbaseado no host da request.GET /api/settings/sttredigeapiKey.POST /api/settings/sttsalva settings e preserva/remover chave conforme contrato.POST /api/stt/transcriptionsvalida provider config e trata erro do provider sem vazar segredo.
Arquivo: extensions/piagentui-server-core.test.ts
Casos:
isLoopbackHost()preserva comportamento atual.- nova validação aceita LAN apenas quando enabled.
getLocalNetworkUrls()ignora loopback, IPv6 local problemático e interfaces internas inválidas.- validação de URL STT recusa protocolos não HTTP(S).
Frontend API/hooks
Arquivos novos:
src/api/stt.test.ts;src/api/network.test.ts;src/hooks/useVoiceTranscription.test.tsx;src/features/sessions/sessionProjectGroups.test.ts.
Casos:
- API STT envia/recebe shape correto.
- API network chama endpoints corretos.
- hook de voz passa por
idle -> recording -> transcribing -> idlee chama callback com texto final. - hook trata permissão negada.
- grouping por projeto cria pastas corretas para paths Windows e POSIX.
- grouping ordena pastas por sessão mais recente.
- grouping mantém sessões ordenadas por update.
Componentes
Arquivos prováveis:
src/features/chat/input/VoiceInputButton.test.tsx;src/features/settings/components/ServersSettings.test.tsxou novo test deNetworkAccessSettings;src/features/sessions/SessionList.test.tsx.
Casos:
- botão de microfone mostra estados e dispara start/stop.
- seção LAN mostra URL e QR quando enabled.
- SessionList renderiza pastas por projeto, expande/recolhe e preserva seleção.
Ordem de implementação recomendada
- Sessões reais primeiro — corrige fundação crítica de chat.
- Agrupamento por projeto — depende de sessões reais/metadata confiável.
- LAN/QR — isolado, mas tem implicações de segurança.
- STT file transcription — maior superfície; deve entrar depois da base de rede/sessão estar sólida.
- Realtime STT — fase seguinte, usando o contrato já criado.
Critérios de aceite
Sessões reais
- Clicar em New chat cria um novo arquivo
.jsonlde sessão Pi. - O id retornado pelo backend é diferente da sessão anterior.
- Enviar mensagem após criar novo chat persiste nessa nova sessão.
- Troca de sessão continua rápida e não perde SSE.
Histórico por projeto
- Sidebar mostra pastas por projeto.
- Cada pasta contém sessões daquele
cwd. - Expand/recolher funciona.
- Busca ainda localiza sessões.
- Selecionar sessão dentro da pasta navega para o chat correto.
LAN/QR
- Com LAN off, acesso por IP LAN recebe 403.
- Com LAN on, URL LAN abre a UI em outro dispositivo da rede.
- QR Code aponta para a URL LAN ativa.
- API continua exigindo token.
- UI mostra aviso de segurança.
STT
- Usuário consegue gravar áudio e transcrever para texto.
- Texto final entra no composer sem auto-send.
- Provider OpenAI-compatible é configurável.
- API key não aparece em respostas/logs.
- Erros de permissão/rede/provider são visíveis e recuperáveis.
Riscos e mitigação
| Risco | Mitigação |
|---|---|
| LAN expõe controle total do agente | default off, warning explícito, bearer obrigatório, permitir desligar rápido |
| Criar sessão manualmente diverge do Pi core | usar header exatamente compatível com SessionManager.newSession(); testes contra parser atual |
switchSession indisponível em runtime antigo |
retornar 501, não simular sucesso |
| QR exige dependência nova | usar qrcode pequena e isolada; se rejeitado, renderizar URL primeiro e QR depois |
| STT com CORS se chamado direto do frontend | chamadas a providers sempre pelo backend local |
| API key em LAN | armazenar server-side local, responder apenas apiKeyConfigured |
SessionList.tsx já é grande |
extrair grouping puro para arquivo separado; modificar renderização o mínimo possível |
Arquivos previstos
Criar
src/api/stt.tssrc/api/stt.test.tssrc/types/api/stt.tssrc/api/network.tssrc/api/network.test.tssrc/types/api/network.tssrc/hooks/useVoiceTranscription.tssrc/hooks/useVoiceTranscription.test.tsxsrc/features/chat/input/VoiceInputButton.tsxsrc/features/chat/input/VoiceInputButton.test.tsxsrc/features/sessions/sessionProjectGroups.tssrc/features/sessions/sessionProjectGroups.test.ts
Modificar
extensions/piagentui-server.tsextensions/piagentui-server.test.tsextensions/piagentui-server-core.tsextensions/piagentui-server-core.test.tssrc/features/chat/InputBox.tsxsrc/features/chat/input/InputToolbar.tsxsrc/features/sessions/SessionList.tsxsrc/features/sessions/SessionList.test.tsxsrc/features/settings/components/ServersSettings.tsxsrc/store/serverStore.tsapenas se necessário para refletir LAN URL ativasrc/locales/en/*.jsonesrc/locales/zh-CN/*.jsonpara labels mínimospackage.jsonse a dependênciaqrcodefor adotada
Abordagens consideradas
Abordagem 1 — implementar tudo no frontend
Rejeitada. STT direto do browser expõe API keys, sofre com CORS e não combina com LAN.
Abordagem 2 — mexer no core do Pi para expor newSession ao extension context
Possível, mas maior. Já fizemos patch de switchSession; para este pacote o objetivo é mudança mínima no PiAgentUI. Só considerar se criação manual de sessão se mostrar incompatível.
Abordagem 3 — backend PiAgentUI como orquestrador local
Recomendada. Mantém mudanças dentro do PiAgentUI, usa switchSession já disponível, preserva segurança local e permite testes focados.
Gate de implementação
Esta spec cobre múltiplos subsistemas e altera segurança/rede/sessão. A implementação deve começar somente após aprovação explícita do usuário sobre este design.
Após aprovação, criar plano em:
docs/superpowers/plans/2026-05-30-piagentui-stt-lan-real-sessions-project-folders.md
O plano deve usar TDD e commits pequenos por feature:
- sessões reais;
- agrupamento por projeto;
- LAN/QR;
- STT settings + file transcription;
- voice UI;
- validação final e revisão de código.