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
907 changes: 894 additions & 13 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions crates/code_assistant/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ name = "code-assistant"
version = "0.2.7"
edition = "2021"

[features]
default = ["document-conversion"]
document-conversion = ["transmutation", "fs_explorer/document-conversion"]

[dependencies]
command_executor = { path = "../command_executor" }
Expand Down Expand Up @@ -86,6 +89,9 @@ base64 = "0.22"
# Image processing
image = "0.25"

# Document conversion (behind feature flag)
transmutation = { version = "0.3", features = ["office"], optional = true }

# Clipboard access for paste
arboard = "3"

Expand Down
7 changes: 3 additions & 4 deletions crates/code_assistant/assets/icons/file_generic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions crates/code_assistant/assets/icons/todo_list.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions crates/code_assistant/src/acp/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ pub struct ACPAgentImpl {
client_capabilities: Arc<Mutex<Option<acp::ClientCapabilities>>>,
/// Sessions created in new_session() but not yet persisted (deferred until first prompt)
pending_sessions: Arc<Mutex<HashMap<String, PendingSession>>>,
/// The session ID currently connected via ACP (for cross-instance awareness).
/// Updated when load_session or new_session is called.
connected_session_id: Arc<std::sync::Mutex<Option<String>>>,
}

struct ModelStateInfo {
Expand All @@ -72,6 +75,7 @@ impl ACPAgentImpl {
playback_path: Option<std::path::PathBuf>,
fast_playback: bool,
session_update_tx: mpsc::UnboundedSender<(acp::SessionNotification, oneshot::Sender<()>)>,
connected_session_id: Arc<std::sync::Mutex<Option<String>>>,
) -> Self {
Self {
session_manager,
Expand All @@ -83,6 +87,7 @@ impl ACPAgentImpl {
session_update_tx,
active_uis: Arc::new(Mutex::new(HashMap::new())),
client_capabilities: Arc::new(Mutex::new(None)),
connected_session_id,
}
}

Expand Down Expand Up @@ -328,6 +333,7 @@ impl acp::Agent for ACPAgentImpl {
let model_name = self.model_name.clone();
let session_config_template = self.session_config_template.clone();
let pending_sessions = self.pending_sessions.clone();
let connected_session_id = self.connected_session_id.clone();

Box::pin(async move {
tracing::info!("ACP: Creating new session with cwd: {:?}", arguments.cwd);
Expand Down Expand Up @@ -357,6 +363,12 @@ impl acp::Agent for ACPAgentImpl {

tracing::info!("ACP: Created pending session: {}", session_id);

// Track this as the connected session for cross-instance awareness
{
let mut connected = connected_session_id.lock().unwrap();
*connected = Some(session_id.clone());
}

let models_state =
ACPAgentImpl::compute_model_state(&model_name, Some(model_name.as_str()))
.map(|info| info.state);
Expand All @@ -381,10 +393,17 @@ impl acp::Agent for ACPAgentImpl {
let session_manager = self.session_manager.clone();
let session_update_tx = self.session_update_tx.clone();
let default_model_name = self.model_name.clone();
let connected_session_id = self.connected_session_id.clone();

Box::pin(async move {
tracing::info!("ACP: Loading session: {}", arguments.session_id.0);

// Track this as the connected session for cross-instance awareness
{
let mut connected = connected_session_id.lock().unwrap();
*connected = Some(arguments.session_id.0.to_string());
}

// Load session into manager
{
let mut manager = session_manager.lock().await;
Expand Down Expand Up @@ -866,6 +885,19 @@ impl acp::Agent for ACPAgentImpl {
arguments.session_id.0
);

// Advance the UI-sync baseline so the file-watcher debounce
// (which fires ~300ms later) won't replay content already
// streamed during this prompt.
{
let mut manager = session_manager.lock().await;
if let Err(e) = manager.advance_ui_sync_baseline(&arguments.session_id.0) {
tracing::warn!(
"ACP: Failed to advance UI sync baseline for {}: {e}",
arguments.session_id.0
);
}
}

// Mark session as disconnected and remove UI from active set
{
let mut manager = session_manager.lock().await;
Expand Down
72 changes: 70 additions & 2 deletions crates/code_assistant/src/acp/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,75 @@ impl UserInterface for ACPUserUI {
);
}

UiEvent::AppendMessages {
messages,
tool_results,
} => {
// Cross-instance awareness: replay new messages that another
// code-assistant instance appended to the currently connected session.
use crate::ui::gpui::elements::MessageRole;

for message_data in &messages {
match message_data.role {
MessageRole::User => {
for fragment in &message_data.fragments {
match fragment {
DisplayFragment::PlainText(text) => {
let content = acp::ContentBlock::Text(
acp::TextContent::new(text.clone()),
);
let chunk = Self::content_chunk(content);
self.queue_session_update(
acp::SessionUpdate::UserMessageChunk(chunk),
);
}
DisplayFragment::CompactionDivider { .. } => {
self.display_fragment(fragment)?;
}
_ => {}
}
}
}
MessageRole::Assistant => {
for fragment in &message_data.fragments {
self.display_fragment(fragment)?;
}
}
}
}

// Replay tool results as ToolCallUpdate with final status
for tool_result in &tool_results {
let status = match tool_result.status {
crate::ui::ToolStatus::Success => acp::ToolCallStatus::Completed,
crate::ui::ToolStatus::Error => acp::ToolCallStatus::Failed,
_ => acp::ToolCallStatus::InProgress,
};

let output_content: Vec<acp::ToolCallContent> = tool_result
.output
.as_ref()
.map(|o| {
vec![acp::ToolCallContent::Content(acp::Content::new(
acp::ContentBlock::Text(acp::TextContent::new(o.clone())),
))]
})
.unwrap_or_default();

let mut update_fields = acp::ToolCallUpdateFields::new().status(status);
if !output_content.is_empty() {
update_fields = update_fields.content(output_content);
}

let tool_call_update = acp::ToolCallUpdate::new(
acp::ToolCallId::new(tool_result.tool_id.clone()),
update_fields,
);

self.queue_session_update(acp::SessionUpdate::ToolCallUpdate(tool_call_update));
}
}

// Events that don't translate to ACP
UiEvent::SetMessages { .. }
| UiEvent::DisplayCompactionSummary { .. }
Expand Down Expand Up @@ -735,8 +804,7 @@ impl UserInterface for ACPUserUI {
| UiEvent::RollbackStreaming { .. }
| UiEvent::ShowTransientStatus { .. }
| UiEvent::ClearTransientStatus
| UiEvent::RefreshCurrentSession { .. }
| UiEvent::AppendMessages { .. } => {
| UiEvent::RefreshCurrentSession { .. } => {
// These are UI management events, not relevant for ACP
// (RollbackStreaming: ACP cannot retract already-sent notifications)
}
Expand Down
4 changes: 2 additions & 2 deletions crates/code_assistant/src/agent/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1231,10 +1231,10 @@ impl Agent {
guidance_section.push_str("\n\n# Repository Guidance\n");

for (file_name, content) in guidance_files {
guidance_section.push_str("\n");
guidance_section.push('\n');
guidance_section.push_str(&format!("Loaded from `{file_name}`.\n\n"));
guidance_section.push_str(&content);
guidance_section.push_str("\n");
guidance_section.push('\n');
}

system_message.push_str(&guidance_section);
Expand Down
Loading