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": "Archivos",
"layout.mainTab.terminal": "Terminal",
"layout.mainTab.context": "Contexto",
"mobile.nav.aria": "Navegación móvil",
"mobile.nav.changes": "Cambios",
"mobile.nav.settings": "Ajustes",
"mobile.surface.closeAria": "Cerrar",
"mobile.header.openMenuAria": "Abrir menú",
"mobile.menu.titleAria": "Herramientas del espacio de trabajo",
"mobile.menu.files": "Archivos",
"mobile.menu.changes": "Cambios",
"mobile.menu.settings": "Ajustes",
"mobile.sessions.newChatCta": "Nuevo chat en {project}",
"mobile.sessions.dateGroup.today": "Hoy",
"mobile.sessions.dateGroup.yesterday": "Ayer",
"mobile.sessions.dateGroup.thisWeek": "Antes esta semana",
"mobile.sessions.dateGroup.older": "Más antiguos",
"mobile.sessions.section.worktrees": "Worktrees",
"mobile.sessions.section.otherProjects": "Cambiar de proyecto",
"mobile.sessions.section.projects": "Proyectos",
"mobile.sessions.empty.noProjectsTitle": "Sin proyectos",
"mobile.sessions.empty.noProjectsDescription": "Agrega un proyecto para empezar a chatear con tu código.",
"mobile.sessions.empty.noSessionsTitle": "Sin sesiones",
"mobile.sessions.empty.noSessionsDescription": "Inicia tu primer chat para verlo aquí.",
"mobile.sessions.empty.searchTitle": "Sin resultados",
"mobile.sessions.empty.searchDescription": "Prueba con otro término de búsqueda.",
"mobile.sessions.showArchived": "Mostrar archivadas ({count})",
"mobile.sessions.hideArchived": "Ocultar archivadas",
"mobile.sessions.activeWorktreeAria": "Worktree activo",
"mobile.sessions.activeProjectAria": "Proyecto activo",
"mobile.sessions.startNewChat": "Iniciar nuevo chat",
"mobile.sessions.newChat": "Nuevo chat",
"mobile.sessions.editOrder": "Reordenar proyectos",
"mobile.sessions.doneEditing": "Listo",
"mobile.sessions.editOrderHint": "Arrastra el asa o usa las flechas para reordenar los proyectos. Toca la marca para finalizar.",
"mobile.sessions.dragHandleAria": "Arrastra {label} para reordenar",
"mobile.sessions.moveUpAria": "Mover {label} arriba",
"mobile.sessions.moveDownAria": "Mover {label} abajo",
"mobile.sessions.removeProjectAria": "Eliminar {label}",
"mobile.sessions.cancelRemoveProjectAria": "Cancelar eliminación de {label}",
"mobile.sessions.confirmRemoveProject": "Eliminar",
"mobile.sessions.confirmRemoveProjectAria": "Confirmar eliminación de {label}",
"mobile.sessions.toast.projectRemoved": "Se eliminó {label}",
"mobile.sessions.showMore": "Mostrar {count} más",
"mobile.sessions.search.section.sessions": "Sesiones",
"mobile.sessions.search.section.archived": "Archivadas",
"mobile.sessions.search.section.projects": "Proyectos",
"mobile.sessions.clearSearchAria": "Limpiar búsqueda",
"mobile.header.noProject": "Selecciona un proyecto",
"mobile.header.activeSession": "Sesión activa",
"mobile.header.noSession": "No hay sesión activa",
"mobile.sessions.openSheetAria": "Abrir sesiones y proyectos",
"mobile.sessions.closeSheetAria": "Cerrar sesiones y proyectos",
"mobile.sessions.sheet.title": "Sesiones",
"mobile.sessions.sheet.description": "Cambia proyectos, abre sesiones o inicia un nuevo chat.",
"mobile.sessions.search.placeholder": "Buscar sesiones",
"mobile.sessions.empty": "No se encontraron sesiones.",
"mobile.sessions.unassignedProject": "Otras sesiones",
"mobile.sessions.newSessionAria": "Iniciar una nueva sesión en este proyecto",
"mobile.sessions.untitled": "Sesión sin título",
"mobile.sessions.project.sessionsSingle": "1 sesión",
"mobile.sessions.project.sessionsPlural": "{count} sesiones",
"mobile.files.refreshAria": "Actualizar archivos",
"mobile.files.backToParentAria": "Volver a {name}",
"mobile.files.rootDirectory": "Archivos del proyecto",
"mobile.files.search.placeholder": "Buscar archivos",
"mobile.files.search.empty": "No se encontraron archivos.",
"mobile.files.parentDirectory": "Directorio superior",
"mobile.files.empty.noDirectory": "Selecciona un proyecto para explorar archivos.",
"mobile.files.empty.directory": "Este directorio está vacío.",
"mobile.files.error.listFailed": "No se pudieron cargar los archivos",
"mobile.files.error.readUnavailable": "La vista previa del archivo no está disponible en este entorno.",
"mobile.files.file.truncated": "Vista previa del archivo truncada para móvil.",
"mobile.files.copyPathAria": "Copiar ruta del archivo",
"mobile.files.copyContent": "Copiar contenido",
"mobile.files.copyContentAria": "Copiar contenido del archivo",
"mobile.files.toast.pathCopied": "Ruta copiada",
"mobile.files.toast.contentCopied": "Contenido copiado",
"mobile.files.toast.copyFailed": "No se pudo copiar",
"mobile.changes.placeholder.title": "Cambios",
"mobile.changes.placeholder.description": "Aquí vivirán la revisión del árbol de trabajo, sync y commits.",
"mobile.changes.branchLabel": "Rama: {branch}",
"mobile.changes.noRemote": "No hay remoto disponible",
"mobile.changes.cleanDescription": "No hay archivos modificados en este workspace.",
"mobile.changes.diffDetail.subtitle": "Diff de solo lectura",
"mobile.changes.diffDetail.loadFailed": "No se pudo cargar el diff",
"mobile.changes.diffDetail.missingTitle": "El archivo ya no tiene cambios",
"mobile.changes.diffDetail.missingDescription": "Vuelve a Cambios y actualiza la lista.",
"mobile.changes.diffDetail.imageUnavailable": "Los diffs de imagen aún no están disponibles en Cambios móvil.",
"mobile.settings.placeholder.title": "Ajustes",
"mobile.settings.placeholder.description": "Aquí vivirán los ajustes móviles de conexión y app.",
"layout.rightSidebar.git": "Git",
"layout.rightSidebar.files": "Archivos",
"layout.rightSidebar.context": "Contexto",
@@ -1125,6 +1213,12 @@ export const dict: Record<I18nKey, string> = {
"header.services.refreshRateLimitsAria": "Actualizar límites de tasa",
"header.services.noRateLimits": "No hay límites de tasa disponibles.",
"header.services.noRateLimitsReported": "No se reportaron límites de tasa.",
"header.services.remoteUpdate.title": "Actualización de instancia remota",
"header.services.remoteUpdate.checking": "Buscando actualizaciones...",
"header.services.remoteUpdate.upToDate": "Esta instancia está actualizada.",
"header.services.remoteUpdate.available": "La versión {version} está disponible para esta instancia.",
"header.services.remoteUpdate.error": "No se pudieron comprobar las actualizaciones de la instancia remota",
"header.services.remoteUpdate.actions.open": "Actualizar",
"header.services.used": "Usado",
"header.services.remaining": "Restante",
"header.services.modelFamily.other": "Otro",
@@ -2035,6 +2129,9 @@ export const dict: Record<I18nKey, string> = {
"desktopHostSwitcher.header.currentDefaultColon": "Predeterminado actual:",
"desktopHostSwitcher.status.connected": "Conectado",
"desktopHostSwitcher.status.authRequired": "Autenticación requerida",
"desktopHostSwitcher.status.checking": "Comprobando",
"desktopHostSwitcher.status.updateRecommended": "Actualización recomendada",
"desktopHostSwitcher.status.incompatible": "Incompatible",
"desktopHostSwitcher.status.wrongService": "Servicio incorrecto",
"desktopHostSwitcher.status.unreachable": "Inalcanzable",
"desktopHostSwitcher.status.unknown": "Desconocido",
@@ -2221,6 +2318,8 @@ export const dict: Record<I18nKey, string> = {
"onboarding.remoteConnection.actions.chooseDifferentServer": "Elegir otro servidor",
"onboarding.remoteConnection.actions.useLocalInstead": "Usar Local en su lugar",
"onboarding.remoteConnection.probe.authMessage": "El servidor requiere autenticación. Puedes conectarte aún así, pero puede que tengas que proporcionar credenciales.",
"onboarding.remoteConnection.probe.updateRecommendedMessage": "Esta instancia ejecuta una versión diferente de OpenChamber. Puedes conectarte, pero actualiza ambas apps si algo no funciona.",
"onboarding.remoteConnection.probe.incompatibleMessage": "El servidor ejecuta OpenChamber pero no es compatible con esta versión de la app. Actualiza OpenChamber en el servidor y vuelve a intentarlo.",
"onboarding.remoteConnection.probe.wrongServiceMessage": "El servidor respondió pero no está ejecutando OpenChamber. Verifica que la dirección apunte a un servidor OpenChamber.",
"onboarding.remoteConnection.probe.unreachableMessage": "El servidor no está disponible. Revisa tu conexión de red y verifica la dirección del servidor.",
"onboarding.desktopRecovery.localUnavailable.title": "OpenCode Local no disponible",
@@ -2234,6 +2333,8 @@ export const dict: Record<I18nKey, string> = {
"onboarding.desktopRecovery.remoteUnreachable.retry": "Reintentar conexión",
"onboarding.desktopRecovery.incompatibleServer.title": "Servidor incompatible",
"onboarding.desktopRecovery.incompatibleServer.description": "El servidor en \"{host}\" no está ejecutando OpenChamber. Verifica que la dirección apunte a un servidor OpenChamber.",
"onboarding.desktopRecovery.remoteIncompatible.title": "Actualización del servidor requerida",
"onboarding.desktopRecovery.remoteIncompatible.description": "El servidor OpenChamber en \"{host}\" no es compatible con esta versión de la app. Actualiza OpenChamber en el servidor y vuelve a intentarlo.",
"onboarding.desktopRecovery.common.useLocal": "Usar Local",
"onboarding.desktopRecovery.common.useRemote": "Usar remoto",
"onboarding.desktopRecovery.actions.retrying": "Reintentando…",