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': '파일',
'layout.mainTab.terminal': '터미널',
'layout.mainTab.context': '컨텍스트',
'mobile.nav.aria': '모바일 내비게이션',
'mobile.nav.changes': '변경사항',
'mobile.nav.settings': '설정',
'mobile.surface.closeAria': '닫기',
'mobile.header.openMenuAria': '메뉴 열기',
'mobile.menu.titleAria': '작업 공간 도구',
'mobile.menu.files': '파일',
'mobile.menu.changes': '변경사항',
'mobile.menu.settings': '설정',
'mobile.sessions.newChatCta': '{project}에서 새 채팅',
'mobile.sessions.dateGroup.today': '오늘',
'mobile.sessions.dateGroup.yesterday': '어제',
'mobile.sessions.dateGroup.thisWeek': '이번 주 초',
'mobile.sessions.dateGroup.older': '이전',
'mobile.sessions.section.worktrees': '워크트리',
'mobile.sessions.section.otherProjects': '프로젝트 전환',
'mobile.sessions.section.projects': '프로젝트',
'mobile.sessions.empty.noProjectsTitle': '프로젝트 없음',
'mobile.sessions.empty.noProjectsDescription': '코드와 채팅을 시작하려면 프로젝트를 추가하세요.',
'mobile.sessions.empty.noSessionsTitle': '세션 없음',
'mobile.sessions.empty.noSessionsDescription': '여기에 표시하려면 첫 번째 채팅을 시작하세요.',
'mobile.sessions.empty.searchTitle': '결과 없음',
'mobile.sessions.empty.searchDescription': '다른 검색어를 시도해 보세요.',
'mobile.sessions.showArchived': '보관된 항목 표시 ({count})',
'mobile.sessions.hideArchived': '보관된 항목 숨기기',
'mobile.sessions.activeWorktreeAria': '활성 워크트리',
'mobile.sessions.activeProjectAria': '활성 프로젝트',
'mobile.sessions.startNewChat': '새 채팅 시작',
'mobile.sessions.newChat': '새 채팅',
'mobile.sessions.editOrder': '프로젝트 순서 변경',
'mobile.sessions.doneEditing': '완료',
'mobile.sessions.editOrderHint': '핸들을 드래그하거나 화살표로 프로젝트 순서를 변경하세요. 완료하려면 체크를 누르세요.',
'mobile.sessions.dragHandleAria': '{label} 드래그하여 순서 변경',
'mobile.sessions.moveUpAria': '{label} 위로 이동',
'mobile.sessions.moveDownAria': '{label} 아래로 이동',
'mobile.sessions.removeProjectAria': '{label} 제거',
'mobile.sessions.cancelRemoveProjectAria': '{label} 제거 취소',
'mobile.sessions.confirmRemoveProject': '삭제',
'mobile.sessions.confirmRemoveProjectAria': '{label} 제거 확인',
'mobile.sessions.toast.projectRemoved': '{label} 제거됨',
'mobile.sessions.showMore': '{count}개 더 보기',
'mobile.sessions.search.section.sessions': '세션',
'mobile.sessions.search.section.archived': '보관됨',
'mobile.sessions.search.section.projects': '프로젝트',
'mobile.sessions.clearSearchAria': '검색 지우기',
'mobile.header.noProject': '프로젝트 선택',
'mobile.header.activeSession': '활성 세션',
'mobile.header.noSession': '활성 세션 없음',
'mobile.sessions.openSheetAria': '세션 및 프로젝트 열기',
'mobile.sessions.closeSheetAria': '세션 및 프로젝트 닫기',
'mobile.sessions.sheet.title': '세션',
'mobile.sessions.sheet.description': '프로젝트를 전환하고 세션을 열거나 새 채팅을 시작하세요.',
'mobile.sessions.search.placeholder': '세션 검색',
'mobile.sessions.empty': '세션을 찾을 수 없습니다.',
'mobile.sessions.unassignedProject': '기타 세션',
'mobile.sessions.newSessionAria': '이 프로젝트에서 새 세션 시작',
'mobile.sessions.untitled': '제목 없는 세션',
'mobile.sessions.project.sessionsSingle': '세션 1개',
'mobile.sessions.project.sessionsPlural': '세션 {count}개',
'mobile.files.refreshAria': '파일 새로고침',
'mobile.files.backToParentAria': '{name}(으)로 돌아가기',
'mobile.files.rootDirectory': '프로젝트 파일',
'mobile.files.search.placeholder': '파일 검색',
'mobile.files.search.empty': '파일을 찾을 수 없습니다.',
'mobile.files.parentDirectory': '상위 디렉터리',
'mobile.files.empty.noDirectory': '파일을 탐색할 프로젝트를 선택하세요.',
'mobile.files.empty.directory': '이 디렉터리는 비어 있습니다.',
'mobile.files.error.listFailed': '파일을 불러오지 못했습니다',
'mobile.files.error.readUnavailable': '이 런타임에서는 파일 미리보기를 사용할 수 없습니다.',
'mobile.files.file.truncated': '모바일용 파일 미리보기가 잘렸습니다.',
'mobile.files.copyPathAria': '파일 경로 복사',
'mobile.files.copyContent': '내용 복사',
'mobile.files.copyContentAria': '파일 내용 복사',
'mobile.files.toast.pathCopied': '경로가 복사되었습니다',
'mobile.files.toast.contentCopied': '내용이 복사되었습니다',
'mobile.files.toast.copyFailed': '복사하지 못했습니다',
'mobile.changes.placeholder.title': '변경사항',
'mobile.changes.placeholder.description': '작업 트리 검토, sync, commit 작업이 여기에 표시됩니다.',
'mobile.changes.branchLabel': '브랜치: {branch}',
'mobile.changes.noRemote': '사용 가능한 remote가 없습니다',
'mobile.changes.cleanDescription': '이 workspace에는 변경된 파일이 없습니다.',
'mobile.changes.diffDetail.subtitle': '읽기 전용 diff',
'mobile.changes.diffDetail.loadFailed': 'diff를 불러오지 못했습니다',
'mobile.changes.diffDetail.missingTitle': '파일이 더 이상 변경되지 않았습니다',
'mobile.changes.diffDetail.missingDescription': '변경사항으로 돌아가 목록을 새로고침하세요.',
'mobile.changes.diffDetail.imageUnavailable': '이미지 diff는 아직 모바일 변경사항에서 사용할 수 없습니다.',
'mobile.settings.placeholder.title': '설정',
'mobile.settings.placeholder.description': '모바일 연결 및 앱 설정이 여기에 표시됩니다.',
'layout.rightSidebar.git': 'Git',
'layout.rightSidebar.files': '파일',
'layout.rightSidebar.context': '컨텍스트',
@@ -1162,6 +1250,12 @@ export const dict: Record<I18nKey, string> = {
'header.services.refreshRateLimitsAria': '레이트 리밋 새로고침',
'header.services.noRateLimits': '사용 가능한 레이트 리밋이 없습니다.',
'header.services.noRateLimitsReported': '보고된 레이트 리밋이 없습니다.',
'header.services.remoteUpdate.title': '원격 인스턴스 업데이트',
'header.services.remoteUpdate.checking': '업데이트를 확인하는 중...',
'header.services.remoteUpdate.upToDate': '이 인스턴스는 최신 상태입니다.',
'header.services.remoteUpdate.available': '이 인스턴스에 버전 {version} 업데이트가 있습니다.',
'header.services.remoteUpdate.error': '원격 인스턴스 업데이트를 확인하지 못했습니다',
'header.services.remoteUpdate.actions.open': '업데이트',
'header.services.used': '사용됨',
'header.services.remaining': '남은 양',
'header.services.modelFamily.other': '기타',
@@ -2069,6 +2163,9 @@ export const dict: Record<I18nKey, string> = {
'desktopHostSwitcher.header.currentDefaultColon': '현재 기본값:',
'desktopHostSwitcher.status.connected': '연결됨',
'desktopHostSwitcher.status.authRequired': '인증 필요',
'desktopHostSwitcher.status.checking': '확인 중',
'desktopHostSwitcher.status.updateRecommended': '업데이트 권장',
'desktopHostSwitcher.status.incompatible': '호환되지 않음',
'desktopHostSwitcher.status.wrongService': '잘못된 서비스',
'desktopHostSwitcher.status.unreachable': '연결할 수 없음',
'desktopHostSwitcher.status.unknown': '알 수 없음',
@@ -2255,6 +2352,8 @@ export const dict: Record<I18nKey, string> = {
'onboarding.remoteConnection.actions.chooseDifferentServer': '다른 서버 선택',
'onboarding.remoteConnection.actions.useLocalInstead': '대신 로컬 사용',
'onboarding.remoteConnection.probe.authMessage': '서버에 인증이 필요합니다. 연결은 가능하지만 자격 증명을 입력해야 할 수 있습니다.',
'onboarding.remoteConnection.probe.updateRecommendedMessage': '이 인스턴스는 다른 OpenChamber 버전을 실행 중입니다. 연결할 수 있지만 문제가 있으면 양쪽 앱을 업데이트하세요.',
'onboarding.remoteConnection.probe.incompatibleMessage': '서버에서 OpenChamber가 실행 중이지만 이 앱 버전과 호환되지 않습니다. 서버의 OpenChamber를 업데이트한 후 다시 시도하세요.',
'onboarding.remoteConnection.probe.wrongServiceMessage': '서버가 응답했지만 OpenChamber가 실행 중이 아닙니다. 주소가 OpenChamber 서버를 가리키는지 확인하세요.',
'onboarding.remoteConnection.probe.unreachableMessage': '서버에 연결할 수 없습니다. 네트워크 연결과 서버 주소를 확인하세요.',
'onboarding.desktopRecovery.localUnavailable.title': '로컬 OpenCode를 사용할 수 없음',
@@ -2268,6 +2367,8 @@ export const dict: Record<I18nKey, string> = {
'onboarding.desktopRecovery.remoteUnreachable.retry': '연결 다시 시도',
'onboarding.desktopRecovery.incompatibleServer.title': '호환되지 않는 서버',
'onboarding.desktopRecovery.incompatibleServer.description': '"{host}"의 서버에서 OpenChamber가 실행 중이 아닙니다. 주소가 OpenChamber 서버를 가리키는지 확인하세요.',
'onboarding.desktopRecovery.remoteIncompatible.title': '서버 업데이트 필요',
'onboarding.desktopRecovery.remoteIncompatible.description': '"{host}"의 OpenChamber 서버가 이 앱 버전과 호환되지 않습니다. 서버의 OpenChamber를 업데이트한 후 다시 시도하세요.',
'onboarding.desktopRecovery.common.useLocal': '로컬 사용',
'onboarding.desktopRecovery.common.useRemote': '원격 사용',
'onboarding.desktopRecovery.actions.retrying': '다시 시도 중…',