diff --git a/crates/executors/default_profiles.json b/crates/executors/default_profiles.json index bfaa795a07..8f6df74546 100644 --- a/crates/executors/default_profiles.json +++ b/crates/executors/default_profiles.json @@ -282,6 +282,18 @@ } } }, + "CODEBUDDY": { + "DEFAULT": { + "CODEBUDDY": { + "yolo": true + } + }, + "APPROVALS": { + "CODEBUDDY": { + "yolo": false + } + } + }, "DROID": { "DEFAULT": { "DROID": { @@ -332,4 +344,4 @@ } } } -} +} \ No newline at end of file diff --git a/crates/executors/src/executors/codebuddy.rs b/crates/executors/src/executors/codebuddy.rs new file mode 100644 index 0000000000..2e552e5d77 --- /dev/null +++ b/crates/executors/src/executors/codebuddy.rs @@ -0,0 +1,132 @@ +use std::{path::Path, sync::Arc}; + +use async_trait::async_trait; +use derivative::Derivative; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; +use workspace_utils::msg_store::MsgStore; + +use crate::{ + approvals::ExecutorApprovalService, + command::{CmdOverrides, CommandBuilder, apply_overrides}, + env::ExecutionEnv, + executors::{ + AppendPrompt, AvailabilityInfo, ExecutorError, SpawnedChild, StandardCodingAgentExecutor, + acp::AcpAgentHarness, + }, +}; + +#[derive(Derivative, Clone, Serialize, Deserialize, TS, JsonSchema)] +#[derivative(Debug, PartialEq)] +pub struct CodeBuddy { + #[serde(default)] + pub append_prompt: AppendPrompt, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub yolo: Option, + #[serde(flatten)] + pub cmd: CmdOverrides, + #[serde(skip)] + #[ts(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub approvals: Option>, +} + +impl CodeBuddy { + fn build_command_builder(&self) -> CommandBuilder { + let mut builder = CommandBuilder::new("npx -y @tencent-ai/codebuddy-code"); + + if self.yolo.unwrap_or(false) { + builder = builder.extend_params(["-y"]); + } + builder = builder.extend_params(["--acp"]); + apply_overrides(builder, &self.cmd) + } +} + +#[async_trait] +impl StandardCodingAgentExecutor for CodeBuddy { + fn use_approvals(&mut self, approvals: Arc) { + self.approvals = Some(approvals); + } + + async fn spawn( + &self, + current_dir: &Path, + prompt: &str, + env: &ExecutionEnv, + ) -> Result { + let codebuddy_command = self.build_command_builder().build_initial()?; + let combined_prompt = self.append_prompt.combine_prompt(prompt); + let harness = AcpAgentHarness::with_session_namespace("codebuddy_sessions"); + let approvals = if self.yolo.unwrap_or(false) { + None + } else { + self.approvals.clone() + }; + harness + .spawn_with_command( + current_dir, + combined_prompt, + codebuddy_command, + env, + &self.cmd, + approvals, + ) + .await + } + + async fn spawn_follow_up( + &self, + current_dir: &Path, + prompt: &str, + session_id: &str, + env: &ExecutionEnv, + ) -> Result { + let codebuddy_command = self.build_command_builder().build_follow_up(&[])?; + let combined_prompt = self.append_prompt.combine_prompt(prompt); + let harness = AcpAgentHarness::with_session_namespace("codebuddy_sessions"); + let approvals = if self.yolo.unwrap_or(false) { + None + } else { + self.approvals.clone() + }; + harness + .spawn_follow_up_with_command( + current_dir, + combined_prompt, + session_id, + codebuddy_command, + env, + &self.cmd, + approvals, + ) + .await + } + + fn normalize_logs(&self, msg_store: Arc, worktree_path: &Path) { + crate::executors::acp::normalize_logs(msg_store, worktree_path); + } + + // MCP configuration methods + fn default_mcp_config_path(&self) -> Option { + dirs::home_dir().map(|home| home.join(".codebuddy").join("settings.json")) + } + + fn get_availability_info(&self) -> AvailabilityInfo { + let mcp_config_found = self + .default_mcp_config_path() + .map(|p| p.exists()) + .unwrap_or(false); + + let installation_indicator_found = dirs::home_dir() + .map(|home| home.join(".codebuddy").join("installation_id").exists()) + .unwrap_or(false); + + if mcp_config_found || installation_indicator_found { + AvailabilityInfo::InstallationFound + } else { + AvailabilityInfo::NotFound + } + } +} diff --git a/crates/executors/src/executors/mod.rs b/crates/executors/src/executors/mod.rs index 95a5e5256d..d311f80213 100644 --- a/crates/executors/src/executors/mod.rs +++ b/crates/executors/src/executors/mod.rs @@ -20,8 +20,8 @@ use crate::{ command::CommandBuildError, env::ExecutionEnv, executors::{ - amp::Amp, claude::ClaudeCode, codex::Codex, copilot::Copilot, cursor::CursorAgent, - droid::Droid, gemini::Gemini, opencode::Opencode, qwen::QwenCode, + amp::Amp, claude::ClaudeCode, codebuddy::CodeBuddy, codex::Codex, copilot::Copilot, + cursor::CursorAgent, droid::Droid, gemini::Gemini, opencode::Opencode, qwen::QwenCode, }, mcp_config::McpConfig, }; @@ -29,6 +29,7 @@ use crate::{ pub mod acp; pub mod amp; pub mod claude; +pub mod codebuddy; pub mod codex; pub mod copilot; pub mod cursor; @@ -104,6 +105,10 @@ pub enum CodingAgent { QwenCode, Copilot, Droid, + #[serde(rename = "CODEBUDDY", alias = "CODE_BUDDY")] + #[strum_discriminants(strum(serialize = "CODEBUDDY"))] + #[strum_discriminants(serde(rename = "CODEBUDDY", alias = "CODE_BUDDY"))] + CodeBuddy, #[cfg(feature = "qa-mode")] QaMock(QaMockExecutor), } @@ -166,7 +171,8 @@ impl CodingAgent { | Self::Gemini(_) | Self::QwenCode(_) | Self::Droid(_) - | Self::Opencode(_) => vec![BaseAgentCapability::SessionFork], + | Self::Opencode(_) + | Self::CodeBuddy(_) => vec![BaseAgentCapability::SessionFork], Self::Codex(_) => vec![ BaseAgentCapability::SessionFork, BaseAgentCapability::SetupHelper, diff --git a/crates/executors/src/mcp_config.rs b/crates/executors/src/mcp_config.rs index 62610f6e46..2cdc0f20c3 100644 --- a/crates/executors/src/mcp_config.rs +++ b/crates/executors/src/mcp_config.rs @@ -294,7 +294,7 @@ impl CodingAgent { let adapter = match self { CodingAgent::ClaudeCode(_) | CodingAgent::Amp(_) | CodingAgent::Droid(_) => Passthrough, - CodingAgent::QwenCode(_) | CodingAgent::Gemini(_) => Gemini, + CodingAgent::QwenCode(_) | CodingAgent::Gemini(_) | CodingAgent::CodeBuddy(_) => Gemini, CodingAgent::CursorAgent(_) => Cursor, CodingAgent::Codex(_) => Codex, CodingAgent::Opencode(_) => Opencode, diff --git a/crates/server/src/bin/generate_types.rs b/crates/server/src/bin/generate_types.rs index a07b6aa14c..87a463851a 100644 --- a/crates/server/src/bin/generate_types.rs +++ b/crates/server/src/bin/generate_types.rs @@ -197,6 +197,7 @@ fn generate_types_content() -> String { executors::executors::droid::Droid::decl(), executors::executors::droid::Autonomy::decl(), executors::executors::droid::ReasoningEffortLevel::decl(), + executors::executors::codebuddy::CodeBuddy::decl(), executors::executors::AppendPrompt::decl(), executors::actions::coding_agent_initial::CodingAgentInitialRequest::decl(), executors::actions::coding_agent_follow_up::CodingAgentFollowUpRequest::decl(), @@ -300,6 +301,10 @@ fn generate_schemas() -> Result, serde_json::Error "droid", generate_json_schema::()?, ), + ( + "codebuddy", + generate_json_schema::()?, + ), ]); println!( "✅ JSON schemas generated. {} schemas created.", diff --git a/docs/agents/codebuddy.mdx b/docs/agents/codebuddy.mdx new file mode 100644 index 0000000000..2945aa8081 --- /dev/null +++ b/docs/agents/codebuddy.mdx @@ -0,0 +1,31 @@ +--- +title: "CodeBuddy Code" +description: "Set up CodeBuddy Code agent" +icon: https://www.vibekanban.com/images/logos/codebuddy-logo.png#" +--- + + + + ```bash + npm install -g @tencent-ai/codebuddy-code + ``` + + + + ```bash + codebuddy --acp + ``` + + Follow the login instructions and complete the authentication flow as prompted. + + + + Once authenticated, launch Vibe Kanban: + + ```bash + npx vibe-kanban + ``` + + You can now select CodeBuddy when creating task attempts. + + diff --git a/docs/docs.json b/docs/docs.json index 1f6bca5af0..c0663c76df 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -39,7 +39,8 @@ "agents/opencode", "agents/droid", "agents/ccr", - "agents/qwen-code" + "agents/qwen-code", + "agents/codebuddy" ] } ] diff --git a/docs/supported-coding-agents.mdx b/docs/supported-coding-agents.mdx index 9d733da044..9c14361710 100644 --- a/docs/supported-coding-agents.mdx +++ b/docs/supported-coding-agents.mdx @@ -47,4 +47,8 @@ Claude Code Router - orchestrate multiple models Qwen Code CLI + + +Tencent CodeBuddy Code + diff --git a/shared/schemas/codebuddy.json b/shared/schemas/codebuddy.json new file mode 100644 index 0000000000..715afb5d2e --- /dev/null +++ b/shared/schemas/codebuddy.json @@ -0,0 +1,52 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "append_prompt": { + "title": "Append Prompt", + "description": "Extra text appended to the prompt", + "type": [ + "string", + "null" + ], + "format": "textarea", + "default": null + }, + "yolo": { + "type": [ + "boolean", + "null" + ] + }, + "base_command_override": { + "title": "Base Command Override", + "description": "Override the base command with a custom command", + "type": [ + "string", + "null" + ] + }, + "additional_params": { + "title": "Additional Parameters", + "description": "Additional parameters to append to the base command", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "env": { + "title": "Environment Variables", + "description": "Environment variables to set when running the executor", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + } + }, + "type": "object" +} \ No newline at end of file diff --git a/shared/types.ts b/shared/types.ts index a7f93c9186..a4d88bc090 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -431,9 +431,9 @@ working_dir: string | null, }; export type ScriptRequestLanguage = "Bash"; -export enum BaseCodingAgent { CLAUDE_CODE = "CLAUDE_CODE", AMP = "AMP", GEMINI = "GEMINI", CODEX = "CODEX", OPENCODE = "OPENCODE", CURSOR_AGENT = "CURSOR_AGENT", QWEN_CODE = "QWEN_CODE", COPILOT = "COPILOT", DROID = "DROID" } +export enum BaseCodingAgent { CLAUDE_CODE = "CLAUDE_CODE", AMP = "AMP", GEMINI = "GEMINI", CODEX = "CODEX", OPENCODE = "OPENCODE", CURSOR_AGENT = "CURSOR_AGENT", QWEN_CODE = "QWEN_CODE", COPILOT = "COPILOT", DROID = "DROID", CODEBUDDY = "CODEBUDDY" } -export type CodingAgent = { "CLAUDE_CODE": ClaudeCode } | { "AMP": Amp } | { "GEMINI": Gemini } | { "CODEX": Codex } | { "OPENCODE": Opencode } | { "CURSOR_AGENT": CursorAgent } | { "QWEN_CODE": QwenCode } | { "COPILOT": Copilot } | { "DROID": Droid }; +export type CodingAgent = { "CLAUDE_CODE": ClaudeCode } | { "AMP": Amp } | { "GEMINI": Gemini } | { "CODEX": Codex } | { "OPENCODE": Opencode } | { "CURSOR_AGENT": CursorAgent } | { "QWEN_CODE": QwenCode } | { "COPILOT": Copilot } | { "DROID": Droid } | { "CODEBUDDY": CodeBuddy }; export type AvailabilityInfo = { "type": "LOGIN_DETECTED", last_auth_timestamp: bigint, } | { "type": "INSTALLATION_FOUND" } | { "type": "NOT_FOUND" }; @@ -457,7 +457,7 @@ executor: BaseCodingAgent, */ variant: string | null, }; -export type ExecutorConfig = { [key in string]?: { "CLAUDE_CODE": ClaudeCode } | { "AMP": Amp } | { "GEMINI": Gemini } | { "CODEX": Codex } | { "OPENCODE": Opencode } | { "CURSOR_AGENT": CursorAgent } | { "QWEN_CODE": QwenCode } | { "COPILOT": Copilot } | { "DROID": Droid } }; +export type ExecutorConfig = { [key in string]?: { "CLAUDE_CODE": ClaudeCode } | { "AMP": Amp } | { "GEMINI": Gemini } | { "CODEX": Codex } | { "OPENCODE": Opencode } | { "CURSOR_AGENT": CursorAgent } | { "QWEN_CODE": QwenCode } | { "COPILOT": Copilot } | { "DROID": Droid } | { "CODEBUDDY": CodeBuddy } }; export type ExecutorConfigs = { executors: { [key in BaseCodingAgent]?: ExecutorConfig }, }; @@ -499,6 +499,8 @@ export type Autonomy = "normal" | "low" | "medium" | "high" | "skip-permissions- export type DroidReasoningEffort = "none" | "dynamic" | "off" | "low" | "medium" | "high"; +export type CodeBuddy = { append_prompt: AppendPrompt, yolo?: boolean | null, base_command_override?: string | null, additional_params?: Array | null, env?: { [key in string]?: string } | null, }; + export type AppendPrompt = string | null; export type CodingAgentInitialRequest = { prompt: string,