Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config/scripts/locale-ko-key-overrides.json
Original file line number Diff line number Diff line change
Expand Up @@ -6264,7 +6264,7 @@
"ko": "이 호스트의 orca CLI가 Orca를 제어하도록 허용"
},
"auto.components.settings.SshTargetForm.allowRemoteCliControlHelp": {
"ko": "기본적으로 켜져 있습니다. 이 호스트에서 실행한 orca 명령이 이 호스트의 작업 공간에서만, 연결되어 있는 동안에만 이 데스크톱의 Orca 브라우저 탭을 열고 조작할 수 있게 하고, 이 호스트에서 이전 버전의 Orca가 시작한 터미널이 자신이 감독받는 작업에 대해 오케스트레이션 명령(check, inbox, send, ask, reply)을 실행할 수 있게 합니다. 파일, 클립보드, 쿠키는 읽을 수 없습니다. 끄면 이 호스트 자체의 Orca 서버에만 접근합니다."
"ko": "새 호스트에서는 기본적으로 켜져 있습니다. 이 호스트에서 실행한 orca 명령이 이 호스트의 작업 공간에서만, 연결되어 있는 동안에만 이 데스크톱의 Orca 브라우저 탭을 열고 조작할 수 있게 하고, 이 호스트에서 이전 버전의 Orca가 시작한 터미널이 자신이 감독받는 작업에 대해 오케스트레이션 명령(check, inbox, send, ask, reply)을 실행할 수 있게 합니다. 파일, 클립보드, 쿠키는 읽을 수 없습니다. 끄면 이 호스트 자체의 Orca 서버에만 접근합니다."
},
"auto.components.settings.AdvancedNetworkSettingsSection.3e431564b5": {
"ko": "localhost, 127.0.0.1, *.internal"
Expand Down
124 changes: 124 additions & 0 deletions src/main/persistence-ssh-remote-cli-control-migration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {
closeTestStores,
testState,
createStore,
writeDataFile,
readDataFile
} from './persistence-test-harness'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { rmSync, mkdtempSync } from 'node:fs'
import { join } from 'node:path'
import { tmpdir } from 'node:os'
import type { SshTarget } from '../shared/ssh-types'
import { isRemoteCliControlAllowed } from '../shared/ssh-remote-cli-control'
import { SshConnectionStore } from './ssh/ssh-connection-store'

vi.mock('./ssh/ssh-config-parser', () => ({
loadUserSshConfig: vi.fn(),
sshConfigHostsToTargets: vi.fn()
}))
vi.mock('electron', () => ({
app: { getPath: () => testState.dir },
safeStorage: {
isEncryptionAvailable: () => true,
encryptString: (plaintext: string) => Buffer.from(`encrypted:${plaintext}`, 'utf-8'),
decryptString: (ciphertext: Buffer) => ciphertext.toString('utf-8').slice('encrypted:'.length)
}
}))
vi.mock('./telemetry/client', () => ({ track: vi.fn() }))
vi.mock('./telemetry/cohort-classifier', () => ({
getCohortAtEmit: () => ({ nth_repo_added: 2 })
}))

const host = (id: string, extra: Partial<SshTarget> = {}): SshTarget => ({
id,
label: id,
host: `${id}.example.com`,
port: 22,
username: 'dev',
...extra
})

function writeProfile(sshTargets: SshTarget[], settings: Record<string, unknown> = {}): void {
writeDataFile({
schemaVersion: 1,
repos: [],
worktreeMeta: {},
settings,
ui: {},
githubCache: { pr: {}, issue: {} },
workspaceSession: {},
sshTargets
})
}

async function reopenStore(): Promise<ReturnType<typeof createStore>> {
await closeTestStores()
return createStore()
}

// The orcad serve host persists its SSH targets through this same Store load path.
describe("pinning existing hosts' orca CLI control switch on upgrade", () => {
beforeEach(() => {
testState.dir = mkdtempSync(join(tmpdir(), 'orca-test-'))
})

afterEach(async () => {
await closeTestStores()
rmSync(testState.dir, { recursive: true, force: true })
})

it('turns an existing absent switch off and keeps explicit values', async () => {
writeProfile([
host('absent'),
host('on', { allowRemoteCliControl: true }),
host('off', { allowRemoteCliControl: false })
])

const store = await createStore()

expect(store.getSshTarget('absent')?.allowRemoteCliControl).toBe(false)
expect(isRemoteCliControlAllowed(store.getSshTarget('absent'))).toBe(false)
expect(store.getSshTarget('on')?.allowRemoteCliControl).toBe(true)
expect(store.getSshTarget('off')?.allowRemoteCliControl).toBe(false)
expect(store.getSettings().sshRemoteCliControlExistingHostsMigrated).toBe(true)

store.flush()
expect(readDataFile()).toMatchObject({
sshTargets: expect.arrayContaining([
expect.objectContaining({ id: 'absent', allowRemoteCliControl: false })
]),
settings: expect.objectContaining({ sshRemoteCliControlExistingHostsMigrated: true })
})
})

it('runs once, so hosts added afterwards keep the on-by-default switch', async () => {
writeProfile([host('existing')])
const store = await createStore()
const added = new SshConnectionStore(store).addTarget({
label: 'new',
host: 'new.example.com',
port: 22,
username: 'dev',
allowRemoteCliControl: true
})
store.flush()

const reopened = await reopenStore()

expect(reopened.getSshTarget('existing')?.allowRemoteCliControl).toBe(false)
expect(reopened.getSshTarget(added.id)?.allowRemoteCliControl).toBe(true)
expect(isRemoteCliControlAllowed(reopened.getSshTarget(added.id))).toBe(true)
})

it('does not re-run after a downgrade wrote an absent switch alongside the marker', async () => {
// An older build keeps unknown settings keys, so the marker survives its saves.
writeProfile([host('added-by-older-build')], {
sshRemoteCliControlExistingHostsMigrated: true
})

const store = await createStore()

expect(store.getSshTarget('added-by-older-build')).not.toHaveProperty('allowRemoteCliControl')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { SshTarget } from '../../../shared/ssh-types'

/**
* Builds before the on-by-default flip saved an explicit "off" as absent, so an absent switch on a
* host that existed before this upgrade may be a deliberate opt-out. Pin those off once; hosts
* added afterwards keep absent=on via `isRemoteCliControlAllowed`.
*/
export function migrateExistingHostsRemoteCliControl(
targets: SshTarget[],
alreadyMigrated: boolean,
markNeedsSave: () => void
): SshTarget[] {
if (alreadyMigrated) {
return targets
}
// Why always save: the marker itself must reach disk, even with no hosts to pin.
markNeedsSave()
return targets.map((target) =>
target.allowRemoteCliControl === undefined
? { ...target, allowRemoteCliControl: false }
: target
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export function normalizeLoadedGlobalSettings(
disabledTuiAgents: migratedDisabledTuiAgents,
...migratedAgentYoloDefaults,
claudeAgentTeamsDefaultDisabledMigrated: true,
sshRemoteCliControlExistingHostsMigrated: true,
openInApplications: normalizeOpenInApplications(parsed.settings?.openInApplications, {
seedDefaults: true
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
normalizeSshRemotePtyLease,
normalizeSshTarget
} from '../leasing-ssh-ptys/ssh-normalization'
import { migrateExistingHostsRemoteCliControl } from '../leasing-ssh-ptys/ssh-remote-cli-control-migration'
import {
normalizeLegacyPaneKeyAliasEntries,
normalizeMigrationUnsupportedPtyEntries
Expand Down Expand Up @@ -89,7 +90,11 @@ export function normalizeLoadedProfileState(
workspaceSession,
markNeedsSave
),
sshTargets: (parsed.sshTargets ?? []).map(normalizeSshTarget),
sshTargets: migrateExistingHostsRemoteCliControl(
(parsed.sshTargets ?? []).map(normalizeSshTarget),
parsed.settings?.sshRemoteCliControlExistingHostsMigrated === true,
markNeedsSave
),
deletedSshConfigAliases: Array.isArray(parsed.deletedSshConfigAliases)
? parsed.deletedSshConfigAliases.filter((alias): alias is string => typeof alias === 'string')
: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export function SshHostAdvancedFields({
<p className="text-muted-foreground">
{translate(
'auto.components.settings.SshTargetForm.allowRemoteCliControlHelp',
"On by default. Lets orca commands run on this host open and drive Orca browser tabs on this desktop, only in this host's workspaces and only while it is connected, and lets terminals started by an older Orca on this host run orchestration commands (check, inbox, send, ask, reply) for their own supervised work. They cannot read your files, clipboard or cookies. When off, they reach only this host's own Orca server."
"On by default for new hosts. Lets orca commands run on this host open and drive Orca browser tabs on this desktop, only in this host's workspaces and only while it is connected, and lets terminals started by an older Orca on this host run orchestration commands (check, inbox, send, ask, reply) for their own supervised work. They cannot read your files, clipboard or cookies. When off, they reach only this host's own Orca server."
)}
</p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -8995,7 +8995,7 @@
"8c922dffba": "Reuse SSH connection for faster setup",
"53e9aabfc0": "Uses OpenSSH multiplexing when available. Turn off for hosts with custom SSH restrictions.",
"allowRemoteCliControl": "Allow this host's orca CLI to control Orca",
"allowRemoteCliControlHelp": "On by default. Lets orca commands run on this host open and drive Orca browser tabs on this desktop, only in this host's workspaces and only while it is connected, and lets terminals started by an older Orca on this host run orchestration commands (check, inbox, send, ask, reply) for their own supervised work. They cannot read your files, clipboard or cookies. When off, they reach only this host's own Orca server.",
"allowRemoteCliControlHelp": "On by default for new hosts. Lets orca commands run on this host open and drive Orca browser tabs on this desktop, only in this host's workspaces and only while it is connected, and lets terminals started by an older Orca on this host run orchestration commands (check, inbox, send, ask, reply) for their own supervised work. They cannot read your files, clipboard or cookies. When off, they reach only this host's own Orca server.",
"editTitle": "Edit SSH host",
"addTitle": "Add SSH host",
"editDescription": "Update connection details for this machine. Changes apply on next connect.",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -8718,7 +8718,7 @@
"addDescription": "Agrega una máquina persistente en la que puedas iniciar sesión por SSH.",
"editingPrefix": "Editando",
"allowRemoteCliControl": "Permitir que la orca CLI de este host controle Orca",
"allowRemoteCliControlHelp": "Activado de forma predeterminada. Permite que los comandos orca ejecutados en este host abran y controlen pestañas del navegador de Orca en este escritorio, solo en los espacios de trabajo de este host y solo mientras esté conectado, y que las terminales iniciadas por una versión anterior de Orca en este host ejecuten comandos de orquestación (check, inbox, send, ask, reply) para su propio trabajo supervisado. No pueden leer tus archivos, tu portapapeles ni tus cookies. Cuando está desactivado, solo llegan al propio servidor de Orca de este host."
"allowRemoteCliControlHelp": "Activado de forma predeterminada en los hosts nuevos. Permite que los comandos orca ejecutados en este host abran y controlen pestañas del navegador de Orca en este escritorio, solo en los espacios de trabajo de este host y solo mientras esté conectado, y que las terminales iniciadas por una versión anterior de Orca en este host ejecuten comandos de orquestación (check, inbox, send, ask, reply) para su propio trabajo supervisado. No pueden leer tus archivos, tu portapapeles ni tus cookies. Cuando está desactivado, solo llegan al propio servidor de Orca de este host."
},
"TasksPane": {
"f71d8a9dd3": "Proveedores de tareas",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -8868,7 +8868,7 @@
"addDescription": "Ajoutez une machine permanente sur laquelle vous pouvez vous connecter en SSH.",
"editingPrefix": "Modification",
"allowRemoteCliControl": "Autoriser l'orca CLI de cet hôte à contrôler Orca",
"allowRemoteCliControlHelp": "Activé par défaut. Permet aux commandes orca exécutées sur cet hôte d'ouvrir et de piloter des onglets du navigateur Orca sur ce bureau, uniquement dans les espaces de travail de cet hôte et uniquement tant qu'il est connecté, et aux terminaux lancés par une ancienne version d'Orca sur cet hôte d'exécuter des commandes d'orchestration (check, inbox, send, ask, reply) pour leur propre travail supervisé. Elles ne peuvent pas lire vos fichiers, votre presse-papiers ni vos cookies. Lorsque cette option est désactivée, elles n'atteignent que le serveur Orca de cet hôte."
"allowRemoteCliControlHelp": "Activé par défaut pour les nouveaux hôtes. Permet aux commandes orca exécutées sur cet hôte d'ouvrir et de piloter des onglets du navigateur Orca sur ce bureau, uniquement dans les espaces de travail de cet hôte et uniquement tant qu'il est connecté, et aux terminaux lancés par une ancienne version d'Orca sur cet hôte d'exécuter des commandes d'orchestration (check, inbox, send, ask, reply) pour leur propre travail supervisé. Elles ne peuvent pas lire vos fichiers, votre presse-papiers ni vos cookies. Lorsque cette option est désactivée, elles n'atteignent que le serveur Orca de cet hôte."
},
"TasksPane": {
"f71d8a9dd3": "Fournisseurs de tâches",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -8744,7 +8744,7 @@
"addDescription": "SSH でログインできる永続的なマシンを追加します。",
"editingPrefix": "編集中",
"allowRemoteCliControl": "このホストの orca CLI による Orca の操作を許可する",
"allowRemoteCliControlHelp": "デフォルトでオンです。このホストで実行される orca コマンドが、このホストのワークスペース内に限り、接続中のみ、このデスクトップの Orca ブラウザのタブを開いて操作できるようにし、このホストで古い Orca が起動したターミナルが自身の監督下の作業についてオーケストレーションコマンド(check、inbox、send、ask、reply)を実行できるようにします。ファイル、クリップボード、Cookie は読み取れません。オフの場合、このホスト自身の Orca サーバーにのみアクセスします。"
"allowRemoteCliControlHelp": "新しいホストではデフォルトでオンです。このホストで実行される orca コマンドが、このホストのワークスペース内に限り、接続中のみ、このデスクトップの Orca ブラウザのタブを開いて操作できるようにし、このホストで古い Orca が起動したターミナルが自身の監督下の作業についてオーケストレーションコマンド(check、inbox、send、ask、reply)を実行できるようにします。ファイル、クリップボード、Cookie は読み取れません。オフの場合、このホスト自身の Orca サーバーにのみアクセスします。"
},
"TasksPane": {
"f71d8a9dd3": "タスクプロバイダー",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/locales/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -8707,7 +8707,7 @@
"addDescription": "SSH로 로그인할 수 있는 영구 머신을 추가합니다.",
"editingPrefix": "편집 중",
"allowRemoteCliControl": "이 호스트의 orca CLI가 Orca를 제어하도록 허용",
"allowRemoteCliControlHelp": "기본적으로 켜져 있습니다. 이 호스트에서 실행한 orca 명령이 이 호스트의 작업 공간에서만, 연결되어 있는 동안에만 이 데스크톱의 Orca 브라우저 탭을 열고 조작할 수 있게 하고, 이 호스트에서 이전 버전의 Orca가 시작한 터미널이 자신이 감독받는 작업에 대해 오케스트레이션 명령(check, inbox, send, ask, reply)을 실행할 수 있게 합니다. 파일, 클립보드, 쿠키는 읽을 수 없습니다. 끄면 이 호스트 자체의 Orca 서버에만 접근합니다."
"allowRemoteCliControlHelp": "새 호스트에서는 기본적으로 켜져 있습니다. 이 호스트에서 실행한 orca 명령이 이 호스트의 작업 공간에서만, 연결되어 있는 동안에만 이 데스크톱의 Orca 브라우저 탭을 열고 조작할 수 있게 하고, 이 호스트에서 이전 버전의 Orca가 시작한 터미널이 자신이 감독받는 작업에 대해 오케스트레이션 명령(check, inbox, send, ask, reply)을 실행할 수 있게 합니다. 파일, 클립보드, 쿠키는 읽을 수 없습니다. 끄면 이 호스트 자체의 Orca 서버에만 접근합니다."
},
"TasksPane": {
"f71d8a9dd3": "작업 제공자",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -8707,7 +8707,7 @@
"addDescription": "添加一台可通过 SSH 登录的持久机器。",
"editingPrefix": "正在编辑",
"allowRemoteCliControl": "允许此主机的 orca CLI 控制 Orca",
"allowRemoteCliControlHelp": "默认开启。允许在此主机上运行的 orca 命令打开并操作此桌面上的 Orca 浏览器标签页,但仅限此主机的工作区,且仅在主机连接期间;并允许此主机上由旧版 Orca 启动的终端为其自身受监督的工作运行编排命令(check、inbox、send、ask、reply)。它们无法读取你的文件、剪贴板或 Cookie。关闭时,它们只能访问此主机自身的 Orca 服务器。"
"allowRemoteCliControlHelp": "新主机默认开启。允许在此主机上运行的 orca 命令打开并操作此桌面上的 Orca 浏览器标签页,但仅限此主机的工作区,且仅在主机连接期间;并允许此主机上由旧版 Orca 启动的终端为其自身受监督的工作运行编排命令(check、inbox、send、ask、reply)。它们无法读取你的文件、剪贴板或 Cookie。关闭时,它们只能访问此主机自身的 Orca 服务器。"
},
"TasksPane": {
"f71d8a9dd3": "任务提供者",
Expand Down
1 change: 1 addition & 0 deletions src/shared/default-global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ export function buildDefaultSettings(args: {
pluginConsents: {},
devPluginPaths: [],
claudeAgentTeamsDefaultDisabledMigrated: true,
sshRemoteCliControlExistingHostsMigrated: true,
skipDeleteWorktreeConfirm: false,
alwaysForceDeleteWorktrees: false,
alwaysForceDeletePreservedBranches: false,
Expand Down
2 changes: 2 additions & 0 deletions src/shared/global-settings-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ export type GlobalSettings = GlobalSettingsBase & {
devPluginPaths: string[]
/** One-shot guard: start Claude Agent Teams hidden for existing profiles without overriding later opt-ins. */
claudeAgentTeamsDefaultDisabledMigrated?: boolean
/** One-shot guard: hosts saved before the remote CLI control default flip were pinned off. */
sshRemoteCliControlExistingHostsMigrated?: boolean
/** Why: closing a terminal with child processes kills foreground work; keep this skip separate from other confirmations. */
skipCloseTerminalWithRunningProcessConfirm: boolean
/** Why: deleting an automation also deletes its run history; keep this skip separate from worktree deletion. */
Expand Down
Loading