From 297d6f3a2d08b152276b6930cfe007ce99d3e52e Mon Sep 17 00:00:00 2001 From: Neil Date: Fri, 2 Oct 2026 01:19:38 -0700 Subject: [PATCH] Manage OpenCode and Devin profiles in account Settings --- docs/reference/managed-data-accounts.md | 23 ++ .../src/components/settings/AccountsPane.tsx | 8 + .../settings/ManagedDataAccountsSection.tsx | 232 ++++++++++++++++++ src/renderer/src/i18n/locales/en.json | 16 ++ src/renderer/src/i18n/locales/es.json | 16 ++ src/renderer/src/i18n/locales/fr.json | 16 ++ src/renderer/src/i18n/locales/ja.json | 16 ++ src/renderer/src/i18n/locales/ko.json | 16 ++ src/renderer/src/i18n/locales/zh.json | 16 ++ 9 files changed, 359 insertions(+) create mode 100644 docs/reference/managed-data-accounts.md create mode 100644 src/renderer/src/components/settings/ManagedDataAccountsSection.tsx diff --git a/docs/reference/managed-data-accounts.md b/docs/reference/managed-data-accounts.md new file mode 100644 index 00000000000..a8db7c6762f --- /dev/null +++ b/docs/reference/managed-data-accounts.md @@ -0,0 +1,23 @@ +# Managed OpenCode and Devin accounts + +Run enrollment in a terminal on the machine running Orca: + +```sh +orca account add --agent opencode --label Work +orca account add --agent opencode --integration opencode-go --label Work +orca account add --agent devin --label Work +orca account list --agent opencode --json +orca account select --agent opencode --account +orca account select --agent opencode --account system +orca account remove --agent opencode --account +``` + +OpenCode enrollment requires OpenCode 2 and runs its official `auth login --standalone` command. Devin runs `auth login --force-manual-token-flow`; obtain the enrollment token through Devin's supported login flow. These commands neither reuse a guessed token nor sign out the system account. Settings → AI Provider Accounts provides the enrollment command, refresh, selection, and removal for the selected Orca host. + +Each profile belongs to the execution host. OpenCode's SQLite credentials and Devin's credential TOML stay in private Orca user-data directories. Enrollment isolates XDG data/config/cache/state, copies only authenticated credentials, and then deletes the temporary directory. OpenCode capture rejects databases containing conversations and includes SQLite WAL contents. RPC summaries contain labels, IDs, and integration names, never tokens or credential paths. Only the authenticated local runtime socket can import a credential directory; paired clients cannot ask the host to read arbitrary paths. + +Selection affects newly launched explicit OpenCode/Devin commands and agent launches. It redirects XDG data and state; OpenCode inline-auth/database overrides cannot bypass the profile. Shell wrappers restore this selection after user startup files. Existing provider configuration and environment-based integrations remain available. Running terminals retain their current profile. Stop agents before removing a profile: removal also deletes conversations created in that private profile, without changing the system login. + +For SSH, enroll by running the command on a headless Orca runtime on the remote machine. The remote runtime owns its profiles and selection; a desktop client's credential paths never cross SSH. Direct SSH relay launches and Windows-hosted WSL panes do not consume the desktop host's profiles. Run a headless runtime inside that execution environment instead. Folder workspaces use the same host account store as git worktrees. Older Orca hosts reject new operations before login through capability negotiation. + +Validation currently covers OpenCode 2.0.16 on macOS, real isolated login and selected terminal authentication, Devin 3000.10.31 saved-login recognition, and the Node headless runtime. Fresh Devin manual-token enrollment, a physical SSH host, Linux, and Windows still require verification; these are not claimed as tested. diff --git a/src/renderer/src/components/settings/AccountsPane.tsx b/src/renderer/src/components/settings/AccountsPane.tsx index 1e479ab7e25..557cf4af122 100644 --- a/src/renderer/src/components/settings/AccountsPane.tsx +++ b/src/renderer/src/components/settings/AccountsPane.tsx @@ -61,6 +61,8 @@ import { renderOpenCodeAccountsSection } from './accounts-pane-provider-setting-sections' import { renderMiniMaxAccountsSection } from './accounts-pane-minimax-section' +import { ManagedDataAccountsSection } from './ManagedDataAccountsSection' +import { getActiveRuntimeTarget } from '@/runtime/runtime-client-target' import { renderAccountsRemovalDialogs } from './accounts-pane-removal-dialogs' export { getAccountsPaneSearchEntries } @@ -370,6 +372,12 @@ export function AccountsPane({ clearMiniMaxCookie } const visibleSections = [ + !searchQuery || /opencode|devin|account/i.test(searchQuery) ? ( +
+ + +
+ ) : null, wslSupportedPlatform && !isRemoteAccountScope && matchesSettingsSearch(searchQuery, getAccountsLocationSearchEntries()) diff --git a/src/renderer/src/components/settings/ManagedDataAccountsSection.tsx b/src/renderer/src/components/settings/ManagedDataAccountsSection.tsx new file mode 100644 index 00000000000..0f0fdbecedd --- /dev/null +++ b/src/renderer/src/components/settings/ManagedDataAccountsSection.tsx @@ -0,0 +1,232 @@ +import { useEffect, useState } from 'react' +import { translate } from '@/i18n/i18n' +import { callRuntimeRpc, type RuntimeClientTarget } from '@/runtime/runtime-rpc-client' +import type { + ManagedDataAccountProvider, + ManagedDataAccountsState +} from '../../../../shared/managed-account-types' +import { Button } from '../ui/button' +import { Badge } from '../ui/badge' +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter +} from '../ui/dialog' + +type Snapshot = { opencode?: ManagedDataAccountsState; devin?: ManagedDataAccountsState } + +export function ManagedDataAccountsSection({ + provider, + target +}: { + provider: ManagedDataAccountProvider + target: RuntimeClientTarget +}): React.JSX.Element { + const [state, setState] = useState(null) + const [error, setError] = useState(null) + const [busy, setBusy] = useState(false) + const [removeId, setRemoveId] = useState(null) + const [copied, setCopied] = useState(false) + const environmentId = target.kind === 'environment' ? target.environmentId : null + + useEffect(() => { + const controller = new AbortController() + const requestTarget: RuntimeClientTarget = environmentId + ? { kind: 'environment', environmentId } + : { kind: 'local' } + void callRuntimeRpc(requestTarget, 'accounts.listData', undefined, { + signal: controller.signal + }) + .then((snapshot) => { + if (!controller.signal.aborted) { + setState(snapshot[provider] ?? null) + } + }) + .catch((cause: unknown) => { + if (!controller.signal.aborted) { + setError(cause instanceof Error ? cause.message : String(cause)) + } + }) + return () => controller.abort() + }, [provider, environmentId]) + + async function refresh(): Promise { + setBusy(true) + setError(null) + try { + const snapshot = await callRuntimeRpc(target, 'accounts.listData') + setState(snapshot[provider] ?? null) + } catch (cause) { + setError(cause instanceof Error ? cause.message : String(cause)) + } finally { + setBusy(false) + } + } + + async function mutate(action: 'select' | 'remove', accountId: string | null): Promise { + setBusy(true) + setError(null) + try { + setState( + await callRuntimeRpc(target, `accounts.${action}Data`, { + provider, + accountId + }) + ) + } catch (cause) { + setError(cause instanceof Error ? cause.message : String(cause)) + } finally { + setBusy(false) + } + } + + const command = `orca account add --agent ${provider}` + return ( +
+

{provider === 'opencode' ? 'OpenCode' : 'Devin'}

+

+ {translate( + 'accounts.managedData.description', + 'Add accounts by running this command in a terminal on the Orca host. Selection applies to new explicit agent launches on that host; direct SSH relay and Windows-hosted WSL launches use their own credentials.' + )} +

+ {command} +
+ + +
+ {!state && !error && ( +

+ {translate( + 'accounts.managedData.upgrade', + 'If accounts do not appear, update or restart the Orca host.' + )} +

+ )} + {error && ( +

+ {error} +

+ )} + {state && ( + <> +
+ + {translate('accounts.managedData.system', 'System default')} + + +
+ {state.accounts.map((account) => ( +
+
+ {account.label} +

{account.integrations.join(', ')}

+
+
+ {state.activeAccountId === account.id ? ( + + {translate('accounts.managedData.active', 'Active')} + + ) : ( + + )} + +
+
+ ))} + + )} + { + if (!open) { + setRemoveId(null) + } + }} + > + + + + {translate('accounts.managedData.removeTitle', 'Remove managed account?')} + + + {translate( + 'accounts.managedData.removeDescription', + 'Stop agents using this profile first. Removal deletes its saved credentials and conversation data. Your system login stays unchanged.' + )} + + + + + + + + +
+ ) +} diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 2f29e560803..c37adaa1122 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -18742,5 +18742,21 @@ "addTypedPath": "Add", "noDirectories": "No folders added yet.", "removePath": "Remove {{name}}" + }, + "accounts": { + "managedData": { + "description": "Add accounts by running this command in a terminal on the Orca host. Selection applies to new explicit agent launches on that host; direct SSH relay and Windows-hosted WSL launches use their own credentials.", + "copied": "Copied", + "add": "Copy add account command", + "refresh": "Refresh accounts", + "upgrade": "If accounts do not appear, update or restart the Orca host.", + "system": "System default", + "active": "Active", + "select": "Select", + "remove": "Remove", + "removeTitle": "Remove managed account?", + "removeDescription": "Stop agents using this profile first. Removal deletes its saved credentials and conversation data. Your system login stays unchanged.", + "cancel": "Cancel" + } } } diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 45afe6f04a0..40fa622722b 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -15550,5 +15550,21 @@ "tooLargeInDiff": "El archivo supera el límite de vista previa de {{limit}}. Cambia al modo fuente para ver las diferencias.", "renderAnyway": "Renderizar de todos modos" } + }, + "accounts": { + "managedData": { + "description": "Añade cuentas ejecutando este comando en una terminal del host de Orca. La selección se aplica a nuevos inicios del agente en ese host; las conexiones SSH directas y WSL alojado en Windows usan sus propias credenciales.", + "copied": "Copiado", + "add": "Copiar comando para añadir cuenta", + "refresh": "Actualizar cuentas", + "upgrade": "Si las cuentas no aparecen, actualiza o reinicia el host de Orca.", + "system": "Predeterminada del sistema", + "active": "Activa", + "select": "Seleccionar", + "remove": "Eliminar", + "removeTitle": "¿Eliminar la cuenta administrada?", + "removeDescription": "Detén primero los agentes que usan este perfil. Al eliminarlo se borran sus credenciales y conversaciones guardadas. Tu inicio de sesión del sistema no cambia.", + "cancel": "Cancelar" + } } } diff --git a/src/renderer/src/i18n/locales/fr.json b/src/renderer/src/i18n/locales/fr.json index 85a1b64c154..46ee70baf12 100644 --- a/src/renderer/src/i18n/locales/fr.json +++ b/src/renderer/src/i18n/locales/fr.json @@ -18657,5 +18657,21 @@ "addTypedPath": "Add", "noDirectories": "No folders added yet.", "removePath": "Remove {{name}}" + }, + "accounts": { + "managedData": { + "description": "Ajoutez des comptes en exécutant cette commande dans un terminal sur l’hôte Orca. La sélection s’applique aux nouveaux lancements sur cet hôte ; les connexions SSH directes et WSL sous Windows utilisent leurs propres identifiants.", + "copied": "Copié", + "add": "Copier la commande d’ajout de compte", + "refresh": "Actualiser les comptes", + "upgrade": "Si les comptes n’apparaissent pas, mettez à jour ou redémarrez l’hôte Orca.", + "system": "Compte système par défaut", + "active": "Actif", + "select": "Sélectionner", + "remove": "Supprimer", + "removeTitle": "Supprimer le compte géré ?", + "removeDescription": "Arrêtez d’abord les agents utilisant ce profil. Sa suppression efface les identifiants et conversations enregistrés. Votre connexion système reste inchangée.", + "cancel": "Annuler" + } } } diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 0bed9bab422..8fe5e4b07ab 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -18657,5 +18657,21 @@ "addTypedPath": "Add", "noDirectories": "No folders added yet.", "removePath": "Remove {{name}}" + }, + "accounts": { + "managedData": { + "description": "Orcaホストのターミナルでこのコマンドを実行してアカウントを追加します。選択はそのホストでの新しいエージェント起動に適用されます。直接SSH接続とWindows上のWSLは独自の認証情報を使用します。", + "copied": "コピーしました", + "add": "アカウント追加コマンドをコピー", + "refresh": "アカウントを更新", + "upgrade": "アカウントが表示されない場合はOrcaホストを更新または再起動してください。", + "system": "システムの既定", + "active": "使用中", + "select": "選択", + "remove": "削除", + "removeTitle": "管理アカウントを削除しますか?", + "removeDescription": "まずこのプロファイルを使うエージェントを停止してください。削除すると保存済みの認証情報と会話データが消去されます。システムのログインには影響しません。", + "cancel": "キャンセル" + } } } diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index a503c3347d8..ba445093524 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -18657,5 +18657,21 @@ "addTypedPath": "Add", "noDirectories": "No folders added yet.", "removePath": "Remove {{name}}" + }, + "accounts": { + "managedData": { + "description": "Orca 호스트의 터미널에서 이 명령을 실행하여 계정을 추가하세요. 선택은 해당 호스트에서 새로 시작하는 에이전트에 적용됩니다. 직접 SSH 연결과 Windows에서 호스팅하는 WSL은 자체 자격 증명을 사용합니다.", + "copied": "복사됨", + "add": "계정 추가 명령 복사", + "refresh": "계정 새로 고침", + "upgrade": "계정이 나타나지 않으면 Orca 호스트를 업데이트하거나 다시 시작하세요.", + "system": "시스템 기본값", + "active": "활성", + "select": "선택", + "remove": "삭제", + "removeTitle": "관리 계정을 삭제할까요?", + "removeDescription": "먼저 이 프로필을 사용하는 에이전트를 중지하세요. 삭제하면 저장된 자격 증명과 대화 데이터가 지워집니다. 시스템 로그인은 변경되지 않습니다.", + "cancel": "취소" + } } } diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 63fc2b87de5..f443dc9acd5 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -18657,5 +18657,21 @@ "addTypedPath": "Add", "noDirectories": "No folders added yet.", "removePath": "Remove {{name}}" + }, + "accounts": { + "managedData": { + "description": "在 Orca 主机的终端中运行此命令以添加账户。选择适用于该主机上新启动的代理;直接 SSH 连接和 Windows 托管的 WSL 使用各自的凭据。", + "copied": "已复制", + "add": "复制添加账户命令", + "refresh": "刷新账户", + "upgrade": "如果账户未显示,请更新或重启 Orca 主机。", + "system": "系统默认", + "active": "当前使用", + "select": "选择", + "remove": "移除", + "removeTitle": "移除托管账户?", + "removeDescription": "请先停止使用此配置的代理。移除将删除其保存的凭据和对话数据。系统登录不受影响。", + "cancel": "取消" + } } }