From e0cabc6f4f1d2965bc983b4ee88daea0c1799789 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Tue, 27 Oct 2026 13:59:01 +0000 Subject: [PATCH 1/4] feat(cloud): use Codex broker for auth recovery Agent-Session: cloud-setup-auth-error-codex-broker --- .../CmuxNextApp/Cloud/CloudPresenter.swift | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudPresenter.swift b/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudPresenter.swift index 970493cd07c7..24722c872fff 100644 --- a/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudPresenter.swift +++ b/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudPresenter.swift @@ -97,7 +97,7 @@ enum CloudPresenter { if provider == .claude { body = String(format: String(localized: "cloud.credentialRecovery.claudeDisclosure", defaultValue: "cmux will run claude setup-token in a visible local terminal, then install the resulting one-year login on this owner VM for %@.", table: "CloudCredentialRecovery", bundle: .module), teamName) } else { - body = String(localized: "cloud.credentialRecovery.codexDisclosure", defaultValue: "cmux will start a fresh Codex device login in a visible local terminal for this VM. Codex refresh credentials are never copied between VMs; Codex requires a separate sign-in for each VM.", table: "CloudCredentialRecovery", bundle: .module) + body = "cmux uses Codex sign-in on this Mac when needed; an existing broker login is refreshed without another sign-in. Only short-lived access moves to this owner VM. The refresh token stays on this Mac, so keep cmux running here to renew access." } let spec = CmuxDialogSpec( title: String(format: String(localized: "cloud.credentialRecovery.title", defaultValue: "Set up %@ on %@?", table: "CloudCredentialRecovery", bundle: .module), providerName, session.machine.title), @@ -125,10 +125,17 @@ enum CloudPresenter { await services?.openLocalProviderLogin(line) ?? false }) do { - try await transfer.run(api: services.cloud.api, - currentTeamID: { services.cloud.auth.teamID }, - currentUserID: { services.cloud.auth.user?.id }, - confirmed: true, refresh: true) + if provider == .codex { + let scope = CloudCodexBrokerScope(userID: userID, teamID: teamID) + try await services.cloud.codexBroker.run( + machineID: session.machineID, scope: scope, confirmed: true, forceRefresh: true, + presentDeviceCode: { prompt in await presentCodexDeviceCode(prompt, in: window) }) + } else { + try await transfer.run(api: services.cloud.api, + currentTeamID: { services.cloud.auth.teamID }, + currentUserID: { services.cloud.auth.user?.id }, + confirmed: true, refresh: true) + } guard services.cloud.auth.user?.id == userID, services.cloud.auth.teamID == teamID, let (currentTab, _) = services.locateTab(chat), currentTab.agentSession?.session == request.sessionID, @@ -149,6 +156,27 @@ enum CloudPresenter { } } + @MainActor private static func presentCodexDeviceCode(_ prompt: CloudCodexLoginPrompt, in window: NSWindow) async -> Bool { + let spec = CmuxDialogSpec( + title: "Sign in to Codex", + lines: ["Use Codex sign-in on this Mac. Enter this one-time code: \(prompt.userCode)", "Short-lived access moves to the owner VM; the refresh token stays on this Mac. Keep cmux running on this Mac to renew access."], + buttons: [.cancel("Cancel"), CmuxDialogButton(id: "open", title: "Open sign-in", role: .default), CmuxDialogButton(id: "signedIn", title: "I signed in", role: .normal)], + identifier: "cmux.dialog.cloud.codexDeviceCode") + var dialogID: Int? + let answer = await withTaskCancellationHandler { + await withCheckedContinuation { continuation in + dialogID = CmuxDialogCenter.shared.present(spec, in: .window(window), completion: { continuation.resume(returning: $0) }) + } + } onCancel: { + Task { @MainActor in if let dialogID { _ = CmuxDialogCenter.shared.dismiss(dialogID) } } + } + if answer.button == "open" { + NSWorkspace.shared.openInBackground(prompt.verificationURL) + return await presentCodexDeviceCode(prompt, in: window) + } + return answer.button == "signedIn" && !answer.isDismissal + } + /// Use the same installed CLI and user as the credentials. Old/root-layout machines fail /// closed rather than restarting a different daemon. No provider output reaches the UI. @MainActor static func restartCredentialSession(_ sessionID: String, exec: (String) async throws -> CloudExecResult) async throws -> Bool { From a1e987dcfed359b446d2293bf87c95c8eba0312c Mon Sep 17 00:00:00 2001 From: Leo Li Date: Tue, 27 Oct 2026 15:32:14 +0000 Subject: [PATCH 2/4] fix(cloud): make Codex recovery prompt cancellation safe Agent-Session: https://cmux.com/deeplink/agent?id=aid-91819a4266f391be2a8a --- .../CmuxNextApp/Cloud/CloudPresenter.swift | 63 ++- .../CmuxNextApp/Control/DebugDialog.swift | 13 +- .../Resources/CloudCodex.xcstrings | 419 ++++++++++++++++++ .../CloudCodexDeviceRecoveryTests.swift | 61 +++ .../CloudCredentialRecoveryTests.swift | 1 + 5 files changed, 538 insertions(+), 19 deletions(-) create mode 100644 Packages/macOS/CmuxNext/Sources/CmuxNextApp/Resources/CloudCodex.xcstrings create mode 100644 Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/CloudCodexDeviceRecoveryTests.swift diff --git a/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudPresenter.swift b/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudPresenter.swift index 24722c872fff..35e58245b7a1 100644 --- a/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudPresenter.swift +++ b/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudPresenter.swift @@ -97,7 +97,7 @@ enum CloudPresenter { if provider == .claude { body = String(format: String(localized: "cloud.credentialRecovery.claudeDisclosure", defaultValue: "cmux will run claude setup-token in a visible local terminal, then install the resulting one-year login on this owner VM for %@.", table: "CloudCredentialRecovery", bundle: .module), teamName) } else { - body = "cmux uses Codex sign-in on this Mac when needed; an existing broker login is refreshed without another sign-in. Only short-lived access moves to this owner VM. The refresh token stays on this Mac, so keep cmux running here to renew access." + body = codexBrokerDisclosure } let spec = CmuxDialogSpec( title: String(format: String(localized: "cloud.credentialRecovery.title", defaultValue: "Set up %@ on %@?", table: "CloudCredentialRecovery", bundle: .module), providerName, session.machine.title), @@ -156,25 +156,52 @@ enum CloudPresenter { } } - @MainActor private static func presentCodexDeviceCode(_ prompt: CloudCodexLoginPrompt, in window: NSWindow) async -> Bool { - let spec = CmuxDialogSpec( - title: "Sign in to Codex", - lines: ["Use Codex sign-in on this Mac. Enter this one-time code: \(prompt.userCode)", "Short-lived access moves to the owner VM; the refresh token stays on this Mac. Keep cmux running on this Mac to renew access."], - buttons: [.cancel("Cancel"), CmuxDialogButton(id: "open", title: "Open sign-in", role: .default), CmuxDialogButton(id: "signedIn", title: "I signed in", role: .normal)], - identifier: "cmux.dialog.cloud.codexDeviceCode") - var dialogID: Int? - let answer = await withTaskCancellationHandler { - await withCheckedContinuation { continuation in - dialogID = CmuxDialogCenter.shared.present(spec, in: .window(window), completion: { continuation.resume(returning: $0) }) + static let codexBrokerDisclosure = String(localized: "cloud.credentialRecovery.codexBroker", defaultValue: "cmux uses Codex sign-in on this Mac when needed; an existing broker login is refreshed without another sign-in. Only short-lived access moves to this owner VM. The refresh token stays on this Mac. Keep cmux running on this Mac to renew Codex access.", table: "CloudCodex", bundle: .module) + + static let codexDeviceCodeIdentifier = "cmux.dialog.cloud.codexDeviceCode" + + @MainActor static func presentCodexDeviceCode(_ prompt: CloudCodexLoginPrompt, in window: NSWindow) async -> Bool { + await presentCodexDeviceCode(prompt, in: .window(window), center: .shared, + open: { NSWorkspace.shared.openInBackground($0) }) + } + + @MainActor static func presentCodexDeviceCode(_ prompt: CloudCodexLoginPrompt, in scope: CmuxDialogScope, + center: CmuxDialogCenter, open: (URL) -> Void) async -> Bool { + let spec = codexDeviceDialogSpec(code: prompt.userCode) + // wakeup-allow: every iteration suspends for a user answer; only Open sign-in repeats it, and cancellation dismisses the dialog. + while !Task.isCancelled { + let answers = AsyncStream.makeStream(of: CmuxDialogAnswer.self) + let dialogID = center.present(spec, in: scope) { answer in + answers.continuation.yield(answer) + answers.continuation.finish() } - } onCancel: { - Task { @MainActor in if let dialogID { _ = CmuxDialogCenter.shared.dismiss(dialogID) } } - } - if answer.button == "open" { - NSWorkspace.shared.openInBackground(prompt.verificationURL) - return await presentCodexDeviceCode(prompt, in: window) + defer { answers.continuation.finish(); center.dismiss(dialogID) } + let answer = await withTaskCancellationHandler { + var iterator = answers.stream.makeAsyncIterator() + return await iterator.next() + } onCancel: { + // task-owner: one-shot cancellation hop dismisses this dialog; the continuation then releases it. + Task { @MainActor in center.dismiss(dialogID) } + } + guard !Task.isCancelled, let answer, !answer.isDismissal else { return false } + if answer.button == "open" { open(prompt.verificationURL) } + else { return answer.button == "signedIn" } } - return answer.button == "signedIn" && !answer.isDismissal + return false + } + + @MainActor static func codexDeviceDialogSpec(code: String) -> CmuxDialogSpec { + CmuxDialogSpec( + title: String(localized: "cloud.codexDevice.title", defaultValue: "Sign in to Codex", table: "CloudCodex", bundle: .module), + lines: [ + String(format: String(localized: "cloud.codexDevice.code", defaultValue: "Use Codex sign-in on this Mac. Enter this one-time code: %@", table: "CloudCodex", bundle: .module), code), + String(localized: "cloud.codexDevice.scope", defaultValue: "Only short-lived access moves to the VM; the refresh token stays on this Mac. Keep cmux running on this Mac to renew Codex access.", table: "CloudCodex", bundle: .module), + ], + buttons: [ + .cancel(String(localized: "cloud.codexDevice.cancel", defaultValue: "Cancel", table: "CloudCodex", bundle: .module)), + CmuxDialogButton(id: "open", title: String(localized: "cloud.codexDevice.open", defaultValue: "Open sign-in", table: "CloudCodex", bundle: .module), role: .default), + CmuxDialogButton(id: "signedIn", title: String(localized: "cloud.codexDevice.signedIn", defaultValue: "I signed in", table: "CloudCodex", bundle: .module), role: .normal), + ], identifier: codexDeviceCodeIdentifier) } /// Use the same installed CLI and user as the credentials. Old/root-layout machines fail diff --git a/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Control/DebugDialog.swift b/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Control/DebugDialog.swift index efc1b777e0bd..37dedfea3afc 100644 --- a/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Control/DebugDialog.swift +++ b/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Control/DebugDialog.swift @@ -26,7 +26,7 @@ enum DebugDialog { /// the person approves, cx-t2rz), a browser pairing (it admits a client to the whole daemon, cx-hnmbt.2), an iPhone pairing, /// an ssh:// link from another app (it connects to a host a web page chose), and a cmux://session /// link's new tab (any page or app can open the link). - static let userOnly: Set = [ClipboardReadStrings.identifier, AgentPaneView.harnessEnableIdentifier, CloudPresenter.createConfirmIdentifier, CloudPresenter.credentialRecoveryIdentifier, + static let userOnly: Set = [ClipboardReadStrings.identifier, AgentPaneView.harnessEnableIdentifier, CloudPresenter.createConfirmIdentifier, CloudPresenter.credentialRecoveryIdentifier, CloudPresenter.codexDeviceCodeIdentifier, CloudWorkspaceSetupWizard.credentialDialogIdentifier, DaemonPairingStrings.identifier, IPhoneHostService.pairDialogIdentifier, ExternalLinkGate.sshDialogIdentifier, SessionLinkGate.dialogIdentifier] @@ -122,8 +122,19 @@ enum DebugDialog { } /// Debug-only fixtures (English only: they never ship). + private static func codexRecoveryFixture(body: String) -> CmuxDialogSpec { + CmuxDialogSpec(title: "Set up Codex on paint?", + lines: [body, "This replaces Codex's login on this VM and restarts this chat before retrying its failed prompt."], + buttons: [.cancel("Cancel"), CmuxDialogButton(id: "transfer", title: "Transfer and restart", role: .default)], + identifier: CloudPresenter.credentialRecoveryIdentifier) + } + private static func fixtures(origin: String?) -> [String: CmuxDialogSpec] { [ + // Inert native auth fixtures: no account, VM, browser, or credentials are accessed. + "cloudCodexDevice": CloudPresenter.codexDeviceDialogSpec(code: "DEMO-CODE"), + "cloudCodexRecovery": codexRecoveryFixture(body: CloudPresenter.codexBrokerDisclosure), + "cloudCodexRecoveryBefore": codexRecoveryFixture(body: "cmux will start a fresh Codex device login in a visible local terminal for this VM. Codex refresh credentials are never copied between VMs; Codex requires a separate sign-in for each VM."), "confirm": CmuxDialogSpec(title: "Close Workspace “api”?", lines: ["vim and npm are still running."], buttons: [.cancel(), CmuxDialogButton(id: "close", title: "Close", role: .destructive)], identifier: "cmux.dialog.fixture.confirm"), diff --git a/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Resources/CloudCodex.xcstrings b/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Resources/CloudCodex.xcstrings new file mode 100644 index 000000000000..7abae203932f --- /dev/null +++ b/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Resources/CloudCodex.xcstrings @@ -0,0 +1,419 @@ +{ + "sourceLanguage": "en", + "strings": { + "cloud.codexDevice.cancel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Abbrechen" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Annuler" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "إلغاء" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cancelar" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "取消" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "取消" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "취소" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "キャンセル" + } + } + } + }, + "cloud.codexDevice.code": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use Codex sign-in on this Mac. Enter this one-time code: %@" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Melde dich auf diesem Mac bei Codex an. Gib diesen Einmalcode ein: %@" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Connectez-vous à Codex sur ce Mac. Saisissez ce code à usage unique : %@" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "سجّل الدخول إلى Codex على جهاز Mac هذا. أدخل هذا الرمز المخصص للاستخدام مرة واحدة: %@" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Inicia sesión en Codex en este Mac. Introduce este código de un solo uso: %@" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "在這部 Mac 上登入 Codex。輸入此一次性驗證碼:%@" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "在这台 Mac 上登录 Codex。输入此一次性验证码:%@" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "이 Mac에서 Codex에 로그인하세요. 다음 일회용 코드를 입력하세요: %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このMacでCodexにログインしてください。次のワンタイムコードを入力してください:%@" + } + } + } + }, + "cloud.codexDevice.open": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open sign-in" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Anmeldung öffnen" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Ouvrir la connexion" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "فتح تسجيل الدخول" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Abrir inicio de sesión" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "開啟登入頁面" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "打开登录页面" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "로그인 열기" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ログイン画面を開く" + } + } + } + }, + "cloud.codexDevice.scope": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Only short-lived access moves to the VM; the refresh token stays on this Mac. Keep cmux running on this Mac to renew Codex access." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Nur der kurzlebige Zugang wird auf die VM übertragen; das Aktualisierungstoken bleibt auf diesem Mac. Lass cmux auf diesem Mac laufen, um den Codex-Zugang zu erneuern." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Seul un accès de courte durée est transféré à la machine virtuelle ; le jeton de renouvellement reste sur ce Mac. Laissez cmux en cours d’exécution sur ce Mac pour renouveler l’accès à Codex." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يُنقل إلى الآلة الافتراضية إذن وصول قصير الأجل فقط؛ ويبقى رمز التجديد على جهاز Mac هذا. أبقِ cmux قيد التشغيل على جهاز Mac هذا لتجديد الوصول إلى Codex." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Solo se transfiere a la máquina virtual el acceso de corta duración; el token de renovación permanece en este Mac. Mantén cmux en ejecución en este Mac para renovar el acceso a Codex." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "只有短期存取權限會傳送至虛擬機器;更新權杖會留在這部 Mac 上。請保持 cmux 在這部 Mac 上執行,以更新 Codex 存取權限。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "只有短期访问权限会传送至虚拟机;刷新令牌会保留在这台 Mac 上。请保持 cmux 在这台 Mac 上运行,以续期 Codex 访问权限。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "단기 액세스 권한만 VM으로 전송되며 갱신 토큰은 이 Mac에 남습니다. Codex 액세스 권한을 갱신하려면 이 Mac에서 cmux를 계속 실행해 두세요." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "VMには短期間のアクセス権限のみが転送され、更新トークンはこのMacに残ります。Codexのアクセス権限を更新するには、このMacでcmuxを実行したままにしてください。" + } + } + } + }, + "cloud.codexDevice.signedIn": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "I signed in" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Ich habe mich angemeldet" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Je me suis connecté" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "لقد سجّلت الدخول" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Ya inicié sesión" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "我已登入" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "我已登录" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "로그인했어요" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ログインしました" + } + } + } + }, + "cloud.codexDevice.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in to Codex" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Bei Codex anmelden" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Se connecter à Codex" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تسجيل الدخول إلى Codex" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Iniciar sesión en Codex" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "登入 Codex" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "登录 Codex" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "Codex에 로그인" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Codexにログイン" + } + } + } + }, + "cloud.credentialRecovery.codexBroker": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux uses Codex sign-in on this Mac when needed; an existing broker login is refreshed without another sign-in. Only short-lived access moves to this owner VM. The refresh token stays on this Mac. Keep cmux running on this Mac to renew Codex access." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "cmux verwendet bei Bedarf die Codex-Anmeldung auf diesem Mac; eine bestehende Broker-Anmeldung wird ohne erneute Anmeldung aktualisiert. Nur der kurzlebige Zugang wird auf diese Eigentümer-VM übertragen. Das Aktualisierungstoken bleibt auf diesem Mac. Lass cmux auf diesem Mac laufen, um den Codex-Zugang zu erneuern." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "cmux utilise la connexion à Codex sur ce Mac si nécessaire ; une connexion existante au service intermédiaire est renouvelée sans nouvelle connexion. Seul un accès de courte durée est transféré à cette machine virtuelle du propriétaire. Le jeton de renouvellement reste sur ce Mac. Laissez cmux en cours d’exécution sur ce Mac pour renouveler l’accès à Codex." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يستخدم cmux تسجيل الدخول إلى Codex على جهاز Mac هذا عند الحاجة؛ ويُجدّد تسجيل الدخول الحالي لدى الوسيط دون الحاجة إلى تسجيل الدخول مجددًا. يُنقل إذن وصول قصير الأجل فقط إلى هذه الآلة الافتراضية الخاصة بالمالك. يبقى رمز التجديد على جهاز Mac هذا. أبقِ cmux قيد التشغيل على جهاز Mac هذا لتجديد الوصول إلى Codex." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "cmux usa el inicio de sesión de Codex en este Mac cuando es necesario; una sesión existente del intermediario se renueva sin volver a iniciar sesión. Solo se transfiere el acceso de corta duración a esta máquina virtual del propietario. El token de renovación permanece en este Mac. Mantén cmux en ejecución en este Mac para renovar el acceso a Codex." + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "cmux 會在需要時使用這部 Mac 上的 Codex 登入資訊;現有的代理服務登入狀態會自動更新,無須再次登入。只有短期存取權限會傳送至這部擁有者專用的虛擬機器。更新權杖會留在這部 Mac 上。請保持 cmux 在這部 Mac 上執行,以更新 Codex 存取權限。" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "cmux 会在需要时使用这台 Mac 上的 Codex 登录信息;现有的代理服务登录状态会自动刷新,无须再次登录。只有短期访问权限会传送至这台所有者专用的虚拟机。刷新令牌会保留在这台 Mac 上。请保持 cmux 在这台 Mac 上运行,以续期 Codex 访问权限。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "cmux는 필요할 때 이 Mac의 Codex 로그인을 사용하며, 기존 중개 서비스 로그인은 다시 로그인할 필요 없이 갱신됩니다. 단기 액세스 권한만 이 소유자 VM으로 전송됩니다. 갱신 토큰은 이 Mac에 남습니다. Codex 액세스 권한을 갱신하려면 이 Mac에서 cmux를 계속 실행해 두세요." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmuxは必要に応じてこのMacのCodexログインを使用し、既存の仲介サービスのログインは再ログインなしで更新されます。この所有者用VMには短期間のアクセス権限のみが転送されます。更新トークンはこのMacに残ります。Codexのアクセス権限を更新するには、このMacでcmuxを実行したままにしてください。" + } + } + } + } + }, + "version": "1.0" +} diff --git a/Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/CloudCodexDeviceRecoveryTests.swift b/Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/CloudCodexDeviceRecoveryTests.swift new file mode 100644 index 000000000000..9dff4076ec96 --- /dev/null +++ b/Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/CloudCodexDeviceRecoveryTests.swift @@ -0,0 +1,61 @@ +import CmuxNextCloud +@testable import CmuxNextApp +import CmuxNextDesign +import Foundation +import Testing + +@MainActor @Suite(.serialized) +struct CloudCodexDeviceRecoveryTests { + private func makePrompt() throws -> CloudCodexLoginPrompt { + CloudCodexLoginPrompt(verificationURL: try #require(URL(string: "https://example.com/device")), userCode: "DEMO-CODE") + } + + @Test func cancellationDismissesAnOpenCodeAndReturnsFalse() async throws { + let prompt = try makePrompt() + let center = CmuxDialogCenter(host: CmuxDialogHeadlessHost()) + let events = AsyncStream.makeStream(of: Void.self) + let token = center.observe { event in + if case .opened = event { events.continuation.yield(()) } + } + defer { center.stopObserving(token); events.continuation.finish() } + let task = Task { + await CloudPresenter.presentCodexDeviceCode(prompt, in: .app, center: center, + open: { _ in Issue.record("Cancelled prompt opened a browser") }) + } + var iterator = events.stream.makeAsyncIterator() + await iterator.next() + task.cancel() + #expect(await task.value == false) + #expect(center.records.isEmpty) + } + + @Test func alreadyCancelledOperationNeverShowsACode() async throws { + let prompt = try makePrompt() + let center = CmuxDialogCenter(host: CmuxDialogHeadlessHost()) + let task = Task { + await CloudPresenter.presentCodexDeviceCode(prompt, in: .app, center: center, + open: { _ in Issue.record("Cancelled prompt opened a browser") }) + } + task.cancel() + #expect(await task.value == false) + #expect(center.records.isEmpty) + } + + @Test func openingBrowserKeepsCodeAvailableUntilExplicitConfirmation() async throws { + let prompt = try makePrompt() + let center = CmuxDialogCenter(host: CmuxDialogHeadlessHost()) + var opened: [URL] = [] + var shown = 0 + let token = center.observe { event in + guard case .opened(let record) = event else { return } + shown += 1 + center.press(record.id, button: shown == 1 ? "open" : "signedIn") + } + defer { center.stopObserving(token) } + #expect(await CloudPresenter.presentCodexDeviceCode(prompt, in: .app, center: center, + open: { opened.append($0) })) + #expect(opened == [prompt.verificationURL]) + #expect(shown == 2) + #expect(center.records.isEmpty) + } +} diff --git a/Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/CloudCredentialRecoveryTests.swift b/Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/CloudCredentialRecoveryTests.swift index 31eb2ba0d338..12a91b38e989 100644 --- a/Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/CloudCredentialRecoveryTests.swift +++ b/Packages/macOS/CmuxNext/Tests/CmuxNextAppTests/CloudCredentialRecoveryTests.swift @@ -34,6 +34,7 @@ struct CloudCredentialRecoveryTests { @Test func credentialRecoveryConfirmationIsAlwaysUserOnly() { #expect(DebugDialog.userOnly.contains(CloudPresenter.credentialRecoveryIdentifier)) + #expect(DebugDialog.userOnly.contains(CloudPresenter.codexDeviceCodeIdentifier)) } @Test func remoteHandshakeCarriesProviderVMIdentitySeparatelyFromSession() async throws { From 4dfa28067be0ae860fba55d64440f1418dd409fb Mon Sep 17 00:00:00 2001 From: Leo Li Date: Tue, 27 Oct 2026 15:33:09 +0000 Subject: [PATCH 3/4] fix(cloud): bound recovery dialog answers Agent-Session: https://cmux.com/deeplink/agent?id=aid-91819a4266f391be2a8a --- .../CmuxNext/Sources/CmuxNextApp/Cloud/CloudPresenter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudPresenter.swift b/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudPresenter.swift index 35e58245b7a1..e08768ef33e8 100644 --- a/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudPresenter.swift +++ b/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudPresenter.swift @@ -170,7 +170,7 @@ enum CloudPresenter { let spec = codexDeviceDialogSpec(code: prompt.userCode) // wakeup-allow: every iteration suspends for a user answer; only Open sign-in repeats it, and cancellation dismisses the dialog. while !Task.isCancelled { - let answers = AsyncStream.makeStream(of: CmuxDialogAnswer.self) + let answers = AsyncStream.makeStream(of: CmuxDialogAnswer.self, bufferingPolicy: .bufferingNewest(1)) let dialogID = center.present(spec, in: scope) { answer in answers.continuation.yield(answer) answers.continuation.finish() From cde52914522c43d0644641163b93d19d87c5fa42 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Tue, 27 Oct 2026 16:06:02 +0000 Subject: [PATCH 4/4] localize(cloud): complete Codex recovery translations Agent-Session: https://cmux.com/deeplink/agent?id=aid-91819a4266f391be2a8a --- .../Resources/CloudCodex.xcstrings | 525 +++++------------- 1 file changed, 147 insertions(+), 378 deletions(-) diff --git a/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Resources/CloudCodex.xcstrings b/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Resources/CloudCodex.xcstrings index 7abae203932f..4128cff7dfb3 100644 --- a/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Resources/CloudCodex.xcstrings +++ b/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Resources/CloudCodex.xcstrings @@ -4,414 +4,183 @@ "cloud.codexDevice.cancel": { "extractionState": "manual", "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Cancel" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Abbrechen" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Annuler" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "إلغاء" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Cancelar" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "取消" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "取消" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "취소" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "キャンセル" - } - } + "en": {"stringUnit": {"state": "translated", "value": "Cancel"}}, + "de": {"stringUnit": {"state": "translated", "value": "Abbrechen"}}, + "fr": {"stringUnit": {"state": "translated", "value": "Annuler"}}, + "ar": {"stringUnit": {"state": "translated", "value": "إلغاء"}}, + "es": {"stringUnit": {"state": "translated", "value": "Cancelar"}}, + "zh-Hant": {"stringUnit": {"state": "translated", "value": "取消"}}, + "zh-Hans": {"stringUnit": {"state": "translated", "value": "取消"}}, + "ko": {"stringUnit": {"state": "translated", "value": "취소"}}, + "ja": {"stringUnit": {"state": "translated", "value": "キャンセル"}}, + "it": {"stringUnit": {"state": "translated", "value": "Annulla"}}, + "da": {"stringUnit": {"state": "translated", "value": "Annuller"}}, + "nb": {"stringUnit": {"state": "translated", "value": "Avbryt"}}, + "pl": {"stringUnit": {"state": "translated", "value": "Anuluj"}}, + "pt-BR": {"stringUnit": {"state": "translated", "value": "Cancelar"}}, + "ru": {"stringUnit": {"state": "translated", "value": "Отмена"}}, + "uk": {"stringUnit": {"state": "translated", "value": "Скасувати"}}, + "bs": {"stringUnit": {"state": "translated", "value": "Otkaži"}}, + "vi": {"stringUnit": {"state": "translated", "value": "Hủy"}}, + "tr": {"stringUnit": {"state": "translated", "value": "İptal"}}, + "th": {"stringUnit": {"state": "translated", "value": "ยกเลิก"}}, + "km": {"stringUnit": {"state": "translated", "value": "បោះបង់"}} } }, "cloud.codexDevice.code": { "extractionState": "manual", "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Use Codex sign-in on this Mac. Enter this one-time code: %@" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Melde dich auf diesem Mac bei Codex an. Gib diesen Einmalcode ein: %@" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Connectez-vous à Codex sur ce Mac. Saisissez ce code à usage unique : %@" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "سجّل الدخول إلى Codex على جهاز Mac هذا. أدخل هذا الرمز المخصص للاستخدام مرة واحدة: %@" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Inicia sesión en Codex en este Mac. Introduce este código de un solo uso: %@" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "在這部 Mac 上登入 Codex。輸入此一次性驗證碼:%@" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在这台 Mac 上登录 Codex。输入此一次性验证码:%@" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "이 Mac에서 Codex에 로그인하세요. 다음 일회용 코드를 입력하세요: %@" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "このMacでCodexにログインしてください。次のワンタイムコードを入力してください:%@" - } - } + "en": {"stringUnit": {"state": "translated", "value": "Use Codex sign-in on this Mac. Enter this one-time code: %@"}}, + "de": {"stringUnit": {"state": "translated", "value": "Melde dich auf diesem Mac bei Codex an. Gib diesen Einmalcode ein: %@"}}, + "fr": {"stringUnit": {"state": "translated", "value": "Connectez-vous à Codex sur ce Mac. Saisissez ce code à usage unique : %@"}}, + "ar": {"stringUnit": {"state": "translated", "value": "سجّل الدخول إلى Codex على جهاز Mac هذا. أدخل هذا الرمز المخصص للاستخدام مرة واحدة: %@"}}, + "es": {"stringUnit": {"state": "translated", "value": "Inicia sesión en Codex en este Mac. Introduce este código de un solo uso: %@"}}, + "zh-Hant": {"stringUnit": {"state": "translated", "value": "在這部 Mac 上登入 Codex。輸入此一次性驗證碼:%@"}}, + "zh-Hans": {"stringUnit": {"state": "translated", "value": "在这台 Mac 上登录 Codex。输入此一次性验证码:%@"}}, + "ko": {"stringUnit": {"state": "translated", "value": "이 Mac에서 Codex에 로그인하세요. 다음 일회용 코드를 입력하세요: %@"}}, + "ja": {"stringUnit": {"state": "translated", "value": "このMacでCodexにログインしてください。次のワンタイムコードを入力してください:%@"}}, + "it": {"stringUnit": {"state": "translated", "value": "Accedi a Codex su questo Mac. Inserisci questo codice monouso: %@"}}, + "da": {"stringUnit": {"state": "translated", "value": "Log ind på Codex på denne Mac. Indtast denne engangskode: %@"}}, + "nb": {"stringUnit": {"state": "translated", "value": "Logg inn på Codex på denne Macen. Skriv inn denne engangskoden: %@"}}, + "pl": {"stringUnit": {"state": "translated", "value": "Zaloguj się do Codex na tym Macu. Wpisz ten jednorazowy kod: %@"}}, + "pt-BR": {"stringUnit": {"state": "translated", "value": "Entre no Codex neste Mac. Insira este código de uso único: %@"}}, + "ru": {"stringUnit": {"state": "translated", "value": "Войдите в Codex на этом Mac. Введите этот одноразовый код: %@"}}, + "uk": {"stringUnit": {"state": "translated", "value": "Увійдіть у Codex на цьому Mac. Введіть цей одноразовий код: %@"}}, + "bs": {"stringUnit": {"state": "translated", "value": "Prijavite se u Codex na ovom Macu. Unesite ovaj jednokratni kôd: %@"}}, + "vi": {"stringUnit": {"state": "translated", "value": "Đăng nhập vào Codex trên máy Mac này. Nhập mã dùng một lần sau: %@"}}, + "tr": {"stringUnit": {"state": "translated", "value": "Bu Mac’te Codex’te oturum açın. Bu tek kullanımlık kodu girin: %@"}}, + "th": {"stringUnit": {"state": "translated", "value": "ลงชื่อเข้าใช้ Codex บน Mac เครื่องนี้ ป้อนรหัสแบบใช้ครั้งเดียวนี้: %@"}}, + "km": {"stringUnit": {"state": "translated", "value": "ចូលប្រើ Codex នៅលើ Mac នេះ។ បញ្ចូលលេខកូដប្រើបានតែម្តងនេះ៖ %@"}} } }, "cloud.codexDevice.open": { "extractionState": "manual", "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Open sign-in" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Anmeldung öffnen" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ouvrir la connexion" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "فتح تسجيل الدخول" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Abrir inicio de sesión" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "開啟登入頁面" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "打开登录页面" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "로그인 열기" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ログイン画面を開く" - } - } + "en": {"stringUnit": {"state": "translated", "value": "Open sign-in"}}, + "de": {"stringUnit": {"state": "translated", "value": "Anmeldung öffnen"}}, + "fr": {"stringUnit": {"state": "translated", "value": "Ouvrir la connexion"}}, + "ar": {"stringUnit": {"state": "translated", "value": "فتح تسجيل الدخول"}}, + "es": {"stringUnit": {"state": "translated", "value": "Abrir inicio de sesión"}}, + "zh-Hant": {"stringUnit": {"state": "translated", "value": "開啟登入頁面"}}, + "zh-Hans": {"stringUnit": {"state": "translated", "value": "打开登录页面"}}, + "ko": {"stringUnit": {"state": "translated", "value": "로그인 열기"}}, + "ja": {"stringUnit": {"state": "translated", "value": "ログイン画面を開く"}}, + "it": {"stringUnit": {"state": "translated", "value": "Apri accesso"}}, + "da": {"stringUnit": {"state": "translated", "value": "Åbn login"}}, + "nb": {"stringUnit": {"state": "translated", "value": "Åpne innlogging"}}, + "pl": {"stringUnit": {"state": "translated", "value": "Otwórz logowanie"}}, + "pt-BR": {"stringUnit": {"state": "translated", "value": "Abrir login"}}, + "ru": {"stringUnit": {"state": "translated", "value": "Открыть страницу входа"}}, + "uk": {"stringUnit": {"state": "translated", "value": "Відкрити сторінку входу"}}, + "bs": {"stringUnit": {"state": "translated", "value": "Otvori prijavu"}}, + "vi": {"stringUnit": {"state": "translated", "value": "Mở trang đăng nhập"}}, + "tr": {"stringUnit": {"state": "translated", "value": "Oturum açma sayfasını aç"}}, + "th": {"stringUnit": {"state": "translated", "value": "เปิดหน้าลงชื่อเข้าใช้"}}, + "km": {"stringUnit": {"state": "translated", "value": "បើកទំព័រចូល"}} } }, "cloud.codexDevice.scope": { "extractionState": "manual", "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Only short-lived access moves to the VM; the refresh token stays on this Mac. Keep cmux running on this Mac to renew Codex access." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Nur der kurzlebige Zugang wird auf die VM übertragen; das Aktualisierungstoken bleibt auf diesem Mac. Lass cmux auf diesem Mac laufen, um den Codex-Zugang zu erneuern." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Seul un accès de courte durée est transféré à la machine virtuelle ; le jeton de renouvellement reste sur ce Mac. Laissez cmux en cours d’exécution sur ce Mac pour renouveler l’accès à Codex." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يُنقل إلى الآلة الافتراضية إذن وصول قصير الأجل فقط؛ ويبقى رمز التجديد على جهاز Mac هذا. أبقِ cmux قيد التشغيل على جهاز Mac هذا لتجديد الوصول إلى Codex." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Solo se transfiere a la máquina virtual el acceso de corta duración; el token de renovación permanece en este Mac. Mantén cmux en ejecución en este Mac para renovar el acceso a Codex." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "只有短期存取權限會傳送至虛擬機器;更新權杖會留在這部 Mac 上。請保持 cmux 在這部 Mac 上執行,以更新 Codex 存取權限。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "只有短期访问权限会传送至虚拟机;刷新令牌会保留在这台 Mac 上。请保持 cmux 在这台 Mac 上运行,以续期 Codex 访问权限。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "단기 액세스 권한만 VM으로 전송되며 갱신 토큰은 이 Mac에 남습니다. Codex 액세스 권한을 갱신하려면 이 Mac에서 cmux를 계속 실행해 두세요." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "VMには短期間のアクセス権限のみが転送され、更新トークンはこのMacに残ります。Codexのアクセス権限を更新するには、このMacでcmuxを実行したままにしてください。" - } - } + "en": {"stringUnit": {"state": "translated", "value": "Only short-lived access moves to the VM; the refresh token stays on this Mac. Keep cmux running on this Mac to renew Codex access."}}, + "de": {"stringUnit": {"state": "translated", "value": "Nur der kurzlebige Zugang wird auf die VM übertragen; das Aktualisierungstoken bleibt auf diesem Mac. Lass cmux auf diesem Mac laufen, um den Codex-Zugang zu erneuern."}}, + "fr": {"stringUnit": {"state": "translated", "value": "Seul un accès de courte durée est transféré à la machine virtuelle ; le jeton de renouvellement reste sur ce Mac. Laissez cmux en cours d’exécution sur ce Mac pour renouveler l’accès à Codex."}}, + "ar": {"stringUnit": {"state": "translated", "value": "يُنقل إلى الآلة الافتراضية إذن وصول قصير الأجل فقط؛ ويبقى رمز التجديد على جهاز Mac هذا. أبقِ cmux قيد التشغيل على جهاز Mac هذا لتجديد الوصول إلى Codex."}}, + "es": {"stringUnit": {"state": "translated", "value": "Solo se transfiere a la máquina virtual el acceso de corta duración; el token de renovación permanece en este Mac. Mantén cmux en ejecución en este Mac para renovar el acceso a Codex."}}, + "zh-Hant": {"stringUnit": {"state": "translated", "value": "只有短期存取權限會傳送至虛擬機器;更新權杖會留在這部 Mac 上。請保持 cmux 在這部 Mac 上執行,以更新 Codex 存取權限。"}}, + "zh-Hans": {"stringUnit": {"state": "translated", "value": "只有短期访问权限会传送至虚拟机;刷新令牌会保留在这台 Mac 上。请保持 cmux 在这台 Mac 上运行,以续期 Codex 访问权限。"}}, + "ko": {"stringUnit": {"state": "translated", "value": "단기 액세스 권한만 VM으로 전송되며 갱신 토큰은 이 Mac에 남습니다. Codex 액세스 권한을 갱신하려면 이 Mac에서 cmux를 계속 실행해 두세요."}}, + "ja": {"stringUnit": {"state": "translated", "value": "VMには短期間のアクセス権限のみが転送され、更新トークンはこのMacに残ります。Codexのアクセス権限を更新するには、このMacでcmuxを実行したままにしてください。"}}, + "it": {"stringUnit": {"state": "translated", "value": "Solo l’accesso di breve durata viene trasferito alla macchina virtuale; il token di rinnovo rimane su questo Mac. Mantieni cmux in esecuzione su questo Mac per rinnovare l’accesso a Codex."}}, + "da": {"stringUnit": {"state": "translated", "value": "Kun kortvarig adgang overføres til den virtuelle maskine; fornyelsestokenet bliver på denne Mac. Hold cmux kørende på denne Mac for at forny adgangen til Codex."}}, + "nb": {"stringUnit": {"state": "translated", "value": "Bare kortvarig tilgang overføres til den virtuelle maskinen; fornyelsestokenet blir på denne Macen. La cmux kjøre på denne Macen for å fornye tilgangen til Codex."}}, + "pl": {"stringUnit": {"state": "translated", "value": "Do maszyny wirtualnej przenoszony jest tylko krótkotrwały dostęp; token odświeżania pozostaje na tym Macu. Pozostaw cmux uruchomiony na tym Macu, aby odnawiać dostęp do Codex."}}, + "pt-BR": {"stringUnit": {"state": "translated", "value": "Somente o acesso de curta duração é transferido para a máquina virtual; o token de renovação permanece neste Mac. Mantenha o cmux em execução neste Mac para renovar o acesso ao Codex."}}, + "ru": {"stringUnit": {"state": "translated", "value": "На виртуальную машину передаётся только краткосрочный доступ; токен обновления остаётся на этом Mac. Не закрывайте cmux на этом Mac, чтобы продлевать доступ к Codex."}}, + "uk": {"stringUnit": {"state": "translated", "value": "На віртуальну машину передається лише короткочасний доступ; токен оновлення залишається на цьому Mac. Не закривайте cmux на цьому Mac, щоб поновлювати доступ до Codex."}}, + "bs": {"stringUnit": {"state": "translated", "value": "Samo kratkotrajni pristup prenosi se na virtuelnu mašinu; token za obnavljanje ostaje na ovom Macu. Ostavite cmux pokrenut na ovom Macu da bi se pristup Codexu obnavljao."}}, + "vi": {"stringUnit": {"state": "translated", "value": "Chỉ quyền truy cập ngắn hạn được chuyển sang máy ảo; mã thông báo làm mới vẫn ở trên máy Mac này. Hãy giữ cmux chạy trên máy Mac này để gia hạn quyền truy cập Codex."}}, + "tr": {"stringUnit": {"state": "translated", "value": "VM’ye yalnızca kısa süreli erişim aktarılır; yenileme belirteci bu Mac’te kalır. Codex erişimini yenilemek için bu Mac’te cmux’u çalışır durumda tutun."}}, + "th": {"stringUnit": {"state": "translated", "value": "เฉพาะสิทธิ์เข้าถึงระยะสั้นเท่านั้นที่จะโอนไปยัง VM ส่วนโทเค็นรีเฟรชจะอยู่บน Mac เครื่องนี้ เปิด cmux บน Mac เครื่องนี้ไว้เพื่อต่ออายุสิทธิ์เข้าถึง Codex"}}, + "km": {"stringUnit": {"state": "translated", "value": "មានតែសិទ្ធិចូលប្រើរយៈពេលខ្លីប៉ុណ្ណោះដែលត្រូវបានផ្ទេរទៅ VM។ ថូខឹនបន្តសុពលភាពនៅតែស្ថិតលើ Mac នេះ។ ទុកឱ្យ cmux ដំណើរការនៅលើ Mac នេះ ដើម្បីបន្តសិទ្ធិចូលប្រើ Codex។"}} } }, "cloud.codexDevice.signedIn": { "extractionState": "manual", "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "I signed in" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Ich habe mich angemeldet" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Je me suis connecté" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "لقد سجّلت الدخول" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ya inicié sesión" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "我已登入" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "我已登录" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "로그인했어요" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ログインしました" - } - } + "en": {"stringUnit": {"state": "translated", "value": "I signed in"}}, + "de": {"stringUnit": {"state": "translated", "value": "Ich habe mich angemeldet"}}, + "fr": {"stringUnit": {"state": "translated", "value": "Je me suis connecté"}}, + "ar": {"stringUnit": {"state": "translated", "value": "لقد سجّلت الدخول"}}, + "es": {"stringUnit": {"state": "translated", "value": "Ya inicié sesión"}}, + "zh-Hant": {"stringUnit": {"state": "translated", "value": "我已登入"}}, + "zh-Hans": {"stringUnit": {"state": "translated", "value": "我已登录"}}, + "ko": {"stringUnit": {"state": "translated", "value": "로그인했어요"}}, + "ja": {"stringUnit": {"state": "translated", "value": "ログインしました"}}, + "it": {"stringUnit": {"state": "translated", "value": "Ho effettuato l’accesso"}}, + "da": {"stringUnit": {"state": "translated", "value": "Jeg har logget ind"}}, + "nb": {"stringUnit": {"state": "translated", "value": "Jeg har logget inn"}}, + "pl": {"stringUnit": {"state": "translated", "value": "Logowanie ukończone"}}, + "pt-BR": {"stringUnit": {"state": "translated", "value": "Já entrei"}}, + "ru": {"stringUnit": {"state": "translated", "value": "Вход выполнен"}}, + "uk": {"stringUnit": {"state": "translated", "value": "Вхід виконано"}}, + "bs": {"stringUnit": {"state": "translated", "value": "Prijava je završena"}}, + "vi": {"stringUnit": {"state": "translated", "value": "Tôi đã đăng nhập"}}, + "tr": {"stringUnit": {"state": "translated", "value": "Oturum açtım"}}, + "th": {"stringUnit": {"state": "translated", "value": "ฉันลงชื่อเข้าใช้แล้ว"}}, + "km": {"stringUnit": {"state": "translated", "value": "ខ្ញុំបានចូលរួចហើយ"}} } }, "cloud.codexDevice.title": { "extractionState": "manual", "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Sign in to Codex" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Bei Codex anmelden" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Se connecter à Codex" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "تسجيل الدخول إلى Codex" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Iniciar sesión en Codex" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "登入 Codex" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "登录 Codex" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "Codex에 로그인" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Codexにログイン" - } - } + "en": {"stringUnit": {"state": "translated", "value": "Sign in to Codex"}}, + "de": {"stringUnit": {"state": "translated", "value": "Bei Codex anmelden"}}, + "fr": {"stringUnit": {"state": "translated", "value": "Se connecter à Codex"}}, + "ar": {"stringUnit": {"state": "translated", "value": "تسجيل الدخول إلى Codex"}}, + "es": {"stringUnit": {"state": "translated", "value": "Iniciar sesión en Codex"}}, + "zh-Hant": {"stringUnit": {"state": "translated", "value": "登入 Codex"}}, + "zh-Hans": {"stringUnit": {"state": "translated", "value": "登录 Codex"}}, + "ko": {"stringUnit": {"state": "translated", "value": "Codex에 로그인"}}, + "ja": {"stringUnit": {"state": "translated", "value": "Codexにログイン"}}, + "it": {"stringUnit": {"state": "translated", "value": "Accedi a Codex"}}, + "da": {"stringUnit": {"state": "translated", "value": "Log ind på Codex"}}, + "nb": {"stringUnit": {"state": "translated", "value": "Logg inn på Codex"}}, + "pl": {"stringUnit": {"state": "translated", "value": "Zaloguj się do Codex"}}, + "pt-BR": {"stringUnit": {"state": "translated", "value": "Entrar no Codex"}}, + "ru": {"stringUnit": {"state": "translated", "value": "Войти в Codex"}}, + "uk": {"stringUnit": {"state": "translated", "value": "Увійти в Codex"}}, + "bs": {"stringUnit": {"state": "translated", "value": "Prijavite se u Codex"}}, + "vi": {"stringUnit": {"state": "translated", "value": "Đăng nhập vào Codex"}}, + "tr": {"stringUnit": {"state": "translated", "value": "Codex’te oturum aç"}}, + "th": {"stringUnit": {"state": "translated", "value": "ลงชื่อเข้าใช้ Codex"}}, + "km": {"stringUnit": {"state": "translated", "value": "ចូលប្រើ Codex"}} } }, "cloud.credentialRecovery.codexBroker": { "extractionState": "manual", "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "cmux uses Codex sign-in on this Mac when needed; an existing broker login is refreshed without another sign-in. Only short-lived access moves to this owner VM. The refresh token stays on this Mac. Keep cmux running on this Mac to renew Codex access." - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "cmux verwendet bei Bedarf die Codex-Anmeldung auf diesem Mac; eine bestehende Broker-Anmeldung wird ohne erneute Anmeldung aktualisiert. Nur der kurzlebige Zugang wird auf diese Eigentümer-VM übertragen. Das Aktualisierungstoken bleibt auf diesem Mac. Lass cmux auf diesem Mac laufen, um den Codex-Zugang zu erneuern." - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "cmux utilise la connexion à Codex sur ce Mac si nécessaire ; une connexion existante au service intermédiaire est renouvelée sans nouvelle connexion. Seul un accès de courte durée est transféré à cette machine virtuelle du propriétaire. Le jeton de renouvellement reste sur ce Mac. Laissez cmux en cours d’exécution sur ce Mac pour renouveler l’accès à Codex." - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "يستخدم cmux تسجيل الدخول إلى Codex على جهاز Mac هذا عند الحاجة؛ ويُجدّد تسجيل الدخول الحالي لدى الوسيط دون الحاجة إلى تسجيل الدخول مجددًا. يُنقل إذن وصول قصير الأجل فقط إلى هذه الآلة الافتراضية الخاصة بالمالك. يبقى رمز التجديد على جهاز Mac هذا. أبقِ cmux قيد التشغيل على جهاز Mac هذا لتجديد الوصول إلى Codex." - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "cmux usa el inicio de sesión de Codex en este Mac cuando es necesario; una sesión existente del intermediario se renueva sin volver a iniciar sesión. Solo se transfiere el acceso de corta duración a esta máquina virtual del propietario. El token de renovación permanece en este Mac. Mantén cmux en ejecución en este Mac para renovar el acceso a Codex." - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "cmux 會在需要時使用這部 Mac 上的 Codex 登入資訊;現有的代理服務登入狀態會自動更新,無須再次登入。只有短期存取權限會傳送至這部擁有者專用的虛擬機器。更新權杖會留在這部 Mac 上。請保持 cmux 在這部 Mac 上執行,以更新 Codex 存取權限。" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "cmux 会在需要时使用这台 Mac 上的 Codex 登录信息;现有的代理服务登录状态会自动刷新,无须再次登录。只有短期访问权限会传送至这台所有者专用的虚拟机。刷新令牌会保留在这台 Mac 上。请保持 cmux 在这台 Mac 上运行,以续期 Codex 访问权限。" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "cmux는 필요할 때 이 Mac의 Codex 로그인을 사용하며, 기존 중개 서비스 로그인은 다시 로그인할 필요 없이 갱신됩니다. 단기 액세스 권한만 이 소유자 VM으로 전송됩니다. 갱신 토큰은 이 Mac에 남습니다. Codex 액세스 권한을 갱신하려면 이 Mac에서 cmux를 계속 실행해 두세요." - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "cmuxは必要に応じてこのMacのCodexログインを使用し、既存の仲介サービスのログインは再ログインなしで更新されます。この所有者用VMには短期間のアクセス権限のみが転送されます。更新トークンはこのMacに残ります。Codexのアクセス権限を更新するには、このMacでcmuxを実行したままにしてください。" - } - } + "en": {"stringUnit": {"state": "translated", "value": "cmux uses Codex sign-in on this Mac when needed; an existing broker login is refreshed without another sign-in. Only short-lived access moves to this owner VM. The refresh token stays on this Mac. Keep cmux running on this Mac to renew Codex access."}}, + "de": {"stringUnit": {"state": "translated", "value": "cmux verwendet bei Bedarf die Codex-Anmeldung auf diesem Mac; eine bestehende Broker-Anmeldung wird ohne erneute Anmeldung aktualisiert. Nur der kurzlebige Zugang wird auf diese Eigentümer-VM übertragen. Das Aktualisierungstoken bleibt auf diesem Mac. Lass cmux auf diesem Mac laufen, um den Codex-Zugang zu erneuern."}}, + "fr": {"stringUnit": {"state": "translated", "value": "cmux utilise la connexion à Codex sur ce Mac si nécessaire ; une connexion existante au service intermédiaire est renouvelée sans nouvelle connexion. Seul un accès de courte durée est transféré à cette machine virtuelle du propriétaire. Le jeton de renouvellement reste sur ce Mac. Laissez cmux en cours d’exécution sur ce Mac pour renouveler l’accès à Codex."}}, + "ar": {"stringUnit": {"state": "translated", "value": "يستخدم cmux تسجيل الدخول إلى Codex على جهاز Mac هذا عند الحاجة؛ ويُجدّد تسجيل الدخول الحالي لدى الوسيط دون الحاجة إلى تسجيل الدخول مجددًا. يُنقل إذن وصول قصير الأجل فقط إلى هذه الآلة الافتراضية الخاصة بالمالك. يبقى رمز التجديد على جهاز Mac هذا. أبقِ cmux قيد التشغيل على جهاز Mac هذا لتجديد الوصول إلى Codex."}}, + "es": {"stringUnit": {"state": "translated", "value": "cmux usa el inicio de sesión de Codex en este Mac cuando es necesario; una sesión existente del intermediario se renueva sin volver a iniciar sesión. Solo se transfiere el acceso de corta duración a esta máquina virtual del propietario. El token de renovación permanece en este Mac. Mantén cmux en ejecución en este Mac para renovar el acceso a Codex."}}, + "zh-Hant": {"stringUnit": {"state": "translated", "value": "cmux 會在需要時使用這部 Mac 上的 Codex 登入資訊;現有的代理服務登入狀態會自動更新,無須再次登入。只有短期存取權限會傳送至這部擁有者專用的虛擬機器。更新權杖會留在這部 Mac 上。請保持 cmux 在這部 Mac 上執行,以更新 Codex 存取權限。"}}, + "zh-Hans": {"stringUnit": {"state": "translated", "value": "cmux 会在需要时使用这台 Mac 上的 Codex 登录信息;现有的代理服务登录状态会自动刷新,无须再次登录。只有短期访问权限会传送至这台所有者专用的虚拟机。刷新令牌会保留在这台 Mac 上。请保持 cmux 在这台 Mac 上运行,以续期 Codex 访问权限。"}}, + "ko": {"stringUnit": {"state": "translated", "value": "cmux는 필요할 때 이 Mac의 Codex 로그인을 사용하며, 기존 중개 서비스 로그인은 다시 로그인할 필요 없이 갱신됩니다. 단기 액세스 권한만 이 소유자 VM으로 전송됩니다. 갱신 토큰은 이 Mac에 남습니다. Codex 액세스 권한을 갱신하려면 이 Mac에서 cmux를 계속 실행해 두세요."}}, + "ja": {"stringUnit": {"state": "translated", "value": "cmuxは必要に応じてこのMacのCodexログインを使用し、既存の仲介サービスのログインは再ログインなしで更新されます。この所有者用VMには短期間のアクセス権限のみが転送されます。更新トークンはこのMacに残ります。Codexのアクセス権限を更新するには、このMacでcmuxを実行したままにしてください。"}}, + "it": {"stringUnit": {"state": "translated", "value": "cmux usa l’accesso a Codex su questo Mac quando necessario; un accesso esistente al servizio intermediario viene rinnovato senza un nuovo accesso. Solo l’accesso di breve durata viene trasferito a questa macchina virtuale del proprietario. Il token di rinnovo rimane su questo Mac. Mantieni cmux in esecuzione su questo Mac per rinnovare l’accesso a Codex."}}, + "da": {"stringUnit": {"state": "translated", "value": "cmux bruger Codex-login på denne Mac efter behov; et eksisterende login hos formidlingstjenesten fornyes uden et nyt login. Kun kortvarig adgang overføres til denne virtuelle maskine for ejeren. Fornyelsestokenet bliver på denne Mac. Hold cmux kørende på denne Mac for at forny adgangen til Codex."}}, + "nb": {"stringUnit": {"state": "translated", "value": "cmux bruker Codex-innlogging på denne Macen ved behov; en eksisterende innlogging hos formidlingstjenesten fornyes uten en ny innlogging. Bare kortvarig tilgang overføres til denne virtuelle maskinen for eieren. Fornyelsestokenet blir på denne Macen. La cmux kjøre på denne Macen for å fornye tilgangen til Codex."}}, + "pl": {"stringUnit": {"state": "translated", "value": "cmux w razie potrzeby korzysta z logowania do Codex na tym Macu; istniejące logowanie w usłudze pośredniczącej jest odświeżane bez ponownego logowania. Do tej maszyny wirtualnej właściciela przenoszony jest tylko krótkotrwały dostęp. Token odświeżania pozostaje na tym Macu. Pozostaw cmux uruchomiony na tym Macu, aby odnawiać dostęp do Codex."}}, + "pt-BR": {"stringUnit": {"state": "translated", "value": "O cmux usa o login do Codex neste Mac quando necessário; um login existente no serviço intermediário é renovado sem exigir um novo login. Somente o acesso de curta duração é transferido para esta máquina virtual do proprietário. O token de renovação permanece neste Mac. Mantenha o cmux em execução neste Mac para renovar o acesso ao Codex."}}, + "ru": {"stringUnit": {"state": "translated", "value": "При необходимости cmux использует вход в Codex на этом Mac; существующий вход в службу-посредник обновляется без повторного входа. На эту виртуальную машину владельца передаётся только краткосрочный доступ. Токен обновления остаётся на этом Mac. Не закрывайте cmux на этом Mac, чтобы продлевать доступ к Codex."}}, + "uk": {"stringUnit": {"state": "translated", "value": "За потреби cmux використовує вхід у Codex на цьому Mac; наявний вхід у службу-посередник оновлюється без повторного входу. На цю віртуальну машину власника передається лише короткочасний доступ. Токен оновлення залишається на цьому Mac. Не закривайте cmux на цьому Mac, щоб поновлювати доступ до Codex."}}, + "bs": {"stringUnit": {"state": "translated", "value": "cmux po potrebi koristi prijavu u Codex na ovom Macu; postojeća prijava u posredničku uslugu obnavlja se bez ponovne prijave. Samo kratkotrajni pristup prenosi se na ovu virtuelnu mašinu vlasnika. Token za obnavljanje ostaje na ovom Macu. Ostavite cmux pokrenut na ovom Macu da bi se pristup Codexu obnavljao."}}, + "vi": {"stringUnit": {"state": "translated", "value": "cmux sử dụng thông tin đăng nhập Codex trên máy Mac này khi cần; phiên đăng nhập hiện có của dịch vụ trung gian được làm mới mà không cần đăng nhập lại. Chỉ quyền truy cập ngắn hạn được chuyển sang máy ảo này của chủ sở hữu. Mã thông báo làm mới vẫn ở trên máy Mac này. Hãy giữ cmux chạy trên máy Mac này để gia hạn quyền truy cập Codex."}}, + "tr": {"stringUnit": {"state": "translated", "value": "cmux gerektiğinde bu Mac’teki Codex oturumunu kullanır; aracı hizmetteki mevcut oturum, yeniden oturum açmadan yenilenir. Sahibin bu VM’sine yalnızca kısa süreli erişim aktarılır. Yenileme belirteci bu Mac’te kalır. Codex erişimini yenilemek için bu Mac’te cmux’u çalışır durumda tutun."}}, + "th": {"stringUnit": {"state": "translated", "value": "cmux ใช้การลงชื่อเข้าใช้ Codex บน Mac เครื่องนี้เมื่อจำเป็น โดยจะรีเฟรชการลงชื่อเข้าใช้บริการตัวกลางที่มีอยู่โดยไม่ต้องลงชื่อเข้าใช้อีกครั้ง เฉพาะสิทธิ์เข้าถึงระยะสั้นเท่านั้นที่จะโอนไปยัง VM ของเจ้าของเครื่องนี้ โทเค็นรีเฟรชจะอยู่บน Mac เครื่องนี้ เปิด cmux บน Mac เครื่องนี้ไว้เพื่อต่ออายุสิทธิ์เข้าถึง Codex"}}, + "km": {"stringUnit": {"state": "translated", "value": "cmux ប្រើការចូលប្រើ Codex នៅលើ Mac នេះ នៅពេលចាំបាច់។ ការចូលប្រើសេវាអន្តរការីដែលមានស្រាប់ត្រូវបានបន្តសុពលភាពដោយមិនចាំបាច់ចូលម្តងទៀត។ មានតែសិទ្ធិចូលប្រើរយៈពេលខ្លីប៉ុណ្ណោះដែលត្រូវបានផ្ទេរទៅ VM របស់ម្ចាស់នេះ។ ថូខឹនបន្តសុពលភាពនៅតែស្ថិតលើ Mac នេះ។ ទុកឱ្យ cmux ដំណើរការនៅលើ Mac នេះ ដើម្បីបន្តសិទ្ធិចូលប្រើ Codex។"}} } } },