mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
feat(tui/gh): live-update gh_run_wait progress
This commit is contained in:
@@ -4878,6 +4878,7 @@ async fn handle_gh_run_wait(
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use chrono::{DateTime, Utc};
|
||||
use crate::protocol::CustomToolCallUpdateEvent;
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
struct Params {
|
||||
@@ -4992,14 +4993,14 @@ async fn handle_gh_run_wait(
|
||||
|
||||
let cwd = sess.cwd.clone();
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
#[derive(Clone, Default, PartialEq, Eq)]
|
||||
struct JobFailure {
|
||||
name: String,
|
||||
conclusion: String,
|
||||
step: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
#[derive(Clone, Default, PartialEq, Eq)]
|
||||
struct JobSummary {
|
||||
total: usize,
|
||||
completed: usize,
|
||||
@@ -5015,6 +5016,12 @@ async fn handle_gh_run_wait(
|
||||
failed_jobs: Vec<JobFailure>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
struct UpdateSnapshot {
|
||||
jobs: JobSummary,
|
||||
url: Option<String>,
|
||||
}
|
||||
|
||||
impl JobSummary {
|
||||
fn to_json(&self) -> Value {
|
||||
serde_json::json!({
|
||||
@@ -5459,6 +5466,7 @@ async fn handle_gh_run_wait(
|
||||
let interval = parsed.interval_seconds.unwrap_or(8).max(1);
|
||||
let (initial_wait_epoch, _) = sess.wait_interrupt_snapshot();
|
||||
let mut last_view = prepared_view.clone();
|
||||
let mut last_update: Option<UpdateSnapshot> = None;
|
||||
|
||||
loop {
|
||||
let view = if let Some(cached) = last_view.take() {
|
||||
@@ -5547,6 +5555,30 @@ async fn handle_gh_run_wait(
|
||||
};
|
||||
}
|
||||
|
||||
let update_url = html_url.clone().or_else(|| prepared_url.clone());
|
||||
if job_summary.total > 0 || update_url.is_some() {
|
||||
let snapshot = UpdateSnapshot {
|
||||
jobs: job_summary.clone(),
|
||||
url: update_url.clone(),
|
||||
};
|
||||
if last_update.as_ref() != Some(&snapshot) {
|
||||
last_update = Some(snapshot.clone());
|
||||
let mut update_params = serde_json::Map::new();
|
||||
update_params.insert("jobs".to_string(), snapshot.jobs.to_json());
|
||||
if let Some(url) = snapshot.url.clone() {
|
||||
update_params.insert("url".to_string(), Value::String(url));
|
||||
}
|
||||
let update_msg = EventMsg::CustomToolCallUpdate(CustomToolCallUpdateEvent {
|
||||
call_id: call_id.clone(),
|
||||
tool_name: "gh_run_wait".to_string(),
|
||||
parameters: Some(Value::Object(update_params)),
|
||||
});
|
||||
let order = sess.background_order_for_ctx(ctx, sess.current_request_ordinal());
|
||||
let event = sess.make_event_with_order(&ctx.sub_id, update_msg, order, ctx.seq_hint);
|
||||
sess.send_event(event).await;
|
||||
}
|
||||
}
|
||||
|
||||
let time_budget_message = {
|
||||
let mut guard = sess.time_budget.lock().unwrap();
|
||||
guard
|
||||
|
||||
@@ -808,6 +808,7 @@ pub enum EventMsg {
|
||||
|
||||
/// Custom tool call events for non-MCP tools (browser, agent, etc)
|
||||
CustomToolCallBegin(CustomToolCallBeginEvent),
|
||||
CustomToolCallUpdate(CustomToolCallUpdateEvent),
|
||||
CustomToolCallEnd(CustomToolCallEndEvent),
|
||||
|
||||
/// Notification that the server is about to execute a command.
|
||||
@@ -1165,6 +1166,16 @@ pub struct CustomToolCallBeginEvent {
|
||||
pub parameters: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct CustomToolCallUpdateEvent {
|
||||
/// Identifier for the corresponding CustomToolCallBegin that is still running.
|
||||
pub call_id: String,
|
||||
/// Name of the tool
|
||||
pub tool_name: String,
|
||||
/// Parameters passed to the tool as JSON
|
||||
pub parameters: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct CustomToolCallEndEvent {
|
||||
/// Identifier for the corresponding CustomToolCallBegin that finished.
|
||||
|
||||
@@ -638,6 +638,9 @@ impl EventProcessor for EventProcessorWithHumanOutput {
|
||||
}
|
||||
}
|
||||
}
|
||||
EventMsg::CustomToolCallUpdate(_) => {
|
||||
// Currently ignored in exec output.
|
||||
}
|
||||
EventMsg::CustomToolCallEnd(event) => {
|
||||
let status = if event.result.is_ok() {
|
||||
"success".style(self.green)
|
||||
|
||||
@@ -340,6 +340,7 @@ async fn run_code_tool_session_inner(
|
||||
| EventMsg::EnteredReviewMode(_)
|
||||
| EventMsg::ExitedReviewMode(_)
|
||||
| EventMsg::CustomToolCallBegin(_)
|
||||
| EventMsg::CustomToolCallUpdate(_)
|
||||
| EventMsg::CustomToolCallEnd(_)
|
||||
| EventMsg::ViewImageToolCall(_) => {
|
||||
// For now, we do not do anything extra for these
|
||||
|
||||
@@ -112,6 +112,7 @@ pub async fn run_conversation_loop(
|
||||
| EventMsg::AgentStatusUpdate(_)
|
||||
| EventMsg::ShutdownComplete
|
||||
| EventMsg::CustomToolCallBegin(_)
|
||||
| EventMsg::CustomToolCallUpdate(_)
|
||||
| EventMsg::CustomToolCallEnd(_) => {
|
||||
// For now, we do not do anything extra for these
|
||||
// events. Note that
|
||||
|
||||
@@ -160,6 +160,7 @@ use code_core::protocol::BrowserScreenshotUpdateEvent;
|
||||
use code_core::protocol::BrowserSnapshotEvent;
|
||||
use code_core::protocol::CustomToolCallBeginEvent;
|
||||
use code_core::protocol::CustomToolCallEndEvent;
|
||||
use code_core::protocol::CustomToolCallUpdateEvent;
|
||||
use code_core::protocol::ErrorEvent;
|
||||
use code_core::protocol::Event;
|
||||
use code_core::protocol::EventMsg;
|
||||
@@ -990,6 +991,7 @@ use crate::history::state::{
|
||||
RateLimitsRecord,
|
||||
TextTone,
|
||||
TextEmphasis,
|
||||
ToolArgument,
|
||||
ToolStatus,
|
||||
};
|
||||
use crate::cloud_tasks_service::CloudEnvironment;
|
||||
@@ -10020,6 +10022,63 @@ impl ChatWidget<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_tool_arguments(existing: &mut Vec<ToolArgument>, updates: Vec<ToolArgument>) {
|
||||
for update in updates {
|
||||
if let Some(existing_arg) = existing.iter_mut().find(|arg| arg.name == update.name) {
|
||||
*existing_arg = update;
|
||||
} else {
|
||||
existing.push(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_custom_tool_update(
|
||||
&mut self,
|
||||
call_id: &str,
|
||||
parameters: Option<serde_json::Value>,
|
||||
) {
|
||||
let Some(params) = parameters else {
|
||||
return;
|
||||
};
|
||||
let updates = history_cell::arguments_from_json(¶ms);
|
||||
if updates.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let running_entry = self
|
||||
.tools_state
|
||||
.running_custom_tools
|
||||
.get(&ToolCallId(call_id.to_string()))
|
||||
.copied();
|
||||
let resolved_idx = running_entry
|
||||
.as_ref()
|
||||
.and_then(|entry| running_tools::resolve_entry_index(self, entry, call_id))
|
||||
.or_else(|| running_tools::find_by_call_id(self, call_id));
|
||||
|
||||
let Some(idx) = resolved_idx else {
|
||||
return;
|
||||
};
|
||||
if idx >= self.history_cells.len() {
|
||||
return;
|
||||
}
|
||||
let Some(running_cell) = self.history_cells[idx]
|
||||
.as_any()
|
||||
.downcast_ref::<history_cell::RunningToolCallCell>()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut state = running_cell.state().clone();
|
||||
Self::merge_tool_arguments(&mut state.arguments, updates);
|
||||
let mut updated_cell = history_cell::RunningToolCallCell::from_state(state.clone());
|
||||
updated_cell.state_mut().call_id = Some(call_id.to_string());
|
||||
self.history_replace_with_record(
|
||||
idx,
|
||||
Box::new(updated_cell),
|
||||
HistoryDomainRecord::from(state),
|
||||
);
|
||||
}
|
||||
|
||||
fn hydrate_cell_from_record(
|
||||
&self,
|
||||
cell: &mut Box<dyn HistoryCell>,
|
||||
@@ -14396,6 +14455,13 @@ impl ChatWidget<'_> {
|
||||
.update_status_text(format!("using tool: {}", tool_name));
|
||||
}
|
||||
}
|
||||
EventMsg::CustomToolCallUpdate(CustomToolCallUpdateEvent {
|
||||
call_id,
|
||||
tool_name: _,
|
||||
parameters,
|
||||
}) => {
|
||||
self.apply_custom_tool_update(&call_id, parameters);
|
||||
}
|
||||
EventMsg::CustomToolCallEnd(CustomToolCallEndEvent {
|
||||
call_id,
|
||||
tool_name,
|
||||
|
||||
@@ -128,6 +128,7 @@ pub(crate) use registry::{cell_from_record, lines_from_record, record_from_cell}
|
||||
pub(crate) use stream::{new_streaming_content, stream_lines_from_state, StreamingContentCell};
|
||||
pub(crate) use tool::{RunningToolCallCell, ToolCallCell};
|
||||
pub(crate) use tool_factory::{
|
||||
arguments_from_json,
|
||||
new_completed_custom_tool_call,
|
||||
new_completed_mcp_tool_call,
|
||||
new_completed_web_fetch_tool_call,
|
||||
|
||||
@@ -114,11 +114,11 @@ fn argument_value_from_json(value: &serde_json::Value) -> ArgumentValue {
|
||||
}
|
||||
}
|
||||
|
||||
fn arguments_from_json(value: &serde_json::Value) -> Vec<ToolArgument> {
|
||||
pub(crate) fn arguments_from_json(value: &serde_json::Value) -> Vec<ToolArgument> {
|
||||
arguments_from_json_excluding(value, &[])
|
||||
}
|
||||
|
||||
fn arguments_from_json_excluding(
|
||||
pub(crate) fn arguments_from_json_excluding(
|
||||
value: &serde_json::Value,
|
||||
exclude: &[&str],
|
||||
) -> Vec<ToolArgument> {
|
||||
|
||||
Reference in New Issue
Block a user