Decouple bundled UI from runtime API and add remote instance tooling (#1228)

Add a packaged-client runtime boundary so the shared UI can talk to local,
desktop, remote, and VS Code runtimes through the right transport instead of
assuming one same-origin web server.

Centralize OpenChamber-owned API access behind RuntimeAPIs, runtimeFetch, and
runtime URL helpers, while keeping official OpenCode traffic on the SDK path.
Support runtime switching, remote host selection, desktop client credentials,
and headless connection links for pairing packaged clients with remote
OpenChamber servers.

Harden the new auth model by moving long-lived client tokens out of browser
URLs, introducing short-lived scoped URL tokens for browser-owned transports,
restricting URL-token access to explicit readable/realtime routes, and making
client-token management session-scoped or self-scoped as appropriate.

Update browser-owned assets and preview proxy flows to work with the split
runtime model, including authenticated project icons, preview token propagation,
CSP-safe preview bridge injection, and preview proxy auth that survives
short-lived URL-token expiry.

Tighten Electron security boundaries for packaged clients by gating privileged
preload state to trusted origins and requiring explicit confirmation before
connect deep-links import or switch remote runtimes.

Also refresh agent guidance and project skills so future runtime/API, auth,
preview, UI, CLI, settings, locale, and drag-to-reorder work follows the new
architecture.
This commit is contained in:
Bohdan Triapitsyn
2026-06-02 00:43:05 +03:00
committed by GitHub
parent a4314c189b
commit 2031e3b4a8
282 changed files with 16524 additions and 4259 deletions
+101
View File
@@ -27,6 +27,94 @@ export const dict: Record<I18nKey, string> = {
"layout.mainTab.files": "Arquivos",
"layout.mainTab.terminal": "Terminal",
"layout.mainTab.context": "Contexto",
"mobile.nav.aria": "Navegação móvel",
"mobile.nav.changes": "Alterações",
"mobile.nav.settings": "Configurações",
"mobile.surface.closeAria": "Fechar",
"mobile.header.openMenuAria": "Abrir menu",
"mobile.menu.titleAria": "Ferramentas do espaço de trabalho",
"mobile.menu.files": "Arquivos",
"mobile.menu.changes": "Alterações",
"mobile.menu.settings": "Configurações",
"mobile.sessions.newChatCta": "Novo chat em {project}",
"mobile.sessions.dateGroup.today": "Hoje",
"mobile.sessions.dateGroup.yesterday": "Ontem",
"mobile.sessions.dateGroup.thisWeek": "Início desta semana",
"mobile.sessions.dateGroup.older": "Mais antigos",
"mobile.sessions.section.worktrees": "Worktrees",
"mobile.sessions.section.otherProjects": "Trocar de projeto",
"mobile.sessions.section.projects": "Projetos",
"mobile.sessions.empty.noProjectsTitle": "Sem projetos",
"mobile.sessions.empty.noProjectsDescription": "Adicione um projeto para começar a conversar com seu código.",
"mobile.sessions.empty.noSessionsTitle": "Sem sessões",
"mobile.sessions.empty.noSessionsDescription": "Inicie seu primeiro chat para vê-lo aqui.",
"mobile.sessions.empty.searchTitle": "Sem resultados",
"mobile.sessions.empty.searchDescription": "Tente outro termo de busca.",
"mobile.sessions.showArchived": "Mostrar arquivadas ({count})",
"mobile.sessions.hideArchived": "Ocultar arquivadas",
"mobile.sessions.activeWorktreeAria": "Worktree ativa",
"mobile.sessions.activeProjectAria": "Projeto ativo",
"mobile.sessions.startNewChat": "Iniciar novo chat",
"mobile.sessions.newChat": "Novo chat",
"mobile.sessions.editOrder": "Reordenar projetos",
"mobile.sessions.doneEditing": "Concluído",
"mobile.sessions.editOrderHint": "Arraste a alça ou use as setas para reordenar os projetos. Toque na marca para finalizar.",
"mobile.sessions.dragHandleAria": "Arrastar {label} para reordenar",
"mobile.sessions.moveUpAria": "Mover {label} para cima",
"mobile.sessions.moveDownAria": "Mover {label} para baixo",
"mobile.sessions.removeProjectAria": "Remover {label}",
"mobile.sessions.cancelRemoveProjectAria": "Cancelar remoção de {label}",
"mobile.sessions.confirmRemoveProject": "Excluir",
"mobile.sessions.confirmRemoveProjectAria": "Confirmar remoção de {label}",
"mobile.sessions.toast.projectRemoved": "Removido {label}",
"mobile.sessions.showMore": "Mostrar mais {count}",
"mobile.sessions.search.section.sessions": "Sessões",
"mobile.sessions.search.section.archived": "Arquivadas",
"mobile.sessions.search.section.projects": "Projetos",
"mobile.sessions.clearSearchAria": "Limpar busca",
"mobile.header.noProject": "Selecione um projeto",
"mobile.header.activeSession": "Sessão ativa",
"mobile.header.noSession": "Nenhuma sessão ativa",
"mobile.sessions.openSheetAria": "Abrir sessões e projetos",
"mobile.sessions.closeSheetAria": "Fechar sessões e projetos",
"mobile.sessions.sheet.title": "Sessões",
"mobile.sessions.sheet.description": "Alterne projetos, abra sessões ou inicie um novo chat.",
"mobile.sessions.search.placeholder": "Pesquisar sessões",
"mobile.sessions.empty": "Nenhuma sessão encontrada.",
"mobile.sessions.unassignedProject": "Outras sessões",
"mobile.sessions.newSessionAria": "Iniciar uma nova sessão neste projeto",
"mobile.sessions.untitled": "Sessão sem título",
"mobile.sessions.project.sessionsSingle": "1 sessão",
"mobile.sessions.project.sessionsPlural": "{count} sessões",
"mobile.files.refreshAria": "Atualizar arquivos",
"mobile.files.backToParentAria": "Voltar para {name}",
"mobile.files.rootDirectory": "Arquivos do projeto",
"mobile.files.search.placeholder": "Buscar arquivos",
"mobile.files.search.empty": "Nenhum arquivo encontrado.",
"mobile.files.parentDirectory": "Diretório pai",
"mobile.files.empty.noDirectory": "Selecione um projeto para navegar pelos arquivos.",
"mobile.files.empty.directory": "Este diretório está vazio.",
"mobile.files.error.listFailed": "Falha ao carregar arquivos",
"mobile.files.error.readUnavailable": "A prévia do arquivo não está disponível neste runtime.",
"mobile.files.file.truncated": "Prévia do arquivo truncada para mobile.",
"mobile.files.copyPathAria": "Copiar caminho do arquivo",
"mobile.files.copyContent": "Copiar conteúdo",
"mobile.files.copyContentAria": "Copiar conteúdo do arquivo",
"mobile.files.toast.pathCopied": "Caminho copiado",
"mobile.files.toast.contentCopied": "Conteúdo copiado",
"mobile.files.toast.copyFailed": "Falha ao copiar",
"mobile.changes.placeholder.title": "Alterações",
"mobile.changes.placeholder.description": "Revisão da árvore de trabalho, sync e commits ficarão aqui.",
"mobile.changes.branchLabel": "Branch: {branch}",
"mobile.changes.noRemote": "Nenhum remote disponível",
"mobile.changes.cleanDescription": "Não há arquivos alterados neste workspace.",
"mobile.changes.diffDetail.subtitle": "Diff somente leitura",
"mobile.changes.diffDetail.loadFailed": "Falha ao carregar diff",
"mobile.changes.diffDetail.missingTitle": "O arquivo não está mais alterado",
"mobile.changes.diffDetail.missingDescription": "Volte para Alterações e atualize a lista.",
"mobile.changes.diffDetail.imageUnavailable": "Diffs de imagem ainda não estão disponíveis nas Alterações móveis.",
"mobile.settings.placeholder.title": "Configurações",
"mobile.settings.placeholder.description": "Configurações móveis de conexão e aplicativo ficarão aqui.",
"layout.rightSidebar.git": "Git",
"layout.rightSidebar.files": "Arquivos",
"layout.rightSidebar.context": "Contexto",
@@ -1125,6 +1213,12 @@ export const dict: Record<I18nKey, string> = {
"header.services.refreshRateLimitsAria": "Atualizar limites de taxa",
"header.services.noRateLimits": "Não há limites de taxa disponíveis.",
"header.services.noRateLimitsReported": "Nenhum limite de taxa foi informado.",
"header.services.remoteUpdate.title": "Atualização da instância remota",
"header.services.remoteUpdate.checking": "Procurando atualizações...",
"header.services.remoteUpdate.upToDate": "Esta instância está atualizada.",
"header.services.remoteUpdate.available": "A versão {version} está disponível para esta instância.",
"header.services.remoteUpdate.error": "Não foi possível verificar atualizações da instância remota",
"header.services.remoteUpdate.actions.open": "Atualizar",
"header.services.used": "Usado",
"header.services.remaining": "Restante",
"header.services.modelFamily.other": "Outro",
@@ -2035,6 +2129,9 @@ export const dict: Record<I18nKey, string> = {
"desktopHostSwitcher.header.currentDefaultColon": "Padrão atual:",
"desktopHostSwitcher.status.connected": "Conectado",
"desktopHostSwitcher.status.authRequired": "Autenticação obrigatória",
"desktopHostSwitcher.status.checking": "Verificando",
"desktopHostSwitcher.status.updateRecommended": "Atualização recomendada",
"desktopHostSwitcher.status.incompatible": "Incompatível",
"desktopHostSwitcher.status.wrongService": "Serviço incorreto",
"desktopHostSwitcher.status.unreachable": "Inacessível",
"desktopHostSwitcher.status.unknown": "Desconhecido",
@@ -2221,6 +2318,8 @@ export const dict: Record<I18nKey, string> = {
"onboarding.remoteConnection.actions.chooseDifferentServer": "Escolher outro servidor",
"onboarding.remoteConnection.actions.useLocalInstead": "Usar Local no lugar",
"onboarding.remoteConnection.probe.authMessage": "O servidor exige autenticação. Você ainda pode se conectar, mas talvez precise fornecer credenciais.",
"onboarding.remoteConnection.probe.updateRecommendedMessage": "Esta instância está executando uma versão diferente do OpenChamber. Você pode se conectar, mas atualize os dois apps se algo não funcionar.",
"onboarding.remoteConnection.probe.incompatibleMessage": "O servidor está executando OpenChamber, mas não é compatível com esta versão do app. Atualize o OpenChamber no servidor e tente novamente.",
"onboarding.remoteConnection.probe.wrongServiceMessage": "O servidor respondeu, mas não está executando OpenChamber. Verifique se o endereço aponta para um servidor OpenChamber.",
"onboarding.remoteConnection.probe.unreachableMessage": "O servidor não está disponível. Revise sua conexão de rede e verifique o endereço do servidor.",
"onboarding.desktopRecovery.localUnavailable.title": "OpenCode Local não disponível",
@@ -2234,6 +2333,8 @@ export const dict: Record<I18nKey, string> = {
"onboarding.desktopRecovery.remoteUnreachable.retry": "Tentar novamente conexão",
"onboarding.desktopRecovery.incompatibleServer.title": "Servidor incompatível",
"onboarding.desktopRecovery.incompatibleServer.description": "O servidor em \"{host}\" não está executando OpenChamber. Verifique se o endereço aponta para um servidor OpenChamber.",
"onboarding.desktopRecovery.remoteIncompatible.title": "Atualização do servidor necessária",
"onboarding.desktopRecovery.remoteIncompatible.description": "O servidor OpenChamber em \"{host}\" não é compatível com esta versão do app. Atualize o OpenChamber no servidor e tente novamente.",
"onboarding.desktopRecovery.common.useLocal": "Usar Local",
"onboarding.desktopRecovery.common.useRemote": "Usar remoto",
"onboarding.desktopRecovery.actions.retrying": "Retentendo…",