Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
f2a0350
All checks pass. Let me provide a summary of what was implemented:
LSRCT Jan 7, 2026
725c64b
Backend changes for the `use_all_workspace_commits` flag are complete…
LSRCT Jan 7, 2026
672c1eb
Frontend UI implementation is complete. Here's a summary of what was …
LSRCT Jan 7, 2026
dd47b51
Done. Changes made:
LSRCT Jan 7, 2026
46927d6
Done. Here's a summary of the changes:
LSRCT Jan 7, 2026
01b246f
Codex review support (vibe-kanban e7996a18)
LSRCT Jan 8, 2026
5f8c51f
Done. Here's a summary of the changes:
LSRCT Jan 8, 2026
4b3345e
Use custom review target for codex
LSRCT Jan 8, 2026
2a500c0
Done. Here's a summary of the changes:
LSRCT Jan 8, 2026
5838c5f
Done! Here's a summary of all the frontend changes:
LSRCT Jan 8, 2026
33878af
Done. The fix adds the `ReviewRequest` case to `latest_executor_profi…
LSRCT Jan 8, 2026
b77ca42
Done. Removed `CommitRange` enum entirely. Changes:
LSRCT Jan 8, 2026
3bd63fa
Done. Simplified the review context building:
LSRCT Jan 8, 2026
5601e4b
Done. Addressed all 23 review comments:
LSRCT Jan 8, 2026
9c071b6
Done. Added the review toggle button:
LSRCT Jan 8, 2026
1c142f4
Fix duplicate routes
LSRCT Jan 9, 2026
d67efac
I've analyzed the problem. Here's my understanding:
LSRCT Jan 9, 2026
16a74e3
Done. Here's a summary of the changes:
LSRCT Jan 9, 2026
d4b4220
Cleanup script changes for workspace 38f8b607-9fdc-43dc-afa3-16e8f5e9…
LSRCT Jan 9, 2026
216038c
Removed the unused `find_first_commit_for_repo` function. Everything …
LSRCT Jan 9, 2026
fb16104
All comments removed and code still compiles.
LSRCT Jan 9, 2026
0578cf8
Fixed. The changes:
LSRCT Jan 9, 2026
dd1f694
Fixed. Now the workspace is re-fetched after `ensure_container_exists…
LSRCT Jan 9, 2026
24271f0
Fixed. Now using the `container_ref` returned directly from `ensure_c…
LSRCT Jan 9, 2026
5749f5d
Fixed the naming:
LSRCT Jan 9, 2026
15e53d5
Cleanup script changes for workspace 38f8b607-9fdc-43dc-afa3-16e8f5e9…
LSRCT Jan 9, 2026
d29a6e0
Done. I've added an "Include git context" checkbox to the StartReview…
LSRCT Jan 9, 2026
22d4d3f
I've completed the i18n task. Here's a summary of what was done:
LSRCT Jan 9, 2026
c8dbffd
Cleanup script changes for workspace 38f8b607-9fdc-43dc-afa3-16e8f5e9…
LSRCT Jan 9, 2026
a5787dc
All checks pass. I've added a description below the "Include git cont…
LSRCT Jan 9, 2026
e3e8a2a
Done. Here's a summary of the changes:
LSRCT Jan 9, 2026
47695bb
Types
LSRCT Jan 9, 2026
ded0fd7
Removed the unused `useStartReview` hook and its export from `hooks/i…
LSRCT Jan 9, 2026
b69efb9
All checks pass. Here's a summary of the changes made:
LSRCT Jan 9, 2026
1b60475
All checks pass. Done. Here's a summary of the additional change:
LSRCT Jan 9, 2026
c4e82e2
All checks pass. Here's a summary of the changes:
LSRCT Jan 9, 2026
1aba7aa
All checks pass. Now toolbar actions work identically to navbar actions:
LSRCT Jan 9, 2026
110c83b
Cleanup script changes for workspace 38f8b607-9fdc-43dc-afa3-16e8f5e9…
LSRCT Jan 9, 2026
68aa8e0
Done. The "Start Review" action with the highlighter icon now appears…
LSRCT Jan 9, 2026
24c63b5
Cleanup script changes for workspace 38f8b607-9fdc-43dc-afa3-16e8f5e9…
LSRCT Jan 9, 2026
9f6e464
Done. Removed `StartReview` from the navbar and added `ToggleChangesM…
LSRCT Jan 9, 2026
948efc3
Done.
LSRCT Jan 9, 2026
7e0a44a
Removed the review-specific branch so the handler is generic, matchin…
LSRCT Jan 9, 2026
ca8125b
Aligned the chatbox to use the shared visibility system, like the nav…
LSRCT Jan 9, 2026
a50ee36
Cleanup script changes for workspace 38f8b607-9fdc-43dc-afa3-16e8f5e9…
LSRCT Jan 9, 2026
2f342f5
Updated the Start Review dialog so it derives the active session and …
LSRCT Jan 9, 2026
cc2244b
Fixed the lint issue and clippy qa-mode compile warning, then reran t…
LSRCT Jan 12, 2026
87b0b42
Renamed the match binding to `_request` so we don’t need the no-op li…
LSRCT Jan 12, 2026
1a42137
Split the `ReviewRequest` arm by cfg so qa-mode uses `_request` and n…
LSRCT Jan 12, 2026
47ba0c7
Done. The fix adds `ReviewRequest` to the match statement that create…
LSRCT Jan 12, 2026
1bca23b
Done. Now if a new `ExecutorActionType` variant is added, the compile…
LSRCT Jan 12, 2026
f69e9f7
Adjusted `sessionsApi.startReview` to return the unwrapped payload li…
LSRCT Jan 12, 2026
7786e8e
All checks pass. Summary of changes:
LSRCT Jan 12, 2026
72f3eec
Done. Now when a new session is created, the dialog will call `select…
LSRCT Jan 12, 2026
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
3 changes: 3 additions & 0 deletions crates/db/src/models/execution_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,9 @@ impl ExecutionProcess {
ExecutorActionType::CodingAgentFollowUpRequest(request) => {
Ok(Some(request.executor_profile_id.clone()))
}
ExecutorActionType::ReviewRequest(request) => {
Ok(Some(request.executor_profile_id.clone()))
}
_ => Err(ExecutionProcessError::ValidationError(
"Couldn't find profile from initial request".to_string(),
)),
Expand Down
8 changes: 7 additions & 1 deletion crates/executors/src/actions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,28 @@ use ts_rs::TS;
use crate::{
actions::{
coding_agent_follow_up::CodingAgentFollowUpRequest,
coding_agent_initial::CodingAgentInitialRequest, script::ScriptRequest,
coding_agent_initial::CodingAgentInitialRequest, review::ReviewRequest,
script::ScriptRequest,
},
approvals::ExecutorApprovalService,
env::ExecutionEnv,
executors::{BaseCodingAgent, ExecutorError, SpawnedChild},
};
pub mod coding_agent_follow_up;
pub mod coding_agent_initial;
pub mod review;
pub mod script;

pub use review::RepoReviewContext;

#[enum_dispatch]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
#[serde(tag = "type")]
pub enum ExecutorActionType {
CodingAgentInitialRequest,
CodingAgentFollowUpRequest,
ScriptRequest,
ReviewRequest,
}

#[derive(Debug, Clone, Serialize, Deserialize, TS)]
Expand Down Expand Up @@ -60,6 +65,7 @@ impl ExecutorAction {
ExecutorActionType::CodingAgentFollowUpRequest(request) => {
Some(request.base_executor())
}
ExecutorActionType::ReviewRequest(request) => Some(request.base_executor()),
ExecutorActionType::ScriptRequest(_) => None,
}
}
Expand Down
81 changes: 81 additions & 0 deletions crates/executors/src/actions/review.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use std::{path::Path, sync::Arc};

