Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
85e8a5b
feat: add interactive :rewind command to truncate conversations
rohithmahesh3 May 15, 2026
a36543c
feat: show only user messages in rewind preview
rohithmahesh3 May 15, 2026
d68cdbd
feat: revert file changes on rewind (Option A)
rohithmahesh3 May 15, 2026
73e68f6
feat: replace input prompt with TUI message selector for :rewind
rohithmahesh3 May 15, 2026
a255ad8
refactor: show only message content in rewind selector, drop header row
rohithmahesh3 May 15, 2026
49c889f
chore: fix unused variable warning in format_messages_for_rewind
rohithmahesh3 May 15, 2026
fd1dc62
feat: strip XML tags from message previews in rewind selector
rohithmahesh3 May 15, 2026
b8d9bd5
fix: exclude auto-generated user messages from rewind selector
rohithmahesh3 May 15, 2026
eefd9b5
fix: two bugs causing rewind file revert to fail
rohithmahesh3 May 15, 2026
fcc9a34
fix(rewind): track multi_patch as modifying tool and preserve operati…
rohithmahesh3 May 15, 2026
50918eb
feat(rewind): comprehensive rewind enhancements
rohithmahesh3 May 15, 2026
049461e
chore: code formatting cleanup and fallback path extraction in modifi…
rohithmahesh3 May 15, 2026
8fcf272
refactor: extract shared logic and improve code reuse
rohithmahesh3 May 16, 2026
c73e1ec
feat: improve task tag handling, conversation picker, and shell plugin
rohithmahesh3 May 16, 2026
e17824a
fix: improve rewind prompt reset and dispatcher integration
rohithmahesh3 May 16, 2026
2e42a97
fix: correct modified files tracking scoping and dedup
rohithmahesh3 May 16, 2026
0181f48
fix: address nightly clippy violations for string_slice and indexing_…
rohithmahesh3 May 16, 2026
f0a8410
[autofix.ci] apply automated fixes
autofix-ci[bot] May 16, 2026
3f101ef
Merge branch 'main' into impl/interactive-rewind
rohithmahesh3 May 16, 2026
be0b02c
Merge branch 'main' into impl/interactive-rewind
rohithmahesh3 May 21, 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
4 changes: 4 additions & 0 deletions crates/forge_api/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,4 +260,8 @@ pub trait API: Sync + Send {

/// Check the OAuth authentication status of an MCP server
async fn mcp_auth_status(&self, server_url: &str) -> Result<String>;

/// Undoes the most recent snapshot for the given file path.
/// Used by the rewind command to revert file changes.
async fn undo_snapshot(&self, path: &str) -> Result<()>;
}
8 changes: 7 additions & 1 deletion crates/forge_api/src/forge_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ impl<
F: CommandInfra
+ EnvironmentInfra<Config = forge_config::ForgeConfig>
+ SkillRepository
+ GrpcInfra,
+ GrpcInfra
+ SnapshotRepository,
> API for ForgeAPI<A, F>
{
async fn discover(&self) -> Result<Vec<File>> {
Expand Down Expand Up @@ -435,6 +436,11 @@ impl<
Ok(forge_infra::mcp_auth_status(server_url, &env).await)
}

async fn undo_snapshot(&self, path: &str) -> Result<()> {
self.infra.undo_snapshot(std::path::Path::new(path)).await?;
Ok(())
}

fn hydrate_channel(&self) -> Result<()> {
self.infra.hydrate();
Ok(())
Expand Down
40 changes: 38 additions & 2 deletions crates/forge_app/src/tool_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,25 @@ impl<S: Services + EnvironmentInfra<Config = forge_config::ForgeConfig>> ToolReg
) -> ToolResult {
let call_id = call.call_id.clone();
let tool_name = call.name.clone();
let output = self.call_inner(agent, call, context).await;
let output = self.call_inner(agent, call.clone(), context).await;

ToolResult::new(tool_name).call_id(call_id).output(output)
let mut modified_files = Vec::new();
if let Ok(output) = &output {
if let Some(text) = output.as_str() {
modified_files = forge_domain::extract_modified_files_from_output(text);
}

// Fallback to extraction from arguments if output didn't yield anything
// This is important for relative paths provided in tool arguments
if modified_files.is_empty() {
modified_files = Self::extract_modified_files(&call);
}
}

ToolResult::new(tool_name)
.call_id(call_id)
.output(output)
.modified_files(modified_files)
}

pub async fn list(&self) -> anyhow::Result<Vec<ToolDefinition>> {
Expand Down Expand Up @@ -381,6 +397,26 @@ impl<S> ToolRegistry<S> {
IMAGE_EXTENSIONS.iter().any(|ext| path_lower.ends_with(ext))
}

/// Extracts the file path from a tool call's arguments if it's a
/// file-modifying tool (write, patch, remove). Returns an empty vec
/// for non-modifying tools or if the path cannot be parsed.
fn extract_modified_files(call: &ToolCallFull) -> Vec<String> {
match call.name.as_str() {
"write" | "patch" | "multi_patch" | "remove" => {
if let Ok(args) = call.arguments.parse() {
if let Some(file_path) = args.get("file_path").and_then(|v| v.as_str()) {
return vec![file_path.to_string()];
}
if let Some(path) = args.get("path").and_then(|v| v.as_str()) {
return vec![path.to_string()];
}
}
vec![]
}
_ => vec![],
}
}

/// Validates if a tool's modality requirements are supported by the current
/// model.
///
Expand Down
2 changes: 2 additions & 0 deletions crates/forge_domain/src/compact/summary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ mod tests {
name: ToolName::new(name),
call_id: Some(ToolCallId::new(call_id)),
output: ToolOutput::text("result").is_error(is_error),
modified_files: vec![],
})
}

Expand Down Expand Up @@ -805,6 +806,7 @@ mod tests {
name: ToolName::new("read"),
call_id: None,
output: ToolOutput::text("result"),
modified_files: vec![],
}),
]);

Expand Down
Loading
Loading