Skip to content
Open
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
13 changes: 8 additions & 5 deletions crates/forge_app/src/fmt/fmt_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,14 @@ impl FormatContent for ToolCatalog {
let display_path = display_path_for(&input.path);
Some(TitleFormat::debug("Undo").sub_title(display_path).into())
}
ToolCatalog::Shell(input) => Some(
TitleFormat::debug(format!("Execute [{}]", env.shell))
.sub_title(&input.command)
.into(),
),
ToolCatalog::Shell(input) => {
let action = if input.nohup { "Spawned" } else { "Execute" };
Some(
TitleFormat::debug(format!("{action} [{}]", env.shell))
.sub_title(&input.command)
.into(),
)
}
ToolCatalog::Fetch(input) => {
Some(TitleFormat::debug("GET").sub_title(&input.url).into())
}
Expand Down
6 changes: 5 additions & 1 deletion crates/forge_app/src/git_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ where

let commit_result = self
.services
.execute(commit_command, cwd, false, true, None, None)
.execute(commit_command, cwd, false, true, None, None, false)
.await
.context("Failed to commit changes")?;

Expand Down Expand Up @@ -232,6 +232,7 @@ where
true,
None,
None,
false,
),
self.services.execute(
"git rev-parse --abbrev-ref HEAD".into(),
Expand All @@ -240,6 +241,7 @@ where
true,
None,
None,
false,
),
);

Expand All @@ -259,6 +261,7 @@ where
true,
None,
None,
false,
),
self.services.execute(
"git diff".into(),
Expand All @@ -267,6 +270,7 @@ where
true,
None,
None,
false,
)
);

Expand Down
1 change: 1 addition & 0 deletions crates/forge_app/src/orch_spec/orch_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ impl ShellService for Runner {
_silent: bool,
_env_vars: Option<Vec<String>>,
_description: Option<String>,
_nohup: bool,
) -> anyhow::Result<ShellOutput> {
let mut outputs = self.test_shell_outputs.lock().await;
if let Some(output) = outputs.pop_front() {
Expand Down
12 changes: 11 additions & 1 deletion crates/forge_app/src/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ pub trait ShellService: Send + Sync {
silent: bool,
env_vars: Option<Vec<String>>,
description: Option<String>,
nohup: bool,
) -> anyhow::Result<ShellOutput>;
}

Expand Down Expand Up @@ -908,9 +909,18 @@ impl<I: Services> ShellService for I {
silent: bool,
env_vars: Option<Vec<String>>,
description: Option<String>,
nohup: bool,
) -> anyhow::Result<ShellOutput> {
self.shell_service()
.execute(command, cwd, keep_ansi, silent, env_vars, description)
.execute(
command,
cwd,
keep_ansi,
silent,
env_vars,
description,
nohup,
)
.await
}
}
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Now update the `From<&TextMessage>` implementation to handle optional call_id:
````
Now let's run the tests to verify everything works:
````
**Execute:**
**Execute:**
```
cargo test --package forge_domain --lib compact::summary
```
Expand All @@ -57,7 +57,7 @@ Now let me read the test section and fix the remaining tests:
````
Now I need to remove the old tests and update the ones that need fixing. Let me first check how many lines the file has:
````
**Execute:**
**Execute:**
```
wc -l crates/forge_domain/src/compact/summary.rs
```
Expand Down Expand Up @@ -108,7 +108,7 @@ Let me remove the last test that uses the old struct-based API:
````
Now let me run the tests again to see if there are any remaining issues:
````
**Execute:**
**Execute:**
```
cargo test --package forge_domain --lib compact::summary 2>&1 | head -100
```
Expand Down Expand Up @@ -159,7 +159,7 @@ Good! Now let me fix the test helpers:
````
Now I need to update all the test cases. Due to the large number of changes needed, let me run the tests first to see the current errors, then I'll provide a summary of what was accomplished:
````
**Execute:**
**Execute:**
```
cargo test --package forge_domain --lib compact::summary 2>&1 | head -50
```
Expand Down Expand Up @@ -241,7 +241,7 @@ Now let me update the transformer files to use the non-optional `tool_call_succe
````
Good! Now let me run the tests to see the current status:
````
**Execute:**
**Execute:**
```
cargo test --package forge_domain --lib compact::summary 2>&1 | head -100
```
Expand All @@ -256,15 +256,15 @@ Now let me search for all other uses of `Block::read`, `Block::update`, and `Blo
````
There are many more uses. Let me use a more efficient approach and fix them all at once with a replace_all operation:
````
**Execute:**
**Execute:**
```
sed -i '' 's/Block::read("/Block::read(None, "/g' crates/forge_domain/src/compact/transformers/strip_working_dir.rs
```
**Execute:**
**Execute:**
```
sed -i '' 's/Block::update("/Block::update(None, "/g' crates/forge_domain/src/compact/transformers/strip_working_dir.rs
```
**Execute:**
**Execute:**
```
sed -i '' 's/Block::remove("/Block::remove(None, "/g' crates/forge_domain/src/compact/transformers/strip_working_dir.rs
```
Expand Down Expand Up @@ -309,7 +309,7 @@ I've successfully added comprehensive tests for `crates/forge_domain/src/compact

Let me run the tests to see the current status:
````
**Execute:**
**Execute:**
```
cargo test --package forge_domain --lib compact::summary::tests 2>&1 | tail -30
```
Expand All @@ -320,14 +320,14 @@ Let me fix those mutable warnings and then run the actual summary tests:
````
Now let me run just the summary tests to verify they work:
````
**Execute:**
**Execute:**
```
cargo test --package forge_domain --lib summary::tests -- --nocapture 2>&1 | grep -A 5 "test result"
```
````
Let me check if there are still compilation errors:
````
**Execute:**
**Execute:**
```
cargo build --package forge_domain --lib 2>&1 | head -50
```
Expand All @@ -338,14 +338,14 @@ Let me fix the strip_working_dir.rs import and type issues:
````
Now let me try building again:
````
**Execute:**
**Execute:**
```
cargo test --package forge_domain --lib compact::summary::tests 2>&1 | tail -20
```
````
The remaining errors are all in the `trim_context_summary.rs` tests which use the old API extensively. Let me format and lint what we have so far:
````
**Execute:**
**Execute:**
```
cargo +nightly fmt --all && cargo +nightly clippy --fix --allow-staged --allow-dirty --package forge_domain 2>&1 | tail -30
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Search for TODO comments

### 8. Assistant

**Execute:**
**Execute:**
```
cargo test
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,13 @@ Bad example:

Returns complete output including stdout, stderr, and exit code for diagnostic purposes.

Background execution:
- Set `nohup: true` to run long-running processes (like web servers) in the background.
- When nohup is true, the command runs via `nohup <command> > /tmp/<log>.log 2>&1 &`
- Returns the log file path and process ID instead of waiting for completion.
- Use the read tool to check the log file for output.
- IMPORTANT: The log file path is preserved during context compaction so you can always access it.

---

### fetch
Expand Down
1 change: 1 addition & 0 deletions crates/forge_app/src/system_prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ impl<S: SkillFetchService + ShellService> SystemPrompt<S> {
true,
None,
None,
false,
)
.await
.ok()?;
Expand Down
1 change: 1 addition & 0 deletions crates/forge_app/src/tool_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ impl<
false,
input.env.clone(),
input.description.clone(),
input.nohup,
)
.await?;
output.into()
Expand Down
2 changes: 1 addition & 1 deletion crates/forge_app/src/transformers/trim_context_summary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ fn to_op(tool: &SummaryTool) -> Operation<'_> {
SummaryTool::FileUpdate { path } => Operation::File(path),
SummaryTool::FileRemove { path } => Operation::File(path),
SummaryTool::Undo { path } => Operation::File(path),
SummaryTool::Shell { command } => Operation::Shell(command),
SummaryTool::Shell { command, .. } => Operation::Shell(command),
SummaryTool::Search { pattern } => Operation::Search(pattern),
SummaryTool::SemSearch { queries } => Operation::CodebaseSearch { queries },
SummaryTool::Fetch { url } => Operation::Fetch(url),
Expand Down
88 changes: 71 additions & 17 deletions crates/forge_domain/src/compact/summary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,17 @@ impl SummaryToolCall {
pub fn shell(command: impl Into<String>) -> Self {
Self {
id: None,
tool: SummaryTool::Shell { command: command.into() },
tool: SummaryTool::Shell { command: command.into(), log_file: None },
is_success: true,
}
}

/// Creates a Shell tool call with a log file for nohup background
/// processes.
pub fn shell_with_log(command: impl Into<String>, log_file: impl Into<String>) -> Self {
Self {
id: None,
tool: SummaryTool::Shell { command: command.into(), log_file: Some(log_file.into()) },
is_success: true,
}
}
Expand Down Expand Up @@ -180,19 +190,47 @@ impl SummaryToolCall {
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SummaryTool {
FileRead { path: String },
FileUpdate { path: String },
FileRemove { path: String },
Shell { command: String },
Search { pattern: String },
SemSearch { queries: Vec<SearchQuery> },
Undo { path: String },
Fetch { url: String },
Followup { question: String },
Plan { plan_name: String },
Skill { name: String },
Mcp { name: String },
TodoWrite { changes: Vec<TodoChange> },
FileRead {
path: String,
},
FileUpdate {
path: String,
},
FileRemove {
path: String,
},
Shell {
command: String,
#[serde(skip_serializing_if = "Option::is_none")]
log_file: Option<String>,
},
Search {
pattern: String,
},
SemSearch {
queries: Vec<SearchQuery>,
},
Undo {
path: String,
},
Fetch {
url: String,
},
Followup {
question: String,
},
Plan {
plan_name: String,
},
Skill {
name: String,
},
Mcp {
name: String,
},
TodoWrite {
changes: Vec<TodoChange>,
},
TodoRead,
}

Expand Down Expand Up @@ -283,7 +321,7 @@ impl From<&Context> for ContextSummary {
.push(SummaryBlock { role: current_role, contents: std::mem::take(&mut buffer) });
}

// Update tool call success status based on results
// Update tool call success status and extract nohup log file paths
messages
.iter_mut()
.flat_map(|message| message.contents.iter_mut())
Expand All @@ -293,6 +331,19 @@ impl From<&Context> for ContextSummary {
&& let Some(result) = tool_results.get(call_id)
{
tool_data.is_success = !result.is_error();

// For nohup shell commands, extract the log file path from
// the tool result output so it is preserved through
// compaction.
if let SummaryTool::Shell { log_file, .. } = &mut tool_data.tool
&& let Some(output_str) = result.output.as_str()
&& let Some(path) = output_str
.lines()
.find(|line| line.starts_with("Log file: "))
.map(|line| line.trim_start_matches("Log file: ").trim())
{
*log_file = Some(path.to_string());
}
}
});

Expand Down Expand Up @@ -342,7 +393,10 @@ fn extract_tool_info(call: &ToolCallFull, current_todos: &[Todo]) -> Option<Summ
ToolCatalog::Write(input) => Some(SummaryTool::FileUpdate { path: input.file_path }),
ToolCatalog::Patch(input) => Some(SummaryTool::FileUpdate { path: input.file_path }),
ToolCatalog::Remove(input) => Some(SummaryTool::FileRemove { path: input.path }),
ToolCatalog::Shell(input) => Some(SummaryTool::Shell { command: input.command }),
ToolCatalog::Shell(input) => Some(SummaryTool::Shell {
command: input.command,
log_file: None, // Will be populated from tool result for nohup commands
}),
ToolCatalog::FsSearch(input) => {
// Use glob, file_type, or pattern as the search identifier
let pattern = input.glob.or(input.file_type).unwrap_or(input.pattern);
Expand Down Expand Up @@ -911,7 +965,7 @@ mod tests {

let expected = Block::ToolCall(SummaryToolCall {
id: None,
tool: SummaryTool::Shell { command: "cargo build".to_string() },
tool: SummaryTool::Shell { command: "cargo build".to_string(), log_file: None },
is_success: true,
});

Expand Down
Loading
Loading