From 74e2c952f1f00f2a2b4d884fa7ea946f6161da49 Mon Sep 17 00:00:00 2001 From: "Yang,Zhou" Date: Sat, 13 Jun 2026 07:10:09 +0800 Subject: [PATCH] feat: add Devin CLI as supported TUI agent (#5282) * feat: add Devin CLI as supported TUI agent Register Devin across shared agent config, telemetry, desktop/mobile catalogs, localization, and README. Uses stdin-after-start prompt delivery with --permission-mode bypass default args. * test: assert Devin default --permission-mode bypass in startup plans --- README.md | 1 + config/scripts/locale-translation-policy.mjs | 1 + mobile/src/tasks/mobile-tui-agents.ts | 4 ++++ src/renderer/src/i18n/locales/en.json | 3 ++- src/renderer/src/i18n/locales/es.json | 3 ++- src/renderer/src/i18n/locales/ja.json | 3 ++- src/renderer/src/i18n/locales/ko.json | 3 ++- src/renderer/src/i18n/locales/zh.json | 3 ++- src/renderer/src/lib/agent-catalog.tsx | 7 +++++++ src/renderer/src/lib/agent-status.ts | 6 ++++-- .../src/lib/tui-agent-startup.test.ts | 18 +++++++++++++++++ src/shared/agent-kind.ts | 3 ++- src/shared/agent-status-types.ts | 1 + src/shared/telemetry-events.ts | 1 + src/shared/tui-agent-config.ts | 11 ++++++++++ src/shared/tui-agent-launch-defaults.ts | 3 ++- src/shared/tui-agent-selection.ts | 1 + src/shared/tui-agent-startup.test.ts | 20 +++++++++++++++++++ src/shared/types.ts | 1 + 19 files changed, 84 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index bdf0caa13..0415570b8 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,7 @@ Works with **any CLI agent** — if it runs in a terminal, it runs in Orca. Pi logo Pi   oh-my-pi logo oh-my-pi   Hermes Agent logo Hermes Agent   + Devin logo Devin   Goose logo Goose   Auggie logo Auggie   Autohand Code logo Autohand Code   diff --git a/config/scripts/locale-translation-policy.mjs b/config/scripts/locale-translation-policy.mjs index 5d49b58eb..8750785fb 100644 --- a/config/scripts/locale-translation-policy.mjs +++ b/config/scripts/locale-translation-policy.mjs @@ -32,6 +32,7 @@ export const NEVER_TRANSLATE_VALUES = new Set([ 'Continue', 'Cursor', 'Droid', + 'Devin', 'Gemini', 'GitHub Copilot', 'Goose', diff --git a/mobile/src/tasks/mobile-tui-agents.ts b/mobile/src/tasks/mobile-tui-agents.ts index 8cecd2434..d2470e011 100644 --- a/mobile/src/tasks/mobile-tui-agents.ts +++ b/mobile/src/tasks/mobile-tui-agents.ts @@ -34,6 +34,7 @@ export const MOBILE_TUI_AGENT_AUTO_PICK_ORDER = [ 'qwen-code', 'rovo', 'hermes', + 'devin', 'openclaw' ] as const satisfies readonly TuiAgent[] @@ -68,6 +69,7 @@ export const MOBILE_TUI_AGENT_LABELS: Record = { 'qwen-code': 'Qwen Code', rovo: 'Rovo Dev', hermes: 'Hermes', + devin: 'Devin', openclaw: 'OpenClaw' } @@ -97,6 +99,7 @@ export const MOBILE_TUI_AGENT_FAVICON_DOMAINS: Partial> 'qwen-code': 'qwenlm.github.io', rovo: 'atlassian.com', hermes: 'nousresearch.com', + devin: 'devin.ai', openclaw: 'openclaw.ai' } @@ -131,6 +134,7 @@ export const MOBILE_TUI_AGENT_LAUNCH_COMMANDS: Record = { 'qwen-code': 'qwen-code', rovo: 'rovo', hermes: 'hermes', + devin: 'devin', openclaw: 'openclaw' } diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 97a7af30e..8e61d3a9e 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -274,7 +274,8 @@ "760bc6883d": "Codex", "a5fc0cb622": "OpenClaude", "bf53f09bf8": "Claude Agent Teams", - "0708ed89f1": "Claude" + "0708ed89f1": "Claude", + "fc80296033": "Devin" }, "skill": { "cli": { diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index b4330c82d..41a42cb76 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -274,7 +274,8 @@ "760bc6883d": "Codex", "a5fc0cb622": "OpenClaude", "bf53f09bf8": "Claude Agent Teams", - "0708ed89f1": "Claude" + "0708ed89f1": "Claude", + "fc80296033": "Devin" }, "skill": { "cli": { diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 5e9b2575c..78f50b6a1 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -274,7 +274,8 @@ "760bc6883d": "Codex", "a5fc0cb622": "OpenClaude", "bf53f09bf8": "Claude Agent Teams", - "0708ed89f1": "Claude" + "0708ed89f1": "Claude", + "fc80296033": "Devin" }, "skill": { "cli": { diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 657684fd4..72d47aae3 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -274,7 +274,8 @@ "760bc6883d": "Codex", "a5fc0cb622": "OpenClaude", "bf53f09bf8": "Claude Agent Teams", - "0708ed89f1": "Claude" + "0708ed89f1": "Claude", + "fc80296033": "Devin" }, "skill": { "cli": { diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 963945017..4a871eecd 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -274,7 +274,8 @@ "760bc6883d": "Codex", "a5fc0cb622": "OpenClaude", "bf53f09bf8": "Claude Agent Teams", - "0708ed89f1": "Claude" + "0708ed89f1": "Claude", + "fc80296033": "Devin" }, "skill": { "cli": { diff --git a/src/renderer/src/lib/agent-catalog.tsx b/src/renderer/src/lib/agent-catalog.tsx index 752ca392b..5ea3eb59c 100644 --- a/src/renderer/src/lib/agent-catalog.tsx +++ b/src/renderer/src/lib/agent-catalog.tsx @@ -244,6 +244,13 @@ export const getAgentCatalog = createLocalizedCatalog((): AgentCatalogEntry[] => faviconDomain: 'nousresearch.com', homepageUrl: 'https://hermes-agent.nousresearch.com/docs/' }, + { + id: 'devin', + label: translate('auto.lib.agent.catalog.fc80296033', 'Devin'), + cmd: 'devin', + faviconDomain: 'devin.ai', + homepageUrl: 'https://devin.ai/cli' + }, { id: 'openclaw', label: translate('auto.lib.agent.catalog.5dff448636', 'OpenClaw'), diff --git a/src/renderer/src/lib/agent-status.ts b/src/renderer/src/lib/agent-status.ts index f413fc838..fa6691364 100644 --- a/src/renderer/src/lib/agent-status.ts +++ b/src/renderer/src/lib/agent-status.ts @@ -126,7 +126,8 @@ const WELL_KNOWN_LABELS: Record = { droid: 'Droid', 'command-code': 'Command Code', grok: 'Grok', - hermes: 'Hermes' + hermes: 'Hermes', + devin: 'Devin' } export function formatAgentTypeLabel(agentType: AgentType | null | undefined): string { @@ -181,7 +182,8 @@ const ICONABLE_AGENT_TYPES: Record = { hermes: true, openclaw: true, copilot: true, - grok: true + grok: true, + devin: true } export function agentTypeToIconAgent(agentType: AgentType | null | undefined): TuiAgent | null { diff --git a/src/renderer/src/lib/tui-agent-startup.test.ts b/src/renderer/src/lib/tui-agent-startup.test.ts index a9d7de160..81be829d8 100644 --- a/src/renderer/src/lib/tui-agent-startup.test.ts +++ b/src/renderer/src/lib/tui-agent-startup.test.ts @@ -4,6 +4,7 @@ import { buildAgentStartupPlan, isShellProcess } from './tui-agent-startup' +import { resolveTuiAgentLaunchArgs } from '../../../shared/tui-agent-launch-defaults' describe('buildAgentStartupPlan', () => { it('passes Claude prompts as a positional interactive argument', () => { @@ -150,6 +151,23 @@ describe('buildAgentStartupPlan', () => { }) }) + it('launches Devin first and injects the prompt after startup', () => { + expect( + buildAgentStartupPlan({ + agent: 'devin', + prompt: 'Trace the failing test', + cmdOverrides: {}, + agentArgs: resolveTuiAgentLaunchArgs('devin', null), + platform: 'linux' + }) + ).toEqual({ + agent: 'devin', + launchCommand: "devin '--permission-mode' 'bypass'", + expectedProcess: 'devin', + followupPrompt: 'Trace the failing test' + }) + }) + it('launches Command Code by its unambiguous binary with a positional prompt', () => { expect( buildAgentStartupPlan({ diff --git a/src/shared/agent-kind.ts b/src/shared/agent-kind.ts index 1ac5f96a5..99eac0522 100644 --- a/src/shared/agent-kind.ts +++ b/src/shared/agent-kind.ts @@ -44,7 +44,8 @@ const TUI_AGENT_KIND_BY_AGENT = { hermes: 'hermes', openclaw: 'openclaw', copilot: 'copilot', - grok: 'grok' + grok: 'grok', + devin: 'devin' } satisfies Record // Why: `satisfies Record` makes the lookup exhaustive at compile diff --git a/src/shared/agent-status-types.ts b/src/shared/agent-status-types.ts index 8f87bd44b..25bb50a0f 100644 --- a/src/shared/agent-status-types.ts +++ b/src/shared/agent-status-types.ts @@ -30,6 +30,7 @@ export type WellKnownAgentType = | 'command-code' | 'grok' | 'hermes' + | 'devin' | 'unknown' export type AgentType = WellKnownAgentType | (string & {}) diff --git a/src/shared/telemetry-events.ts b/src/shared/telemetry-events.ts index 7de381fd6..9f8593b3c 100644 --- a/src/shared/telemetry-events.ts +++ b/src/shared/telemetry-events.ts @@ -100,6 +100,7 @@ export const AGENT_KIND_VALUES = [ 'openclaw', 'copilot', 'grok', + 'devin', 'other' ] as const export const agentKindSchema = z.enum(AGENT_KIND_VALUES) diff --git a/src/shared/tui-agent-config.ts b/src/shared/tui-agent-config.ts index 0e828b432..aaacad5eb 100644 --- a/src/shared/tui-agent-config.ts +++ b/src/shared/tui-agent-config.ts @@ -301,6 +301,17 @@ export const TUI_AGENT_CONFIG: Record = { launchCmd: 'grok', expectedProcess: 'grok', promptInjectionMode: 'stdin-after-start' + }, + devin: { + detectCmd: 'devin', + launchCmd: 'devin', + expectedProcess: 'devin', + // Why: `devin -- ` auto-submits the prompt (the issue's claim + // that it pre-fills without submitting is incorrect per the official + // docs at docs.devin.ai/cli/reference/commands). `stdin-after-start` + // launches the REPL first, then pastes via bracketed paste so the + // user can review before submitting — same as aider, goose, amp, etc. + promptInjectionMode: 'stdin-after-start' } } diff --git a/src/shared/tui-agent-launch-defaults.ts b/src/shared/tui-agent-launch-defaults.ts index 483bcdf09..deb757143 100644 --- a/src/shared/tui-agent-launch-defaults.ts +++ b/src/shared/tui-agent-launch-defaults.ts @@ -28,7 +28,8 @@ export const DEFAULT_TUI_AGENT_ARGS: Partial> = { rovo: '--yolo', hermes: '--yolo', copilot: '--yolo', - grok: '--permission-mode bypassPermissions' + grok: '--permission-mode bypassPermissions', + devin: '--permission-mode bypass' } export const DEFAULT_TUI_AGENT_ENV: Partial>> = { diff --git a/src/shared/tui-agent-selection.ts b/src/shared/tui-agent-selection.ts index b3685f5e8..487a81910 100644 --- a/src/shared/tui-agent-selection.ts +++ b/src/shared/tui-agent-selection.ts @@ -34,6 +34,7 @@ export const TUI_AGENT_AUTO_PICK_ORDER = [ 'qwen-code', 'rovo', 'hermes', + 'devin', 'openclaw' ] as const satisfies readonly TuiAgent[] diff --git a/src/shared/tui-agent-startup.test.ts b/src/shared/tui-agent-startup.test.ts index 7a1f349cf..15bbf5928 100644 --- a/src/shared/tui-agent-startup.test.ts +++ b/src/shared/tui-agent-startup.test.ts @@ -266,4 +266,24 @@ describe('tui agent startup plans', () => { expect(plan?.expectedProcess).toBe('omp') expect(plan?.launchCommand).toBe('omp; unset ORCA_OMP_PREFILL') }) + + it('launches Devin with stdin-after-start prompt delivery', () => { + const plan = buildAgentStartupPlan({ + agent: 'devin', + prompt: 'fix the tests', + cmdOverrides: {}, + agentArgs: resolveTuiAgentLaunchArgs('devin', null), + platform: 'linux' + }) + expect(plan).toEqual({ + agent: 'devin', + launchCommand: "devin '--permission-mode' 'bypass'", + expectedProcess: 'devin', + followupPrompt: 'fix the tests' + }) + }) + + it('appends Devin default permission-mode bypass before stdin prompt delivery', () => { + expect(resolveTuiAgentLaunchArgs('devin', null)).toBe('--permission-mode bypass') + }) }) diff --git a/src/shared/types.ts b/src/shared/types.ts index 6f712a922..4616061c0 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -1962,6 +1962,7 @@ export type TuiAgent = | 'openclaw' // OpenClaw | 'copilot' // GitHub Copilot CLI | 'grok' // xAI Grok CLI + | 'devin' // Devin CLI export type TaskViewPresetId = 'all' | 'issues' | 'review' | 'my-issues' | 'my-prs' | 'prs'