feat(auto-drive): forward cli_context to CLI prompt

This commit is contained in:
James Peter
2025-10-03 10:35:11 +10:00
parent 53c07d8068
commit 0d2e51f280
5 changed files with 89 additions and 4 deletions
+2
View File
@@ -1625,6 +1625,7 @@ impl App<'_> {
status,
progress_past,
progress_current,
cli_context,
cli_prompt,
transcript,
turn_config,
@@ -1634,6 +1635,7 @@ impl App<'_> {
status,
progress_past,
progress_current,
cli_context,
cli_prompt,
transcript,
turn_config,
+1
View File
@@ -155,6 +155,7 @@ pub(crate) enum AppEvent {
status: AutoCoordinatorStatus,
progress_past: Option<String>,
progress_current: Option<String>,
cli_context: Option<String>,
cli_prompt: Option<String>,
transcript: Vec<ResponseItem>,
turn_config: Option<TurnConfig>,
+31 -3
View File
@@ -508,6 +508,7 @@ struct AutoCoordinatorUiState {
current_progress_past: Option<String>,
current_progress_current: Option<String>,
current_cli_prompt: Option<String>,
current_cli_context: Option<String>,
current_display_line: Option<String>,
current_display_is_summary: bool,
current_reasoning_title: Option<String>,
@@ -11399,6 +11400,7 @@ impl ChatWidget<'_> {
self.auto_state.current_progress_past = None;
self.auto_state.current_progress_current = None;
self.auto_state.current_cli_prompt = None;
self.auto_state.current_cli_context = None;
self.auto_state.current_display_line = None;
self.auto_state.current_display_is_summary = false;
self.auto_state.current_reasoning_title = None;
@@ -11443,6 +11445,7 @@ impl ChatWidget<'_> {
self.auto_state.current_progress_past = None;
self.auto_state.current_progress_current = None;
self.auto_state.current_cli_prompt = None;
self.auto_state.current_cli_context = None;
self.auto_state.last_broadcast_summary = None;
self.auto_state.current_summary_index = None;
self.auto_state.current_display_line = None;
@@ -11462,6 +11465,7 @@ impl ChatWidget<'_> {
status: AutoCoordinatorStatus,
progress_past: Option<String>,
progress_current: Option<String>,
cli_context: Option<String>,
cli_prompt: Option<String>,
transcript: Vec<codex_protocol::models::ResponseItem>,
turn_config: Option<TurnConfig>,
@@ -11481,6 +11485,7 @@ impl ChatWidget<'_> {
self.auto_state.current_progress_current = progress_current.clone();
self.auto_state.last_decision_progress_past = progress_past.clone();
self.auto_state.last_decision_progress_current = progress_current.clone();
self.auto_state.current_cli_context = cli_context.clone();
let summary_text = Self::compose_progress_summary(&progress_current, &progress_past);
self.auto_state.last_decision_summary = Some(summary_text.clone());
@@ -11659,6 +11664,8 @@ impl ChatWidget<'_> {
return;
}
let full_prompt = self.auto_build_cli_message(&prompt);
self.auto_state.awaiting_submission = false;
self.auto_state.waiting_for_response = true;
self.auto_state.coordinator_waiting = false;
@@ -11695,14 +11702,33 @@ impl ChatWidget<'_> {
self.bottom_pane.update_status_text(String::new());
self.bottom_pane.set_task_running(false);
if preface.trim().is_empty() {
self.submit_text_message(prompt);
self.submit_text_message(full_prompt);
} else {
self.submit_text_message_with_preface(prompt, preface);
self.submit_text_message_with_preface(full_prompt, preface);
}
self.auto_rebuild_live_ring();
self.request_redraw();
}
fn auto_build_cli_message(&self, prompt: &str) -> String {
let context = self
.auto_state
.current_cli_context
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty());
if let Some(ctx) = context {
if ctx.ends_with('\n') {
format!("{ctx}{prompt}")
} else {
format!("{ctx}\n\n{prompt}")
}
} else {
prompt.to_string()
}
}
fn auto_pause_for_manual_edit(&mut self) {
if !self.auto_state.active || !self.auto_state.awaiting_submission {
return;
@@ -11711,12 +11737,14 @@ impl ChatWidget<'_> {
return;
};
let full_prompt = self.auto_build_cli_message(&prompt);
self.auto_state.paused_for_manual_edit = true;
self.auto_state.resume_after_manual_submit = true;
self.auto_state.countdown_id = self.auto_state.countdown_id.wrapping_add(1);
self.auto_state.seconds_remaining = AUTO_COUNTDOWN_SECONDS;
self.clear_composer();
self.insert_str(&prompt);
self.insert_str(&full_prompt);
self.bottom_pane.ensure_input_focus();
self.bottom_pane.set_task_running(true);
self.bottom_pane
@@ -109,6 +109,8 @@ struct CoordinatorDecision {
#[serde(default)]
progress_current: Option<String>,
#[serde(default)]
cli_context: Option<String>,
#[serde(default)]
cli_prompt: Option<String>,
#[serde(default)]
turn_config: Option<TurnConfig>,
@@ -118,6 +120,7 @@ struct ParsedCoordinatorDecision {
status: AutoCoordinatorStatus,
progress_past: Option<String>,
progress_current: Option<String>,
cli_context: Option<String>,
cli_prompt: Option<String>,
response_items: Vec<ResponseItem>,
turn_config: Option<TurnConfig>,
@@ -284,6 +287,7 @@ fn run_auto_loop(
status,
progress_past,
progress_current,
cli_context,
cli_prompt,
response_items,
turn_config,
@@ -313,6 +317,7 @@ fn run_auto_loop(
status,
progress_past,
progress_current,
cli_context: cli_context.clone(),
cli_prompt,
transcript: response_items,
turn_config: turn_config.clone(),
@@ -373,6 +378,7 @@ fn run_auto_loop(
status,
progress_past,
progress_current,
cli_context,
cli_prompt,
transcript: response_items,
turn_config: turn_config.clone(),
@@ -390,6 +396,7 @@ fn run_auto_loop(
status: AutoCoordinatorStatus::Failed,
progress_past: None,
progress_current: Some(format!("Coordinator error: {err}")),
cli_context: None,
cli_prompt: None,
transcript: Vec::new(),
turn_config: None,
@@ -546,6 +553,11 @@ fn build_schema() -> Value {
"maxLength": 100,
"description": "A few words describing what is happening when the CLI performs the cli_prompt. Use current tense e.g. `Now updating documentation.`"
},
"cli_context": {
"type": ["string", "null"],
"minLength": 1,
"description": "This context is background information given to the CLI which it doesn't already know. Generally only should be used at the start of a session if the auto session was started with a lot of background information. Ignored unless finish_status is 'continue'"
},
"cli_prompt": {
"type": ["string", "null"],
"minLength": 1,
@@ -557,10 +569,18 @@ fn build_schema() -> Value {
"read_only": { "type": "boolean", "description": "If true, this turn should not modify files." },
"complexity": { "type": "string", "enum": ["low","medium","high"], "description": "Complexity estimate for this turn." }
},
"required": ["read_only", "complexity"],
"additionalProperties": false
}
},
"required": ["finish_status", "progress_past", "progress_current", "cli_prompt"],
"required": [
"finish_status",
"progress_past",
"progress_current",
"cli_context",
"cli_prompt",
"turn_config"
],
"additionalProperties": false
})
}
@@ -618,6 +638,14 @@ fn request_coordinator_decision(
let progress_past = clean_field(decision.progress_past);
let progress_current = clean_field(decision.progress_current);
let cli_context = match status {
AutoCoordinatorStatus::Continue => clean_field(decision.cli_context).map(|value| {
let cleaned = strip_role_prefix(&value);
cleaned.trim().to_string()
}),
_ => None,
};
let cli_prompt = match status {
AutoCoordinatorStatus::Continue => {
let prompt_text = decision
@@ -636,6 +664,7 @@ fn request_coordinator_decision(
status,
progress_past,
progress_current,
cli_context,
cli_prompt,
response_items,
turn_config: decision.turn_config,
+25
View File
@@ -2012,6 +2012,7 @@ fn auto_decision_persists_summary_through_cli_cycle() {
AutoCoordinatorStatus::Continue,
Some("Reviewed failing integration tests.".to_string()),
Some("Running tests now.".to_string()),
None,
Some("run tests".to_string()),
Vec::new(),
);
@@ -2119,6 +2120,7 @@ fn auto_history_captures_raw_transcript() {
AutoCoordinatorStatus::Continue,
None,
Some("Next".to_string()),
None,
Some("do thing".to_string()),
transcript.clone(),
);
@@ -2126,6 +2128,29 @@ fn auto_history_captures_raw_transcript() {
assert_eq!(chat.auto_history.raw_items(), transcript.as_slice());
}
#[test]
fn auto_cli_context_is_prefixed_before_prompt() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual();
chat.auto_state.active = true;
chat.auto_state.awaiting_submission = true;
chat.auto_state.current_cli_prompt = Some("Run diagnostics".to_string());
chat.auto_state.current_cli_context = Some("Project background: mock services are offline.".to_string());
assert!(chat.pending_dispatched_user_messages.is_empty());
chat.auto_submit_prompt();
let sent = chat
.pending_dispatched_user_messages
.back()
.cloned();
assert_eq!(
sent.as_deref(),
Some("Project background: mock services are offline.\n\nRun diagnostics")
);
}
fn pump_app_events(
chat: &mut ChatWidget<'_>,
rx: &std::sync::mpsc::Receiver<AppEvent>,