fix(runtime): harden orphan recovery validation

This commit is contained in:
OrcaWin
2026-07-22 22:16:50 -07:00
parent 1fd5a20318
commit 711838207e
17 changed files with 318 additions and 82 deletions
+1
View File
@@ -1442,6 +1442,7 @@
"src/renderer/src/runtime/web-session-tabs-sync.test.ts",
"tests/e2e/remote-terminal-tab-retirement.unit.test.ts",
"src/main/runtime/orca-runtime.test.ts",
"src/main/runtime/terminal-orphan-owner.test.ts",
"src/main/runtime/terminal-orphan-topology.test.ts",
"src/renderer/src/runtime/web-session-terminal-orphan-recovery.test.ts",
"src/renderer/src/runtime/web-session-terminal-orphan-mixed-version.test.ts"
+3 -7
View File
@@ -79,9 +79,7 @@ export function registerRuntimeEnvironmentHandlers(store: Store): void {
}
ipcMain.removeAllListeners('runtimeEnvironments:subscriptionBinary')
ipcMain.handle('runtimeEnvironments:list', (): PublicKnownRuntimeEnvironment[] =>
listPublicRuntimeEnvironments()
)
ipcMain.handle('runtimeEnvironments:list', listPublicRuntimeEnvironments)
ipcMain.handle(
'runtimeEnvironments:addFromPairingCode',
(
@@ -91,10 +89,8 @@ export function registerRuntimeEnvironmentHandlers(store: Store): void {
environment: redactRuntimeEnvironment(addEnvironmentFromPairingCode(getUserDataPath(), args))
})
)
ipcMain.handle(
'runtimeEnvironments:resolve',
(_event, args: { selector: string }): PublicKnownRuntimeEnvironment =>
redactRuntimeEnvironment(resolveEnvironment(getUserDataPath(), args.selector))
ipcMain.handle('runtimeEnvironments:resolve', (_event, args: { selector: string }) =>
redactRuntimeEnvironment(resolveEnvironment(getUserDataPath(), args.selector))
)
ipcMain.handle(
'runtimeEnvironments:remove',
+17
View File
@@ -9,6 +9,7 @@ const {
previewGhosttyImportMock,
previewWarpThemeImportMock,
prepareLocalWorktreeRootsForReposMock,
resolveEnvironmentMock,
rebuildAppMenuMock
} = vi.hoisted(() => ({
applyAppIconMock: vi.fn(),
@@ -19,10 +20,12 @@ const {
previewGhosttyImportMock: vi.fn(),
previewWarpThemeImportMock: vi.fn(),
prepareLocalWorktreeRootsForReposMock: vi.fn(),
resolveEnvironmentMock: vi.fn(),
rebuildAppMenuMock: vi.fn()
}))
vi.mock('electron', () => ({
app: { getPath: vi.fn(() => '/test/user-data') },
BrowserWindow: { getAllWindows: browserWindowGetAllWindowsMock },
ipcMain: { handle: handleMock, on: onMock },
nativeTheme: { themeSource: 'system' }
@@ -52,6 +55,10 @@ vi.mock('../menu/register-app-menu', () => ({
rebuildAppMenu: rebuildAppMenuMock
}))
vi.mock('../../shared/runtime-environment-store', () => ({
resolveEnvironment: resolveEnvironmentMock
}))
import { registerSettingsHandlers } from './settings'
const settingsInvokeEvent = { sender: { id: 1 } }
@@ -79,6 +86,12 @@ describe('registerSettingsHandlers', () => {
previewGhosttyImportMock.mockClear()
previewWarpThemeImportMock.mockClear()
prepareLocalWorktreeRootsForReposMock.mockReset().mockResolvedValue(undefined)
resolveEnvironmentMock.mockReset().mockImplementation((_userDataPath, selector) => {
if (selector !== 'windows-2') {
throw new Error('Runtime environment not found')
}
return { id: selector }
})
rebuildAppMenuMock.mockClear()
browserWindowGetAllWindowsMock.mockReset()
store.getSettings.mockReset()
@@ -143,6 +156,10 @@ describe('registerSettingsHandlers', () => {
expect(() => handler(settingsInvokeEvent, { environmentId: 42 as never })).toThrow(
'Invalid Active Server preference'
)
expect(() => handler(settingsInvokeEvent, { environmentId: 'does-not-exist' })).toThrow(
'Runtime environment not found'
)
expect(store.updateSettings).toHaveBeenCalledTimes(1)
})
it('applies bot-author deltas against the authoritative settings snapshot', () => {
+5 -1
View File
@@ -1,4 +1,4 @@
import { BrowserWindow, ipcMain, nativeTheme } from 'electron'
import { app, BrowserWindow, ipcMain, nativeTheme } from 'electron'
import type { Store } from '../persistence'
import type { GlobalSettings, PersistedState } from '../../shared/types'
import { listSystemFontFamilies } from '../system-fonts'
@@ -22,6 +22,7 @@ import { normalizeTerminalLineHeight } from '../../shared/terminal-line-height-s
import { prepareLocalWorktreeRootsForRepos } from '../worktree-root-preparation'
import { scheduleCurrentWorktreeBaseDirectoryWatcherSync } from './worktree-base-directory-watcher'
import { applyPRBotAuthorOverride } from '../../shared/pr-bot-author-overrides'
import { resolveEnvironment } from '../../shared/runtime-environment-store'
// Why: the whitelist is the source-of-truth for which keys we emit on. Casting
// to a Set once at module load lets the IPC handler's per-key membership
@@ -216,6 +217,9 @@ export function registerSettingsHandlers(
throw new Error('Invalid Active Server preference')
}
const environmentId = requestedEnvironmentId?.trim() || null
if (environmentId !== null) {
resolveEnvironment(app.getPath('userData'), environmentId)
}
return store.updateSettings(
{ activeRuntimeEnvironmentId: environmentId },
{ notifyListeners: true, originWebContentsId: event.sender.id }
+30 -15
View File
@@ -15942,7 +15942,8 @@ describe('OrcaRuntimeService', () => {
terminalHandle,
title,
cwd: TEST_WORKTREE_PATH,
worktreeId: TEST_WORKTREE_ID
worktreeId: TEST_WORKTREE_ID,
wslDistro: null
}))
})
const before = await runtime.listTerminals(`id:${TEST_WORKTREE_ID}`)
@@ -16113,7 +16114,8 @@ describe('OrcaRuntimeService', () => {
terminalHandle: 'term_new',
title: 'Terminal 3',
cwd: TEST_WORKTREE_PATH,
worktreeId: TEST_WORKTREE_ID
worktreeId: TEST_WORKTREE_ID,
wslDistro: null
},
{
id: 'pty-agent',
@@ -16121,7 +16123,8 @@ describe('OrcaRuntimeService', () => {
terminalHandle: 'term_agent',
title: 'Claude',
cwd: TEST_WORKTREE_PATH,
worktreeId: TEST_WORKTREE_ID
worktreeId: TEST_WORKTREE_ID,
wslDistro: null
},
{
id: 'pty-setup',
@@ -16129,7 +16132,8 @@ describe('OrcaRuntimeService', () => {
terminalHandle: 'term_setup',
title: 'Setup',
cwd: TEST_WORKTREE_PATH,
worktreeId: TEST_WORKTREE_ID
worktreeId: TEST_WORKTREE_ID,
wslDistro: null
},
{
id: 'pty-shell',
@@ -16137,7 +16141,8 @@ describe('OrcaRuntimeService', () => {
terminalHandle: 'term_shell',
title: 'Shell',
cwd: TEST_WORKTREE_PATH,
worktreeId: TEST_WORKTREE_ID
worktreeId: TEST_WORKTREE_ID,
wslDistro: null
}
]
})
@@ -16280,7 +16285,8 @@ describe('OrcaRuntimeService', () => {
terminalHandle: 'term_alias',
title: 'Alias shell',
cwd: TEST_WORKTREE_PATH,
worktreeId: TEST_WORKTREE_ID
worktreeId: TEST_WORKTREE_ID,
wslDistro: null
}
]
})
@@ -16337,7 +16343,8 @@ describe('OrcaRuntimeService', () => {
terminalHandle: 'term_current',
title: 'Current shell',
cwd: TEST_WORKTREE_PATH,
worktreeId: TEST_WORKTREE_ID
worktreeId: TEST_WORKTREE_ID,
wslDistro: null
}
]
: []
@@ -16412,7 +16419,8 @@ describe('OrcaRuntimeService', () => {
terminalHandle: 'term_cas',
title: 'shell',
cwd: TEST_WORKTREE_PATH,
worktreeId: TEST_WORKTREE_ID
worktreeId: TEST_WORKTREE_ID,
wslDistro: null
}
]
})
@@ -16456,7 +16464,8 @@ describe('OrcaRuntimeService', () => {
terminalHandle: 'term_wrong_owner',
title: 'shell',
cwd: TEST_WORKTREE_PATH,
worktreeId: TEST_WORKTREE_ID
worktreeId: TEST_WORKTREE_ID,
wslDistro: null
}
]
})
@@ -16495,7 +16504,8 @@ describe('OrcaRuntimeService', () => {
terminalHandle: 'term_reused',
title: 'shell',
cwd: TEST_WORKTREE_PATH,
worktreeId: TEST_WORKTREE_ID
worktreeId: TEST_WORKTREE_ID,
wslDistro: null
},
{
id: 'pty-second',
@@ -16503,7 +16513,8 @@ describe('OrcaRuntimeService', () => {
terminalHandle: 'term_reused',
title: 'shell',
cwd: TEST_WORKTREE_PATH,
worktreeId: TEST_WORKTREE_ID
worktreeId: TEST_WORKTREE_ID,
wslDistro: null
}
]
})
@@ -16620,7 +16631,8 @@ describe('OrcaRuntimeService', () => {
terminalHandle,
title: id,
cwd: TEST_WORKTREE_PATH,
worktreeId: TEST_WORKTREE_ID
worktreeId: TEST_WORKTREE_ID,
wslDistro: null
}))
})
@@ -16720,7 +16732,8 @@ describe('OrcaRuntimeService', () => {
terminalHandle: 'term_old',
title: 'old',
cwd: TEST_WORKTREE_PATH,
worktreeId: TEST_WORKTREE_ID
worktreeId: TEST_WORKTREE_ID,
wslDistro: null
}
runtime.setPtyController({
write: () => true,
@@ -16790,7 +16803,8 @@ describe('OrcaRuntimeService', () => {
terminalHandle: 'term_orphan',
title: 'orphan',
cwd: TEST_WORKTREE_PATH,
worktreeId: TEST_WORKTREE_ID
worktreeId: TEST_WORKTREE_ID,
wslDistro: null
}
]
})
@@ -16859,7 +16873,8 @@ describe('OrcaRuntimeService', () => {
terminalHandle: 'term_duplicate',
title: 'duplicate',
cwd: TEST_WORKTREE_PATH,
worktreeId: TEST_WORKTREE_ID
worktreeId: TEST_WORKTREE_ID,
wslDistro: null
}
]
})
+1 -1
View File
@@ -13057,7 +13057,7 @@ export class OrcaRuntimeService {
this.hydrateHeadlessMobileSessionTabsFromWorkspaceSession(worktree.id, {
force: true,
allowAttachedWindow: true,
onlyServeOwnedTerminals: true
onlyRuntimeOwnedTerminals: true
})
this.notifyMobileSessionTabsChanged(worktree.id)
return {
@@ -1,5 +1,6 @@
import { z } from 'zod'
import type { TabGroupLayoutNode } from '../../../../shared/types'
import { isPtyIncarnationId, type PtyIncarnationId } from '../../../../shared/pty-incarnation'
import { defineMethod, type RpcAnyMethod } from '../core'
import { OptionalString, requiredString } from '../schemas'
import { TerminalPaneLayoutNodeSchema } from './session-tabs-schemas'
@@ -78,6 +79,11 @@ const TerminalOrphanTopology = z.object({
groupLayout: TerminalOrphanGroupLayout.optional()
})
const TerminalOrphanIncarnationId = z.custom<PtyIncarnationId>(
isPtyIncarnationId,
'Invalid PTY incarnation'
)
const TerminalAdoptOrphans = z.object({
worktree: requiredString('Missing worktree selector').pipe(z.string().max(32_768)),
expectedTopologyRevision: z.number().int().nonnegative(),
@@ -86,7 +92,7 @@ const TerminalAdoptOrphans = z.object({
z.object({
terminal: requiredString('Missing terminal handle').pipe(z.string().max(256)),
ptyId: requiredString('Missing PTY id').pipe(z.string().max(8_192)),
incarnationId: requiredString('Missing PTY incarnation').pipe(z.string().max(256)),
incarnationId: TerminalOrphanIncarnationId,
tabId: requiredString('Missing tab id').pipe(z.string().max(256)),
leafId: requiredString('Missing leaf id').pipe(z.string().max(128))
})
+5
View File
@@ -123,6 +123,11 @@ describe('RPC optional pipe schemas', () => {
expectedTopologyRevision: 4,
claims: [{ ...claim, incarnationId: '' }]
})
expectRejects(adopt, {
worktree: 'id:repo::/worktree',
expectedTopologyRevision: 4,
claims: [{ ...claim, incarnationId: 'i'.repeat(129) }]
})
expectRejects(adopt, {
worktree: 'id:repo::/worktree',
expectedTopologyRevision: 4,
@@ -512,7 +512,12 @@ export function RuntimeEnvironmentsPane({
try {
if (allowLocalRuntime && settings.activeRuntimeEnvironmentId === environment.id) {
if (mountedRef.current) {
setRemoveError('Choose another Active Server in Advanced before removing this server.')
setRemoveError(
translate(
'auto.components.settings.RuntimeEnvironmentsPane.removeActiveServerBlocked',
'Choose another Active Server in Advanced before removing this server.'
)
)
}
return false
}
@@ -1382,8 +1387,8 @@ export function RuntimeEnvironmentsPane({
{removingActiveServer
? allowLocalRuntime
? translate(
'auto.components.settings.RuntimeEnvironmentsPane.9f7665a01b',
'Removing the active server first switches Orca back to Local desktop. Existing host sessions are left alone.'
'auto.components.settings.RuntimeEnvironmentsPane.removeActiveServerDescription',
'Choose another Active Server in Advanced before removing this server. Existing host sessions are left alone.'
)
: translate(
'auto.components.settings.RuntimeEnvironmentsPane.b2fda48c39',
+26 -5
View File
@@ -204,7 +204,8 @@
"slices": {
"browser": {
"d175274b6d": "New Browser Tab",
"08fc23631d": "Browser"
"08fc23631d": "Browser",
"remoteCookieImportUnavailable": "Manual cookie file import is unavailable while a remote runtime is active."
},
"editor": {
"dcb521ed29": "This file is in a conflict state, but no working-tree file is available to edit.",
@@ -1273,7 +1274,17 @@
"noRunTargets": "No run targets are ready for this project.",
"perWorkspaceEnvHint": "Provision an on-demand environment from a recipe",
"branchName": "Branch name",
"branchNamePlaceholder": "feature/my-branch"
"branchNamePlaceholder": "feature/my-branch",
"connectTimedOut": "Connection timed out. It may still be connecting in the background.",
"connectingHost": "Connecting…",
"connectHost": "Connect",
"addHost": "Add host",
"addHostHint": "Register another machine or Orca server",
"addSshHost": "Add SSH host",
"addSshHostHint": "Use an existing machine over SSH",
"addRemoteOrcaServer": "Add Remote Orca Server",
"addRemoteOrcaServerHint": "Pair another Orca runtime",
"hostConnectionFailed": "Connection failed"
},
"NewWorkspaceComposerModal": {
"createWorktree": "Create worktree",
@@ -4581,7 +4592,14 @@
"worktree": {
"flow": {
"c460fecc4a": "Failed to sleep some workspaces",
"8bc3fc0671": "Failed to sleep workspace"
"8bc3fc0671": "Failed to sleep workspace",
"legacy": {
"unverified": "The older host runtime could not confirm terminal shutdown. The workspace was kept open; update the host and try again."
},
"host": {
"unverified": "The host could not confirm terminal shutdown. The workspace was kept open; check the connection and try again."
},
"retry": "The workspace was kept open. Try again; if the problem continues, check the host connection."
}
}
},
@@ -4848,7 +4866,8 @@
"sshImportSynced": "Synced {{value0}} host{{value1}}.",
"sshImportFailed": "Failed to import SSH config.",
"importing": "Importing...",
"importSshConfig": "Import ~/.ssh/config"
"importSshConfig": "Import ~/.ssh/config",
"advanced": "Advanced"
},
"ForgetSshWorkspaceDialog": {
"reconnectFailed": "Reconnection failed",
@@ -6514,7 +6533,9 @@
"updateServer": "Update",
"reviewServerUpdates": "Check for Server Updates",
"updatingServers": "Updating servers…",
"orcaVersion": "Orca v{{value0}}"
"orcaVersion": "Orca v{{value0}}",
"removeActiveServerBlocked": "Choose another Active Server in Advanced before removing this server.",
"removeActiveServerDescription": "Choose another Active Server in Advanced before removing this server. Existing host sessions are left alone."
},
"RuntimePairingGeneratedUrlRows": {
"0495f68959": "Copy {{value0}}"
+43 -6
View File
@@ -181,7 +181,8 @@
"slices": {
"browser": {
"d175274b6d": "Nueva pestaña del navegador",
"08fc23631d": "Navegador"
"08fc23631d": "Navegador",
"remoteCookieImportUnavailable": "Manual cookie file import is unavailable while a remote runtime is active."
},
"editor": {
"dcb521ed29": "Este archivo está en conflicto, pero no hay una copia editable en el worktree.",
@@ -1250,7 +1251,17 @@
"noRunTargets": "No hay destinos de ejecución listos para este proyecto.",
"perWorkspaceEnvHint": "Provisiona un entorno bajo demanda a partir de una receta",
"branchName": "Nombre de rama",
"branchNamePlaceholder": "feature/my-branch"
"branchNamePlaceholder": "feature/my-branch",
"connectTimedOut": "Connection timed out. It may still be connecting in the background.",
"connectingHost": "Connecting…",
"connectHost": "Connect",
"addHost": "Add host",
"addHostHint": "Register another machine or Orca server",
"addSshHost": "Add SSH host",
"addSshHostHint": "Use an existing machine over SSH",
"addRemoteOrcaServer": "Add Remote Orca Server",
"addRemoteOrcaServerHint": "Pair another Orca runtime",
"hostConnectionFailed": "Connection failed"
},
"NewWorkspaceComposerModal": {
"createWorktree": "Crear worktree",
@@ -4423,7 +4434,23 @@
"0bed8727db": "Puede que se haya movido o eliminado. Actualiza los espacios de trabajo o quítalo de Orca.",
"3921d3d9a5": "No se encontró la carpeta del espacio de trabajo.",
"f387af445b": "La ruta del espacio de trabajo no es una ruta local válida.",
"3ec372b664": "gestor de archivos"
"3ec372b664": "gestor de archivos",
"localOnly": "Local only",
"remoteSsh": "Remote SSH",
"remoteRuntimeUnsupported": "Opening this path in a local app is not available.",
"remoteRuntimeUnsupportedDetail": "Switch to a local or SSH workspace, then try again.",
"sshTargetNotFound": "SSH host is no longer available.",
"sshTargetNotFoundDetail": "Refresh workspaces or reconnect the host, then try again.",
"sshTargetInvalid": "SSH host configuration is incomplete.",
"sshTargetInvalidDetail": "Edit or reconnect the SSH host, then try again.",
"sshAliasRequired": "VS Code needs an SSH config alias for this host.",
"sshAliasRequiredDetail": "Add a Host alias for {{host}}:{{port}} to your local SSH config, reconnect the workspace, then try again.",
"remoteEditorUnsupported": "This app cannot open SSH workspaces.",
"remoteEditorUnsupportedDetail": "Choose VS Code or use the app locally.",
"remotePathInvalid": "Path is not valid for the SSH host.",
"remotePathInvalidDetail": "Refresh the workspace before trying again.",
"remoteLaunchFailed": "Could not open the path in VS Code.",
"remoteLaunchFailedDetail": "Check the VS Code command configured on this machine."
},
"WorktreeTitleInlineRename": {
"2f42ae024f": "No leído:",
@@ -4519,7 +4546,14 @@
"worktree": {
"flow": {
"c460fecc4a": "No se pudieron poner en reposo algunos espacios de trabajo",
"8bc3fc0671": "No se pudo poner en reposo el espacio de trabajo"
"8bc3fc0671": "No se pudo poner en reposo el espacio de trabajo",
"legacy": {
"unverified": "The older host runtime could not confirm terminal shutdown. The workspace was kept open; update the host and try again."
},
"host": {
"unverified": "The host could not confirm terminal shutdown. The workspace was kept open; check the connection and try again."
},
"retry": "The workspace was kept open. Try again; if the problem continues, check the host connection."
}
}
},
@@ -4809,7 +4843,8 @@
"sshImportFailed": "No se pudo importar la configuración SSH.",
"importing": "Importando...",
"importSshConfig": "Importar ~/.ssh/config",
"sshPersistenceDefault": "Los terminales remotos en este host seguirán activos hasta que los cierres o restablezcas el relay."
"sshPersistenceDefault": "Los terminales remotos en este host seguirán activos hasta que los cierres o restablezcas el relay.",
"advanced": "Advanced"
},
"ForgetSshWorkspaceDialog": {
"reconnectFailed": "Error de reconexión",
@@ -6437,7 +6472,9 @@
"versionUnavailable": "Orca version unavailable",
"updateServer": "Update",
"reviewServerUpdates": "Server updates",
"orcaVersion": "Orca v{{value0}}"
"orcaVersion": "Orca v{{value0}}",
"removeActiveServerBlocked": "Choose another Active Server in Advanced before removing this server.",
"removeActiveServerDescription": "Choose another Active Server in Advanced before removing this server. Existing host sessions are left alone."
},
"RuntimePairingGeneratedUrlRows": {
"0495f68959": "Copiar {{value0}}"
+43 -6
View File
@@ -181,7 +181,8 @@
"slices": {
"browser": {
"d175274b6d": "新規ブラウザタブ",
"08fc23631d": "ブラウザ"
"08fc23631d": "ブラウザ",
"remoteCookieImportUnavailable": "Manual cookie file import is unavailable while a remote runtime is active."
},
"editor": {
"dcb521ed29": "このファイルは競合状態にありますが、編集できる作業ツリー ファイルがありません。",
@@ -1250,7 +1251,17 @@
"noRunTargets": "このプロジェクトには実行ターゲットが準備されていません。",
"perWorkspaceEnvHint": "レシピからオンデマンド環境をプロビジョニングする",
"branchName": "ブランチ名",
"branchNamePlaceholder": "feature/my-branch"
"branchNamePlaceholder": "feature/my-branch",
"connectTimedOut": "Connection timed out. It may still be connecting in the background.",
"connectingHost": "Connecting…",
"connectHost": "Connect",
"addHost": "Add host",
"addHostHint": "Register another machine or Orca server",
"addSshHost": "Add SSH host",
"addSshHostHint": "Use an existing machine over SSH",
"addRemoteOrcaServer": "Add Remote Orca Server",
"addRemoteOrcaServerHint": "Pair another Orca runtime",
"hostConnectionFailed": "Connection failed"
},
"NewWorkspaceComposerModal": {
"createWorktree": "ワークツリーを作成する",
@@ -4404,7 +4415,23 @@
"0bed8727db": "移動または削除された可能性があります。ワークスペースを更新するか、Orca からワークスペースを削除します。",
"3921d3d9a5": "ワークスペースフォルダーが見つかりませんでした。",
"f387af445b": "ワークスペース パスは有効なローカル パスではありません。",
"3ec372b664": "ファイルマネージャー"
"3ec372b664": "ファイルマネージャー",
"localOnly": "Local only",
"remoteSsh": "Remote SSH",
"remoteRuntimeUnsupported": "Opening this path in a local app is not available.",
"remoteRuntimeUnsupportedDetail": "Switch to a local or SSH workspace, then try again.",
"sshTargetNotFound": "SSH host is no longer available.",
"sshTargetNotFoundDetail": "Refresh workspaces or reconnect the host, then try again.",
"sshTargetInvalid": "SSH host configuration is incomplete.",
"sshTargetInvalidDetail": "Edit or reconnect the SSH host, then try again.",
"sshAliasRequired": "VS Code needs an SSH config alias for this host.",
"sshAliasRequiredDetail": "Add a Host alias for {{host}}:{{port}} to your local SSH config, reconnect the workspace, then try again.",
"remoteEditorUnsupported": "This app cannot open SSH workspaces.",
"remoteEditorUnsupportedDetail": "Choose VS Code or use the app locally.",
"remotePathInvalid": "Path is not valid for the SSH host.",
"remotePathInvalidDetail": "Refresh the workspace before trying again.",
"remoteLaunchFailed": "Could not open the path in VS Code.",
"remoteLaunchFailedDetail": "Check the VS Code command configured on this machine."
},
"WorktreeTitleInlineRename": {
"2f42ae024f": "未読:",
@@ -4500,7 +4527,14 @@
"worktree": {
"flow": {
"c460fecc4a": "一部のワークスペースのスリープに失敗しました",
"8bc3fc0671": "ワークスペースのスリープに失敗しました"
"8bc3fc0671": "ワークスペースのスリープに失敗しました",
"legacy": {
"unverified": "The older host runtime could not confirm terminal shutdown. The workspace was kept open; update the host and try again."
},
"host": {
"unverified": "The host could not confirm terminal shutdown. The workspace was kept open; check the connection and try again."
},
"retry": "The workspace was kept open. Try again; if the problem continues, check the host connection."
}
}
},
@@ -4809,7 +4843,8 @@
"sshImportFailed": "SSH 構成のインポートに失敗しました。",
"importing": "インポート中...",
"importSshConfig": "~/.ssh/config をインポートします",
"sshPersistenceDefault": "このホスト上のリモートターミナルは、終了するかリレーをリセットするまで動作し続けます。"
"sshPersistenceDefault": "このホスト上のリモートターミナルは、終了するかリレーをリセットするまで動作し続けます。",
"advanced": "Advanced"
},
"ForgetSshWorkspaceDialog": {
"reconnectFailed": "再接続に失敗しました",
@@ -6459,7 +6494,9 @@
"versionUnavailable": "Orca version unavailable",
"updateServer": "Update",
"reviewServerUpdates": "Server updates",
"orcaVersion": "Orca v{{value0}}"
"orcaVersion": "Orca v{{value0}}",
"removeActiveServerBlocked": "Choose another Active Server in Advanced before removing this server.",
"removeActiveServerDescription": "Choose another Active Server in Advanced before removing this server. Existing host sessions are left alone."
},
"RuntimePairingGeneratedUrlRows": {
"0495f68959": "{{value0}}をコピー"
+43 -6
View File
@@ -181,7 +181,8 @@
"slices": {
"browser": {
"d175274b6d": "새 브라우저 탭",
"08fc23631d": "브라우저"
"08fc23631d": "브라우저",
"remoteCookieImportUnavailable": "Manual cookie file import is unavailable while a remote runtime is active."
},
"editor": {
"dcb521ed29": "이 파일은 충돌 상태에 있지만 편집할 수 있는 작업 트리 파일이 없습니다.",
@@ -1250,7 +1251,17 @@
"noRunTargets": "이 프로젝트에는 실행 대상이 준비되어 있지 않습니다.",
"perWorkspaceEnvHint": "레시피에서 온디맨드 환경 프로비저닝",
"branchName": "브랜치 이름",
"branchNamePlaceholder": "feature/my-branch"
"branchNamePlaceholder": "feature/my-branch",
"connectTimedOut": "Connection timed out. It may still be connecting in the background.",
"connectingHost": "Connecting…",
"connectHost": "Connect",
"addHost": "Add host",
"addHostHint": "Register another machine or Orca server",
"addSshHost": "Add SSH host",
"addSshHostHint": "Use an existing machine over SSH",
"addRemoteOrcaServer": "Add Remote Orca Server",
"addRemoteOrcaServerHint": "Pair another Orca runtime",
"hostConnectionFailed": "Connection failed"
},
"NewWorkspaceComposerModal": {
"createWorktree": "작업 트리 만들기",
@@ -4404,7 +4415,23 @@
"0bed8727db": "이동되었거나 삭제되었을 수 있습니다. 워크스페이스를 새로고침하거나 Orca에서 제거하세요.",
"3921d3d9a5": "워크스페이스 폴더를 찾을 수 없습니다.",
"f387af445b": "워크스페이스 경로가 유효한 로컬 경로가 아닙니다.",
"3ec372b664": "파일 관리자"
"3ec372b664": "파일 관리자",
"localOnly": "Local only",
"remoteSsh": "Remote SSH",
"remoteRuntimeUnsupported": "Opening this path in a local app is not available.",
"remoteRuntimeUnsupportedDetail": "Switch to a local or SSH workspace, then try again.",
"sshTargetNotFound": "SSH host is no longer available.",
"sshTargetNotFoundDetail": "Refresh workspaces or reconnect the host, then try again.",
"sshTargetInvalid": "SSH host configuration is incomplete.",
"sshTargetInvalidDetail": "Edit or reconnect the SSH host, then try again.",
"sshAliasRequired": "VS Code needs an SSH config alias for this host.",
"sshAliasRequiredDetail": "Add a Host alias for {{host}}:{{port}} to your local SSH config, reconnect the workspace, then try again.",
"remoteEditorUnsupported": "This app cannot open SSH workspaces.",
"remoteEditorUnsupportedDetail": "Choose VS Code or use the app locally.",
"remotePathInvalid": "Path is not valid for the SSH host.",
"remotePathInvalidDetail": "Refresh the workspace before trying again.",
"remoteLaunchFailed": "Could not open the path in VS Code.",
"remoteLaunchFailedDetail": "Check the VS Code command configured on this machine."
},
"WorktreeTitleInlineRename": {
"2f42ae024f": "읽지 않음:",
@@ -4500,7 +4527,14 @@
"worktree": {
"flow": {
"c460fecc4a": "일부 워크스페이스를 절전 모드로 전환하지 못했습니다.",
"8bc3fc0671": "워크스페이스를 절전 모드로 전환하지 못했습니다."
"8bc3fc0671": "워크스페이스를 절전 모드로 전환하지 못했습니다.",
"legacy": {
"unverified": "The older host runtime could not confirm terminal shutdown. The workspace was kept open; update the host and try again."
},
"host": {
"unverified": "The host could not confirm terminal shutdown. The workspace was kept open; check the connection and try again."
},
"retry": "The workspace was kept open. Try again; if the problem continues, check the host connection."
}
}
},
@@ -4809,7 +4843,8 @@
"sshImportFailed": "SSH 구성을 가져오지 못했습니다.",
"importing": "가져오는 중...",
"importSshConfig": "~/.ssh/config 가져오기",
"sshPersistenceDefault": "이 호스트의 원격 터미널은 종료하거나 릴레이를 재설정할 때까지 계속 실행됩니다."
"sshPersistenceDefault": "이 호스트의 원격 터미널은 종료하거나 릴레이를 재설정할 때까지 계속 실행됩니다.",
"advanced": "Advanced"
},
"ForgetSshWorkspaceDialog": {
"reconnectFailed": "재연결 실패",
@@ -6422,7 +6457,9 @@
"versionUnavailable": "Orca version unavailable",
"updateServer": "Update",
"reviewServerUpdates": "Server updates",
"orcaVersion": "Orca v{{value0}}"
"orcaVersion": "Orca v{{value0}}",
"removeActiveServerBlocked": "Choose another Active Server in Advanced before removing this server.",
"removeActiveServerDescription": "Choose another Active Server in Advanced before removing this server. Existing host sessions are left alone."
},
"RuntimePairingGeneratedUrlRows": {
"0495f68959": "{{value0}} 복사"
+43 -6
View File
@@ -181,7 +181,8 @@
"slices": {
"browser": {
"d175274b6d": "新浏览器选项卡",
"08fc23631d": "浏览器"
"08fc23631d": "浏览器",
"remoteCookieImportUnavailable": "Manual cookie file import is unavailable while a remote runtime is active."
},
"editor": {
"dcb521ed29": "该文件处于冲突状态,但没有可编辑的工作树文件。",
@@ -1250,7 +1251,17 @@
"noRunTargets": "此项目还没有可用的运行目标。",
"perWorkspaceEnvHint": "通过环境模板按需创建环境",
"branchName": "分支名称",
"branchNamePlaceholder": "feature/my-branch"
"branchNamePlaceholder": "feature/my-branch",
"connectTimedOut": "Connection timed out. It may still be connecting in the background.",
"connectingHost": "Connecting…",
"connectHost": "Connect",
"addHost": "Add host",
"addHostHint": "Register another machine or Orca server",
"addSshHost": "Add SSH host",
"addSshHostHint": "Use an existing machine over SSH",
"addRemoteOrcaServer": "Add Remote Orca Server",
"addRemoteOrcaServerHint": "Pair another Orca runtime",
"hostConnectionFailed": "Connection failed"
},
"NewWorkspaceComposerModal": {
"createWorktree": "创建工作树",
@@ -4404,7 +4415,23 @@
"0bed8727db": "它可能已被手机或删除。刷新工作区或将其从 Orca 中删除。",
"3921d3d9a5": "找不到工作区文件夹。",
"f387af445b": "工作区路径不是有效的本地路径。",
"3ec372b664": "文件管理器"
"3ec372b664": "文件管理器",
"localOnly": "Local only",
"remoteSsh": "Remote SSH",
"remoteRuntimeUnsupported": "Opening this path in a local app is not available.",
"remoteRuntimeUnsupportedDetail": "Switch to a local or SSH workspace, then try again.",
"sshTargetNotFound": "SSH host is no longer available.",
"sshTargetNotFoundDetail": "Refresh workspaces or reconnect the host, then try again.",
"sshTargetInvalid": "SSH host configuration is incomplete.",
"sshTargetInvalidDetail": "Edit or reconnect the SSH host, then try again.",
"sshAliasRequired": "VS Code needs an SSH config alias for this host.",
"sshAliasRequiredDetail": "Add a Host alias for {{host}}:{{port}} to your local SSH config, reconnect the workspace, then try again.",
"remoteEditorUnsupported": "This app cannot open SSH workspaces.",
"remoteEditorUnsupportedDetail": "Choose VS Code or use the app locally.",
"remotePathInvalid": "Path is not valid for the SSH host.",
"remotePathInvalidDetail": "Refresh the workspace before trying again.",
"remoteLaunchFailed": "Could not open the path in VS Code.",
"remoteLaunchFailedDetail": "Check the VS Code command configured on this machine."
},
"WorktreeTitleInlineRename": {
"2f42ae024f": "未读:",
@@ -4500,7 +4527,14 @@
"worktree": {
"flow": {
"c460fecc4a": "无法睡眠某些工作区",
"8bc3fc0671": "无法睡眠工作区"
"8bc3fc0671": "无法睡眠工作区",
"legacy": {
"unverified": "The older host runtime could not confirm terminal shutdown. The workspace was kept open; update the host and try again."
},
"host": {
"unverified": "The host could not confirm terminal shutdown. The workspace was kept open; check the connection and try again."
},
"retry": "The workspace was kept open. Try again; if the problem continues, check the host connection."
}
}
},
@@ -4809,7 +4843,8 @@
"sshImportFailed": "导入 SSH 配置失败。",
"importing": "正在导入...",
"importSshConfig": "导入 ~/.ssh/config",
"sshPersistenceDefault": "此主机上的远程终端会保持运行,直到你结束它们或重置中继。"
"sshPersistenceDefault": "此主机上的远程终端会保持运行,直到你结束它们或重置中继。",
"advanced": "Advanced"
},
"ForgetSshWorkspaceDialog": {
"reconnectFailed": "重新连接失败",
@@ -6422,7 +6457,9 @@
"versionUnavailable": "Orca version unavailable",
"updateServer": "Update",
"reviewServerUpdates": "Server updates",
"orcaVersion": "Orca v{{value0}}"
"orcaVersion": "Orca v{{value0}}",
"removeActiveServerBlocked": "Choose another Active Server in Advanced before removing this server.",
"removeActiveServerDescription": "Choose another Active Server in Advanced before removing this server. Existing host sessions are left alone."
},
"RuntimePairingGeneratedUrlRows": {
"0495f68959": "复制 {{value0}}"
@@ -2769,16 +2769,22 @@ export function useWebSessionTabsSync(): void {
environmentId
)
)
).then((recovered) => {
if (!disposed) {
const applicable = recovered.filter(
(snapshot): snapshot is RuntimeMobileSessionTabsResult => snapshot !== null
)
applyWebSessionTabsStorePatch((state) =>
applyFreshWebSessionTabsSnapshots(state, applicable, environmentId)
)
}
})
)
.then((recovered) => {
if (!disposed) {
const applicable = recovered.filter(
(snapshot): snapshot is RuntimeMobileSessionTabsResult => snapshot !== null
)
applyWebSessionTabsStorePatch((state) =>
applyFreshWebSessionTabsSnapshots(state, applicable, environmentId)
)
}
})
.catch((error) => {
if (!disposed) {
console.warn('[web-session-tabs-sync] snapshot recovery failed:', error)
}
})
return
}
if (event.type !== 'snapshot' && event.type !== 'updated') {
@@ -2791,13 +2797,19 @@ export function useWebSessionTabsSync(): void {
useAppStore.getState(),
event,
environmentId
).then((recovered) => {
if (!disposed && recovered) {
applyWebSessionTabsStorePatch((state) =>
applyFreshWebSessionTabsSnapshot(state, recovered, environmentId)
)
}
})
)
.then((recovered) => {
if (!disposed && recovered) {
applyWebSessionTabsStorePatch((state) =>
applyFreshWebSessionTabsSnapshot(state, recovered, environmentId)
)
}
})
.catch((error) => {
if (!disposed) {
console.warn('[web-session-tabs-sync] snapshot recovery failed:', error)
}
})
},
onError: (error) => {
console.warn('[web-session-tabs-sync] global subscription error:', error.message)
@@ -2907,7 +2919,7 @@ export function useWebSessionTabsSync(): void {
}
if (!disposed && shouldBootstrapInitialTerminal) {
requestedInitialTerminal = true
void createWebRuntimeSessionTerminal({
await createWebRuntimeSessionTerminal({
worktreeId: activeWorktreeId,
environmentId,
activate: true
@@ -2918,14 +2930,12 @@ export function useWebSessionTabsSync(): void {
beginWebRuntimeWakeTerminalRespawn(activeWorktreeId)
) {
requestedRespawnAfterWake = true
void createWebRuntimeSessionTerminal({
await createWebRuntimeSessionTerminal({
worktreeId: activeWorktreeId,
environmentId,
activate: true,
selectWorktree: false
}).finally(() => {
endWebRuntimeWakeTerminalRespawn(activeWorktreeId)
})
}).finally(() => endWebRuntimeWakeTerminalRespawn(activeWorktreeId))
}
}
void window.api.runtimeEnvironments
@@ -2953,7 +2963,11 @@ export function useWebSessionTabsSync(): void {
if (event.type !== 'snapshot' && event.type !== 'updated') {
return
}
void applyActiveSnapshot(event, response)
void applyActiveSnapshot(event, response).catch((error) => {
if (!disposed) {
console.warn('[web-session-tabs-sync] active snapshot recovery failed:', error)
}
})
},
onError: (error) => {
console.warn('[web-session-tabs-sync] subscription error:', error.message)
+4 -1
View File
@@ -1924,7 +1924,10 @@ export const createBrowserSlice: StateCreator<AppState, [], [], BrowserSlice> =
importCookiesToProfile: async (profileId) => {
const hostId = getBrowserSettingsHostId(get())
if (getBrowserSettingsRuntimeEnvironmentId(get())) {
const reason = 'Manual cookie file import is unavailable while a remote runtime is active.'
const reason = translate(
'auto.store.slices.browser.remoteCookieImportUnavailable',
'Manual cookie file import is unavailable while a remote runtime is active.'
)
set((state) =>
browserImportStateForHostUpdate(state, hostId, {
profileId,
+2 -1
View File
@@ -39,6 +39,7 @@ import type {
import type { StartupCommandDelivery } from './codex-startup-delivery'
import type { RemoteServerUpdateSupport } from './remote-server-update'
import type { ExecutionHostId } from './execution-host'
import type { PtyIncarnationId } from './pty-incarnation'
export type { RuntimeMarkdownReadTabResult, RuntimeMarkdownSaveTabResult }
@@ -490,7 +491,7 @@ export type RuntimeTerminalListResult = {
export type RuntimeTerminalOrphanAdoptionClaim = {
terminal: string
ptyId: string
incarnationId: string
incarnationId: PtyIncarnationId
tabId: string
leafId: string
}