use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use uuid::Uuid;

use crate::{
actions::Executable,
approvals::ExecutorApprovalService,
env::ExecutionEnv,
executors::{BaseCodingAgent, ExecutorError, SpawnedChild, StandardCodingAgentExecutor},
profile::{ExecutorConfigs, ExecutorProfileId},
};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
pub struct RepoReviewContext {
pub repo_id: Uuid,
pub repo_name: String,
pub base_commit: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)]
pub struct ReviewRequest {
pub executor_profile_id: ExecutorProfileId,
pub context: Option<Vec<RepoReviewContext>>,
pub prompt: String,
/// Optional session ID to resume an existing session
#[serde(default)]
pub session_id: Option<String>,
/// Optional relative path to execute the agent in (relative to container_ref).
#[serde(default)]
pub working_dir: Option<String>,
}

impl ReviewRequest {
pub fn base_executor(&self) -> BaseCodingAgent {
self.executor_profile_id.executor
}

pub fn effective_dir(&self, current_dir: &Path) -> std::path::PathBuf {
match &self.working_dir {
Some(rel_path) => current_dir.join(rel_path),
None => current_dir.to_path_buf(),
}
}
}

#[async_trait]
impl Executable for ReviewRequest {
async fn spawn(
&self,
current_dir: &Path,
approvals: Arc<dyn ExecutorApprovalService>,
env: &ExecutionEnv,
) -> Result<SpawnedChild, ExecutorError> {
// Use working_dir if specified, otherwise use current_dir
let effective_dir = match &self.working_dir {
Some(rel_path) => current_dir.join(rel_path),
None => current_dir.to_path_buf(),
};

let executor_profile_id = self.executor_profile_id.clone();
let mut agent = ExecutorConfigs::get_cached()
.get_coding_agent(&executor_profile_id)
.ok_or(ExecutorError::UnknownExecutorType(
executor_profile_id.to_string(),
))?;

agent.use_approvals(approvals.clone());

agent
.spawn_review(
&effective_dir,
&self.prompt,
self.session_id.as_deref(),
env,
)
.await
}
}
85 changes: 67 additions & 18 deletions crates/executors/src/executors/codex.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod client;
pub mod jsonrpc;
pub mod normalize_logs;
pub mod review;
pub mod session;
use std::{
collections::HashMap,
Expand All @@ -9,7 +10,7 @@ use std::{
};

use async_trait::async_trait;
use codex_app_server_protocol::NewConversationParams;
use codex_app_server_protocol::{NewConversationParams, ReviewTarget};
use codex_protocol::{
config_types::SandboxMode as CodexSandboxMode, protocol::AskForApproval as CodexAskForApproval,
};
Expand Down Expand Up @@ -102,6 +103,11 @@ pub enum ReasoningSummaryFormat {
Experimental,
}

enum CodexSessionAction {
Chat { prompt: String },
Review { target: ReviewTarget },
}

#[derive(Derivative, Clone, Serialize, Deserialize, TS, JsonSchema)]
#[derivative(Debug, PartialEq)]
pub struct Codex {
Expand Down Expand Up @@ -155,7 +161,11 @@ impl StandardCodingAgentExecutor for Codex {
env: &ExecutionEnv,
) -> Result<SpawnedChild, ExecutorError> {
let command_parts = self.build_command_builder().build_initial()?;
self.spawn_inner(current_dir, prompt, command_parts, None, env)
let combined_prompt = self.append_prompt.combine_prompt(prompt);
let action = CodexSessionAction::Chat {
prompt: combined_prompt,
};
self.spawn_inner(current_dir, command_parts, action, None, env)
.await
}

Expand All @@ -167,7 +177,11 @@ impl StandardCodingAgentExecutor for Codex {
env: &ExecutionEnv,
) -> Result<SpawnedChild, ExecutorError> {
let command_parts = self.build_command_builder().build_follow_up(&[])?;
self.spawn_inner(current_dir, prompt, command_parts, Some(session_id), env)
let combined_prompt = self.append_prompt.combine_prompt(prompt);
let action = CodexSessionAction::Chat {
prompt: combined_prompt,
};
self.spawn_inner(current_dir, command_parts, action, Some(session_id), env)
.await
}

Expand Down Expand Up @@ -206,6 +220,24 @@ impl StandardCodingAgentExecutor for Codex {
AvailabilityInfo::NotFound
}
}

async fn spawn_review(
&self,
current_dir: &Path,
prompt: &str,
session_id: Option<&str>,
env: &ExecutionEnv,
) -> Result<SpawnedChild, ExecutorError> {
let command_parts = self.build_command_builder().build_initial()?;
let review_target = ReviewTarget::Custom {
instructions: prompt.to_string(),
};
let action = CodexSessionAction::Review {
target: review_target,
};
self.spawn_inner(current_dir, command_parts, action, session_id, env)
.await
}
}

