fix: separate worker state from runtime lifecycle
This commit is contained in:
+37
-16
@@ -1180,18 +1180,14 @@ impl App {
|
||||
self.assistant_streaming = false;
|
||||
}
|
||||
Event::TurnStart { .. } => {
|
||||
self.set_worker_status(WorkerStatus::Running);
|
||||
self.run_requests += 1;
|
||||
self.current_tool = None;
|
||||
self.latest_llm_wait_event = None;
|
||||
self.assistant_streaming = false;
|
||||
}
|
||||
Event::InvokeStart { .. } => {
|
||||
self.set_worker_status(WorkerStatus::Running);
|
||||
}
|
||||
Event::InvokeStart { .. } => {}
|
||||
// UI consumers of per-attempt LlmCall semantics remain out of scope;
|
||||
// the run-level status starts at InvokeStart and TurnStart counts each
|
||||
// LLM request within that run.
|
||||
// authoritative run state comes only from WorkerStateSnapshot.
|
||||
Event::LlmCallStart { .. } | Event::LlmCallEnd { .. } => {
|
||||
self.latest_llm_wait_event = None;
|
||||
}
|
||||
@@ -1398,12 +1394,7 @@ impl App {
|
||||
output_tokens: self.run_output_tokens,
|
||||
});
|
||||
self.pending_submit_rollback = None;
|
||||
self.reset_run_state(match result {
|
||||
RunResult::Paused => WorkerStatus::Paused,
|
||||
RunResult::Finished | RunResult::LimitReached | RunResult::RolledBack => {
|
||||
WorkerStatus::Idle
|
||||
}
|
||||
});
|
||||
self.reset_run_state();
|
||||
}
|
||||
}
|
||||
Event::CompactStart { .. } => {
|
||||
@@ -1536,7 +1527,7 @@ impl App {
|
||||
};
|
||||
self.completion = None;
|
||||
self.close_rewind_picker();
|
||||
self.reset_run_state(self.worker_status);
|
||||
self.reset_run_state();
|
||||
let mut message = if restored_composer {
|
||||
format!(
|
||||
"Rewound session: discarded {} log entries; restored selected input to composer.",
|
||||
@@ -1584,8 +1575,7 @@ impl App {
|
||||
None
|
||||
}
|
||||
|
||||
fn reset_run_state(&mut self, status: WorkerStatus) {
|
||||
self.set_worker_status(status);
|
||||
fn reset_run_state(&mut self) {
|
||||
self.run_requests = 0;
|
||||
self.run_upload_tokens = 0;
|
||||
self.run_output_tokens = 0;
|
||||
@@ -1615,7 +1605,7 @@ impl App {
|
||||
"Rolled back empty assistant turn; no local submitted input was available to restore."
|
||||
.to_owned()
|
||||
};
|
||||
self.reset_run_state(WorkerStatus::Idle);
|
||||
self.reset_run_state();
|
||||
self.blocks.push(Block::Alert {
|
||||
level: AlertLevel::Warn,
|
||||
source: AlertSource::Worker,
|
||||
@@ -3583,6 +3573,37 @@ mod completion_flow_tests {
|
||||
assert!(matches!(app.blocks.first(), Some(Block::Greeting(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn occurrence_events_do_not_infer_foreground_worker_state() {
|
||||
let mut app = App::new("test".into());
|
||||
app.handle_worker_event(Event::TurnStart { turn: 1 });
|
||||
app.handle_worker_event(Event::InvokeStart {
|
||||
kind: protocol::InvokeKind::UserSend,
|
||||
});
|
||||
app.handle_worker_event(Event::RunEnd {
|
||||
result: RunResult::Paused,
|
||||
});
|
||||
assert_eq!(app.worker_state.state, protocol::WorkerState::Idle);
|
||||
assert_eq!(app.worker_status, WorkerStatus::Idle);
|
||||
|
||||
let running = WorkerStateSnapshot {
|
||||
execution_generation: 1,
|
||||
revision: 1,
|
||||
state: protocol::WorkerState::Busy(protocol::WorkerBusyState::Run(
|
||||
protocol::WorkerRunState::Running,
|
||||
)),
|
||||
last_command_id: 0,
|
||||
};
|
||||
app.handle_worker_event(Event::WorkerState {
|
||||
snapshot: running.clone(),
|
||||
});
|
||||
app.handle_worker_event(Event::RunEnd {
|
||||
result: RunResult::Finished,
|
||||
});
|
||||
assert_eq!(app.worker_state, running);
|
||||
assert_eq!(app.worker_status, WorkerStatus::Running);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn worker_state_events_and_acknowledgements_share_monotonic_application() {
|
||||
let mut app = App::new("test".into());
|
||||
|
||||
@@ -321,7 +321,7 @@ fn row_line(
|
||||
Span::raw(" "),
|
||||
Span::styled(
|
||||
pad_column(&worker_state(worker), widths.state),
|
||||
state_style(worker.state.as_str()),
|
||||
state_style(worker_state_label(worker)),
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::styled(
|
||||
@@ -341,8 +341,20 @@ fn worker_name(worker: &BackendWorkerSummary) -> &str {
|
||||
}
|
||||
}
|
||||
|
||||
fn worker_state_label(worker: &BackendWorkerSummary) -> &str {
|
||||
match worker.worker_state.as_ref().map(|state| &state.state) {
|
||||
Some(protocol::WorkerState::Idle) => "idle",
|
||||
Some(protocol::WorkerState::Busy(protocol::WorkerBusyState::Run(
|
||||
protocol::WorkerRunState::Paused,
|
||||
))) => "paused",
|
||||
Some(protocol::WorkerState::Busy(_)) => "running",
|
||||
None if worker.state == "stopped" => "stopped",
|
||||
None => "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
fn worker_state(worker: &BackendWorkerSummary) -> String {
|
||||
format!("[{}]", worker.state)
|
||||
format!("[{}]", worker_state_label(worker))
|
||||
}
|
||||
|
||||
fn text_width(value: &str) -> usize {
|
||||
@@ -413,7 +425,15 @@ mod tests {
|
||||
identity: "ws".to_string(),
|
||||
workspace_id: Some("ws".to_string()),
|
||||
},
|
||||
state: "running".to_string(),
|
||||
state: "idle".to_string(),
|
||||
worker_state: Some(protocol::WorkerStateSnapshot {
|
||||
execution_generation: 1,
|
||||
revision: 1,
|
||||
state: protocol::WorkerState::Busy(protocol::WorkerBusyState::Run(
|
||||
protocol::WorkerRunState::Running,
|
||||
)),
|
||||
last_command_id: 0,
|
||||
}),
|
||||
last_seen_at: None,
|
||||
pinned: false,
|
||||
retention_state: String::new(),
|
||||
@@ -450,6 +470,7 @@ mod tests {
|
||||
worker.display_name = "Coder".to_string();
|
||||
worker.label = "Coder · T-585".to_string();
|
||||
worker.state = "stopped".to_string();
|
||||
worker.worker_state = None;
|
||||
worker.working_directory = Some(
|
||||
serde_json::from_value(serde_json::json!({
|
||||
"working_directory_id": "001a06a9f0202000000",
|
||||
@@ -478,12 +499,19 @@ mod tests {
|
||||
short.label = "Coder".to_string();
|
||||
short.display_name = short.label.clone();
|
||||
short.state = "idle".to_string();
|
||||
short.worker_state = Some(protocol::WorkerStateSnapshot {
|
||||
execution_generation: 1,
|
||||
revision: 2,
|
||||
state: protocol::WorkerState::Idle,
|
||||
last_command_id: 0,
|
||||
});
|
||||
|
||||
let mut long = worker("runtime-a", "worker-b", None);
|
||||
long.resource_key = "W-100".to_string();
|
||||
long.label = "Longer worker · T-9".to_string();
|
||||
long.display_name = long.label.clone();
|
||||
long.state = "stopped".to_string();
|
||||
long.worker_state = None;
|
||||
|
||||
for worker in [&mut short, &mut long] {
|
||||
worker.working_directory = Some(
|
||||
|
||||
Reference in New Issue
Block a user