fix(tui): surface concrete run errors instead of a bare error header
StreamEvent::Error is encoded by the agent loop as an assistant message with stop_reason=error and error_message, but the UI only rendered the header and the (empty) content, hiding the actual message (e.g. a provider HTTP 400 detail). Now: - UiBlock::Error renders error_message in red, for both committed and live assistant messages - on job Done, the error is also surfaced into the status bar and a note, even when the job itself reported no error (it was encoded as a message) - regression test: an error message renders its concrete text
This commit is contained in:
parent
4ffd82b096
commit
a9de6fe24e
|
|
@ -438,8 +438,22 @@ impl App {
|
|||
|
||||
self.persist_entries(&to_persist);
|
||||
self.transcript = messages;
|
||||
if let Some(err) = error {
|
||||
self.status = format!("⚠ {}", err);
|
||||
// 错误优先取 job 报告的 error;若错误被编码成消息(如 provider 的
|
||||
// HTTP 400 → StreamEvent::Error → assistant error 消息),
|
||||
// 从最后一条 assistant 消息的 error_message 提取。
|
||||
// Prefer the job's reported error; when the error is encoded as
|
||||
// a message (provider HTTP 400 → StreamEvent::Error → an
|
||||
// assistant error message), extract it from the last assistant.
|
||||
let surfaced = error.or_else(|| {
|
||||
self.transcript.iter().rev().find_map(|m| match m {
|
||||
Message::Assistant(a) if a.stop_reason == StopReason::Error => {
|
||||
a.error_message.clone()
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
});
|
||||
if let Some(err) = surfaced {
|
||||
self.status = format!("⚠ {}", crate::format::truncate(&err, 80));
|
||||
self.push_note(format!("运行出错:{}", err));
|
||||
} else {
|
||||
self.status = "✓ 完成".into();
|
||||
|
|
|
|||
|
|
@ -77,6 +77,9 @@ pub enum UiBlock {
|
|||
is_error: bool,
|
||||
expanded: bool,
|
||||
},
|
||||
/// 运行错误(assistant 消息的 error_message,红色显示)。
|
||||
/// A run error (an assistant message's error_message, shown in red).
|
||||
Error { id: BlockId, text: String },
|
||||
/// 工具执行过程(运行中 / 完成,含耗时与输出)。
|
||||
/// A tool execution (running / done, with duration and output).
|
||||
ToolExec {
|
||||
|
|
@ -382,6 +385,14 @@ pub fn build_blocks(
|
|||
expanded,
|
||||
);
|
||||
}
|
||||
// 具体错误文本(如 provider 的 HTTP 400 详情)。
|
||||
// The concrete error text (e.g. a provider HTTP 400 detail).
|
||||
if let Some(err) = &a.error_message {
|
||||
out.push(UiBlock::Error {
|
||||
id: BlockId(format!("m{}-err", mi)),
|
||||
text: err.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Message::ToolResult(t) => {
|
||||
out.push(UiBlock::ToolResult {
|
||||
|
|
@ -414,6 +425,12 @@ pub fn build_blocks(
|
|||
expanded,
|
||||
);
|
||||
}
|
||||
if let Some(err) = &partial.error_message {
|
||||
out.push(UiBlock::Error {
|
||||
id: BlockId("live-err".into()),
|
||||
text: err.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
for (ei, exec) in rs.tool_execs.iter().enumerate() {
|
||||
out.push(UiBlock::ToolExec {
|
||||
|
|
@ -485,7 +502,8 @@ impl UiBlock {
|
|||
| UiBlock::Thinking { id, .. }
|
||||
| UiBlock::ToolCall { id, .. }
|
||||
| UiBlock::ToolResult { id, .. }
|
||||
| UiBlock::ToolExec { id, .. } => Some(id),
|
||||
| UiBlock::ToolExec { id, .. }
|
||||
| UiBlock::Error { id, .. } => Some(id),
|
||||
UiBlock::Note { .. } => None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -271,6 +271,14 @@ fn block_lines(block: &UiBlock, width: usize) -> Vec<Line<'static>> {
|
|||
)));
|
||||
}
|
||||
}
|
||||
UiBlock::Error { text, .. } => {
|
||||
for line in wrap_text(text, width) {
|
||||
out.push(Line::from(Span::styled(
|
||||
line,
|
||||
Style::new().fg(Color::Red).bold(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
UiBlock::ToolCall {
|
||||
name,
|
||||
summary,
|
||||
|
|
|
|||
|
|
@ -279,3 +279,26 @@ fn working_phase_detection() {
|
|||
});
|
||||
assert_eq!(rs.working_phase(), "streaming…");
|
||||
}
|
||||
|
||||
/// 错误消息必须渲染出具体的错误文本(而不是只有消息头)。
|
||||
/// An error message must render its concrete error text (not just a header).
|
||||
#[test]
|
||||
fn error_message_renders_as_error_block() {
|
||||
let transcript = vec![Message::Assistant(AssistantMessage {
|
||||
content: Vec::new(),
|
||||
model: "m".into(),
|
||||
usage: Usage::default(),
|
||||
stop_reason: StopReason::Error,
|
||||
error_message: Some("http error 400: bad request detail".into()),
|
||||
timestamp: 0,
|
||||
})];
|
||||
let blocks = build_blocks(&transcript, &HashMap::new(), None, &HashSet::new());
|
||||
let err = blocks
|
||||
.iter()
|
||||
.find_map(|b| match b {
|
||||
UiBlock::Error { text, .. } => Some(text.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.expect("error block");
|
||||
assert_eq!(err, "http error 400: bad request detail");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue