Skip to content

Commit 05d86d5

Browse files
committed
Add effective_exec_events_path function and related tests; enhance harness_exec output normalization
1 parent 96b8362 commit 05d86d5

File tree

6 files changed

+442
-12
lines changed

6 files changed

+442
-12
lines changed

src/cli/exec/mod.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ mod tests {
3939
use super::event_output::{
4040
ExecEventProcessor, human_event_line, render_final_tail, serialize_event_line,
4141
};
42-
use super::run::{REVIEW_TASK_ID, resolve_exec_event_log_path, task_instructions, task_spec};
42+
use super::run::{
43+
REVIEW_TASK_ID, effective_exec_events_path, resolve_exec_event_log_path, task_instructions,
44+
task_spec,
45+
};
4346
use tempfile::TempDir;
4447
use vtcode_core::core::agent::task::{TaskOutcome, TaskResults};
4548
use vtcode_core::exec::events::{
@@ -215,6 +218,43 @@ mod tests {
215218
assert!(file_name.ends_with(".jsonl"));
216219
}
217220

221+
#[test]
222+
fn effective_exec_events_path_uses_config_when_cli_path_is_absent() {
223+
let temp = TempDir::new().expect("tempdir");
224+
let configured = temp.path().join("events.jsonl");
225+
226+
let resolved = effective_exec_events_path(
227+
None,
228+
Some(configured.to_str().expect("configured path")),
229+
"session-123",
230+
)
231+
.expect("resolved path");
232+
233+
assert_eq!(resolved, configured);
234+
}
235+
236+
#[test]
237+
fn effective_exec_events_path_ignores_empty_config_path() {
238+
let resolved = effective_exec_events_path(None, Some(""), "session-123");
239+
assert!(resolved.is_none());
240+
}
241+
242+
#[test]
243+
fn effective_exec_events_path_prefers_cli_path_over_config() {
244+
let temp = TempDir::new().expect("tempdir");
245+
let cli = temp.path().join("cli-events.jsonl");
246+
let configured = temp.path().join("configured-events.jsonl");
247+
248+
let resolved = effective_exec_events_path(
249+
Some(cli.as_path()),
250+
Some(configured.to_str().expect("configured path")),
251+
"session-123",
252+
)
253+
.expect("resolved path");
254+
255+
assert_eq!(resolved, cli);
256+
}
257+
218258
#[test]
219259
fn final_tail_includes_summary_and_metrics() {
220260
let result = TaskResults {

src/cli/exec/run.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use anyhow::{Context, Result};
22
use chrono::Utc;
33
use std::fs::File;
44
use std::io::{self, BufWriter};
5+
use std::path::Path;
56
use std::path::PathBuf;
67
use std::sync::{Arc, Mutex};
78
use vtcode_core::config::VTCodeConfig;
@@ -63,6 +64,18 @@ pub(super) fn resolve_exec_event_log_path(path: &str, session_id: &str) -> PathB
6364
base
6465
}
6566

67+
pub(super) fn effective_exec_events_path(
68+
cli_events_path: Option<&Path>,
69+
harness_event_log_path: Option<&str>,
70+
session_id: &str,
71+
) -> Option<PathBuf> {
72+
cli_events_path.map(Path::to_path_buf).or_else(|| {
73+
harness_event_log_path
74+
.filter(|path| !path.trim().is_empty())
75+
.map(|path| resolve_exec_event_log_path(path, session_id))
76+
})
77+
}
78+
6679
pub(super) async fn handle_exec_command_impl(
6780
config: &CoreAgentConfig,
6881
vt_cfg: &VTCodeConfig,
@@ -111,15 +124,11 @@ pub(super) async fn handle_exec_command_impl(
111124
runner.enable_plan_mode();
112125
}
113126
runner.set_quiet(true);
114-
let events_path = options.events_path.clone().or_else(|| {
115-
run_vt_cfg
116-
.agent
117-
.harness
118-
.event_log_path
119-
.as_deref()
120-
.filter(|path| !path.trim().is_empty())
121-
.map(|path| resolve_exec_event_log_path(path, &event_session_id))
122-
});
127+
let events_path = effective_exec_events_path(
128+
options.events_path.as_deref(),
129+
run_vt_cfg.agent.harness.event_log_path.as_deref(),
130+
&event_session_id,
131+
);
123132

124133
let processor = Arc::new(Mutex::new(ExecEventProcessor::<
125134
io::Stdout,

vtcode-core/src/core/agent/completion.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ pub fn check_for_response_loop(response_text: &str, session_state: &mut AgentSes
8383
.iter()
8484
.rev()
8585
.filter(|m| m.role == MessageRole::Assistant)
86+
.skip(1)
8687
.take(2)
8788
.any(|m| {
8889
let normalized_prev = m
@@ -109,6 +110,7 @@ pub fn check_for_response_loop(response_text: &str, session_state: &mut AgentSes
109110
#[cfg(test)]
110111
mod tests {
111112
use super::*;
113+
use crate::llm::provider::Message;
112114

113115
#[test]
114116
fn test_completion_indicators() {
@@ -126,4 +128,29 @@ mod tests {
126128
assert!(!check_completion_indicators("Is the task done?"));
127129
assert!(!check_completion_indicators("random text"));
128130
}
131+
132+
#[test]
133+
fn response_loop_ignores_current_assistant_message() {
134+
let repeated_response = "The task is complete";
135+
let mut state = AgentSessionState::new("session".to_string(), 8, 4, 128_000);
136+
state
137+
.messages
138+
.push(Message::assistant(repeated_response.to_string()));
139+
140+
assert!(!check_for_response_loop(repeated_response, &mut state));
141+
}
142+
143+
#[test]
144+
fn response_loop_still_detects_prior_duplicate_assistant_message() {
145+
let repeated_response = "The task is complete";
146+
let mut state = AgentSessionState::new("session".to_string(), 8, 4, 128_000);
147+
state
148+
.messages
149+
.push(Message::assistant(repeated_response.to_string()));
150+
state
151+
.messages
152+
.push(Message::assistant(repeated_response.to_string()));
153+
154+
assert!(check_for_response_loop(repeated_response, &mut state));
155+
}
129156
}

0 commit comments

Comments
 (0)