impl Codex {
Expand Down Expand Up @@ -294,12 +326,11 @@ impl Codex {
async fn spawn_inner(
&self,
current_dir: &Path,
prompt: &str,
command_parts: CommandParts,
action: CodexSessionAction,
resume_session: Option<&str>,
env: &ExecutionEnv,
) -> Result<SpawnedChild, ExecutorError> {
let combined_prompt = self.append_prompt.combine_prompt(prompt);
let (program_path, args) = command_parts.into_resolved().await?;

let mut process = Command::new(program_path);
Expand Down Expand Up @@ -340,19 +371,37 @@ impl Codex {
tokio::spawn(async move {
let exit_signal_tx = ExitSignalSender::new(exit_signal_tx);
let log_writer = LogWriter::new(new_stdout);
if let Err(err) = Self::launch_codex_app_server(
params,
resume_session,
combined_prompt,
child_stdout,
child_stdin,
log_writer.clone(),
exit_signal_tx.clone(),
approvals,
auto_approve,
)
.await
{
let launch_result = match action {
CodexSessionAction::Chat { prompt } => {
Self::launch_codex_app_server(
params,
resume_session,
prompt,
child_stdout,
child_stdin,
log_writer.clone(),
exit_signal_tx.clone(),
approvals,
auto_approve,
)
.await
}
CodexSessionAction::Review { target } => {
review::launch_codex_review(
params,
resume_session,
target,
child_stdout,
child_stdin,
log_writer.clone(),
exit_signal_tx.clone(),
approvals,
auto_approve,
)
.await
}
};
if let Err(err) = launch_result {
match &err {
ExecutorError::Io(io_err)
if io_err.kind() == std::io::ErrorKind::BrokenPipe =>
Expand Down
23 changes: 21 additions & 2 deletions crates/executors/src/executors/codex/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ use codex_app_server_protocol::{
GetAuthStatusParams, GetAuthStatusResponse, InitializeParams, InitializeResponse, InputItem,
JSONRPCError, JSONRPCNotification, JSONRPCRequest, JSONRPCResponse, NewConversationParams,
NewConversationResponse, RequestId, ResumeConversationParams, ResumeConversationResponse,
SendUserMessageParams, SendUserMessageResponse, ServerNotification, ServerRequest,
ReviewStartParams, ReviewStartResponse, ReviewTarget, SendUserMessageParams,
SendUserMessageResponse, ServerNotification, ServerRequest,
};
use codex_protocol::{ConversationId, protocol::ReviewDecision};
use serde::{Serialize, de::DeserializeOwned};
Expand Down Expand Up @@ -146,6 +147,23 @@ impl AppServerClient {
};
self.send_request(request, "getAuthStatus").await
}

pub async fn start_review(
&self,
thread_id: String,
target: ReviewTarget,
) -> Result<ReviewStartResponse, ExecutorError> {
let request = ClientRequest::ReviewStart {
request_id: self.next_request_id(),
params: ReviewStartParams {
thread_id,
target,
delivery: None,
},
};
self.send_request(request, "reviewStart").await
}

async fn handle_server_request(
&self,
peer: &JsonRpcPeer,
Expand Down Expand Up @@ -482,7 +500,8 @@ fn request_id(request: &ClientRequest) -> RequestId {
| ClientRequest::GetAuthStatus { request_id, .. }
| ClientRequest::ResumeConversation { request_id, .. }
| ClientRequest::AddConversationListener { request_id, .. }
| ClientRequest::SendUserMessage { request_id, .. } => request_id.clone(),
| ClientRequest::SendUserMessage { request_id, .. }
| ClientRequest::ReviewStart { request_id, .. } => request_id.clone(),
_ => unreachable!("request_id called for unsupported request variant"),
}
}
Expand Down
63 changes: 63 additions & 0 deletions crates/executors/src/executors/codex/review.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use std::sync::Arc;

use codex_app_server_protocol::{NewConversationParams, ReviewTarget};

use super::{
client::{AppServerClient, LogWriter},
jsonrpc::{ExitSignalSender, JsonRpcPeer},
session::SessionHandler,
};
use crate::{approvals::ExecutorApprovalService, executors::ExecutorError};

#[allow(clippy::too_many_arguments)]
pub async fn launch_codex_review(
conversation_params: NewConversationParams,
resume_session: Option<String>,
review_target: ReviewTarget,
child_stdout: tokio::process::ChildStdout,
child_stdin: tokio::process::ChildStdin,
log_writer: LogWriter,
exit_signal_tx: ExitSignalSender,
approvals: Option<Arc<dyn ExecutorApprovalService>>,
auto_approve: bool,
) -> Result<(), ExecutorError> {
let client = AppServerClient::new(log_writer, approvals, auto_approve);
let rpc_peer = JsonRpcPeer::spawn(child_stdin, child_stdout, client.clone(), exit_signal_tx);
client.connect(rpc_peer);
client.initialize().await?;
let auth_status = client.get_auth_status().await?;
if auth_status.requires_openai_auth.unwrap_or(true) && auth_status.auth_method.is_none() {
return Err(ExecutorError::AuthRequired(
"Codex authentication required".to_string(),
));
}

let conversation_id = match resume_session {
Some(session_id) => {
let (rollout_path, _forked_session_id) = SessionHandler::fork_rollout_file(&session_id)
.map_err(|e| ExecutorError::FollowUpNotSupported(e.to_string()))?;
let response = client
.resume_conversation(rollout_path.clone(), conversation_params)
.await?;
tracing::debug!(
"resuming session for review using rollout file {}, response {:?}",
rollout_path.display(),
response
);
response.conversation_id
}
None => {
let response = client.new_conversation(conversation_params).await?;
response.conversation_id
}
};

client.register_session(&conversation_id).await?;
client.add_conversation_listener(conversation_id).await?;

client
.start_review(conversation_id.to_string(), review_target)
.await?;

Ok(())
}
Loading