Synced from monorepo
Changes: - Stop hooks for session lifecycle - Add x.ai/session/state and x.ai/session/import ACP methods - Deny-and-continue for auto-mode classifier blocks with denial limits - Drop codebase-upload from dhat soak test - scheduler_create upsert via task_id; retire one-shot tasks - Clipboard: copy file fallback + honest toasts for SSH/Apple Terminal - Polarity-safe syntax colors in minimal mode - Auto mode classifies unvetted env prefixes instead of hard-prompting - Add GROK_CLIPBOARD_NO_OSC52 kill switch to force OSC 52 off
This commit is contained in:
parent
7cfcb20d2b
commit
ba76b0a683
143 changed files with 9465 additions and 3419 deletions
|
|
@ -1885,6 +1885,7 @@ mod tests {
|
|||
created_at: std::time::Instant::now(),
|
||||
next_fire_at: None,
|
||||
tag: "loop".into(),
|
||||
last_subagent_id: None,
|
||||
}
|
||||
}
|
||||
/// A turn-idle agent with a RUNNING background task is `Working`, not
|
||||
|
|
|
|||
|
|
@ -1146,7 +1146,13 @@ fn highlight_to_ratatui_line(
|
|||
if piece.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let fg = syntect_to_ratatui_color(style.foreground);
|
||||
// Shared path: polarity-safe under the terminal-native lock, else
|
||||
// normal theme quantize (see xai_grok_pager_render::syntax).
|
||||
let fg = crate::syntax::syntect_rgb_to_fg(
|
||||
style.foreground.r,
|
||||
style.foreground.g,
|
||||
style.foreground.b,
|
||||
);
|
||||
spans.push(Span::styled(piece, Style::default().fg(fg)));
|
||||
}
|
||||
|
||||
|
|
@ -1156,13 +1162,6 @@ fn highlight_to_ratatui_line(
|
|||
Line::from(spans)
|
||||
}
|
||||
|
||||
/// Convert a syntect RGBA color to a ratatui Color.
|
||||
///
|
||||
/// Quantizes the RGB value to the terminal's supported color level.
|
||||
fn syntect_to_ratatui_color(c: syntect::highlighting::Color) -> ratatui::style::Color {
|
||||
crate::theme::quantize(ratatui::style::Color::Rgb(c.r, c.g, c.b))
|
||||
}
|
||||
|
||||
/// Count digits in a number (for line number padding).
|
||||
fn digit_count(n: usize) -> usize {
|
||||
if n == 0 {
|
||||
|
|
|
|||
|
|
@ -238,6 +238,7 @@ pub enum TaskEntry {
|
|||
label: String,
|
||||
styled: Line<'static>,
|
||||
started_at: Instant,
|
||||
linked_subagent: Option<String>,
|
||||
},
|
||||
/// Collapsible group header row (e.g. `▾ Subagents 2`). Not a task —
|
||||
/// selecting it and pressing Enter (or clicking it) toggles the group's
|
||||
|
|
@ -448,7 +449,9 @@ impl TaskEntry {
|
|||
info: &ScheduledTaskInfo,
|
||||
current_cron: Option<&str>,
|
||||
is_queued: bool,
|
||||
linked: Option<(String, bool)>,
|
||||
) -> Self {
|
||||
let linked_running = linked.as_ref().is_some_and(|(_, running)| *running);
|
||||
let theme = Theme::current();
|
||||
let prompt_preview = if info.prompt.chars().count() > 60 {
|
||||
info.prompt.chars().take(57).collect::<String>() + "..."
|
||||
|
|
@ -469,7 +472,7 @@ impl TaskEntry {
|
|||
}
|
||||
};
|
||||
let is_provisional = info.task_id.starts_with("provisional-");
|
||||
let suffix = if current_cron == Some(&info.task_id) {
|
||||
let suffix = if current_cron == Some(&info.task_id) || linked_running {
|
||||
" (running)".to_string()
|
||||
} else if is_queued {
|
||||
" (queued)".to_string()
|
||||
|
|
@ -501,7 +504,7 @@ impl TaskEntry {
|
|||
}
|
||||
};
|
||||
let label = format!(
|
||||
"{} {} {}{}",
|
||||
"{} {} \u{b7} {}{}",
|
||||
tag_display, info.human_schedule, &prompt_preview, &suffix
|
||||
);
|
||||
|
||||
|
|
@ -510,11 +513,11 @@ impl TaskEntry {
|
|||
// neutral secondary text color so the row reads calmly with a single
|
||||
// point of color. No surrounding `[ ]` brackets: the color alone
|
||||
// sets the tag apart from the schedule that follows it.
|
||||
let schedule_style = format!("{} ", info.human_schedule);
|
||||
let schedule_style = format!("{} \u{b7} ", info.human_schedule);
|
||||
let neutral = Style::default().fg(theme.text_secondary);
|
||||
let styled = Line::from(vec![
|
||||
Span::styled(
|
||||
format!("{} ", tag_display),
|
||||
format!("{} ", tag_display),
|
||||
Style::default().fg(theme.accent_system),
|
||||
),
|
||||
Span::styled(schedule_style, neutral),
|
||||
|
|
@ -537,6 +540,7 @@ impl TaskEntry {
|
|||
label,
|
||||
styled,
|
||||
started_at: info.created_at,
|
||||
linked_subagent: linked.map(|(sid, _)| sid),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -660,7 +664,7 @@ impl ListItem for TaskEntry {
|
|||
enum OverlayEntryData {
|
||||
BgTask(String),
|
||||
Agent(String, String),
|
||||
Scheduled(String),
|
||||
Scheduled(String, Option<String>),
|
||||
}
|
||||
|
||||
const MAX_TASKS_HEIGHT: u16 = 8;
|
||||
|
|
@ -834,10 +838,17 @@ impl TasksPane {
|
|||
|
||||
// Add scheduled task items (always "running")
|
||||
for info in scheduled.values() {
|
||||
let linked = info.last_subagent_id.as_deref().and_then(|sid| {
|
||||
subagents
|
||||
.values()
|
||||
.find(|s| s.subagent_id.as_ref() == sid)
|
||||
.map(|s| (sid.to_string(), s.is_running()))
|
||||
});
|
||||
self.items.push(TaskEntry::from_scheduled(
|
||||
info,
|
||||
current_cron_task_id,
|
||||
queued_cron_ids.contains(info.task_id.as_str()),
|
||||
linked,
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -1307,9 +1318,11 @@ impl TasksPane {
|
|||
child_session_id,
|
||||
..
|
||||
} => OverlayEntryData::Agent(subagent_id.clone(), child_session_id.clone()),
|
||||
TaskEntry::Scheduled { task_id, .. } => {
|
||||
OverlayEntryData::Scheduled(task_id.clone())
|
||||
}
|
||||
TaskEntry::Scheduled {
|
||||
task_id,
|
||||
linked_subagent,
|
||||
..
|
||||
} => OverlayEntryData::Scheduled(task_id.clone(), linked_subagent.clone()),
|
||||
// Group headers have no kill/view buttons; they still
|
||||
// occupy a row (vis_row is enumerated before this filter),
|
||||
// so the y offsets for following items stay correct.
|
||||
|
|
@ -1333,8 +1346,15 @@ impl TasksPane {
|
|||
};
|
||||
self.render_agent_overlay(area, buf, y, subagent_id, info, &theme);
|
||||
}
|
||||
OverlayEntryData::Scheduled(ref task_id) => {
|
||||
self.render_scheduled_overlay(area, buf, y, task_id, &theme);
|
||||
OverlayEntryData::Scheduled(ref task_id, ref linked_subagent) => {
|
||||
self.render_scheduled_overlay(
|
||||
area,
|
||||
buf,
|
||||
y,
|
||||
task_id,
|
||||
linked_subagent.as_deref(),
|
||||
&theme,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1643,6 +1663,7 @@ impl TasksPane {
|
|||
buf: &mut Buffer,
|
||||
y: u16,
|
||||
task_id: &str,
|
||||
linked_subagent: Option<&str>,
|
||||
theme: &Theme,
|
||||
) {
|
||||
let frames = crate::glyphs::dot_spinner_frames();
|
||||
|
|
@ -1654,8 +1675,8 @@ impl TasksPane {
|
|||
2,
|
||||
);
|
||||
|
||||
// Clear overlay area (kill button + separator = 4 cols).
|
||||
clear_overlay_area(buf, area, y, 4);
|
||||
let overlay_cols = if linked_subagent.is_some() { 7 } else { 4 };
|
||||
clear_overlay_area(buf, area, y, overlay_cols);
|
||||
|
||||
let mut rx = area.x + area.width;
|
||||
|
||||
|
|
@ -1681,6 +1702,29 @@ impl TasksPane {
|
|||
Rect::new(rx, y, 3, 1),
|
||||
));
|
||||
|
||||
if linked_subagent.is_some() {
|
||||
rx = rx.saturating_sub(3);
|
||||
let is_view_hovered = matches!(
|
||||
&self.hovered_view,
|
||||
Some(TaskEntryId::Scheduled(tid)) if tid == task_id
|
||||
);
|
||||
let view_style = if is_view_hovered {
|
||||
Style::default().fg(theme.text_primary)
|
||||
} else {
|
||||
Style::default().fg(theme.gray)
|
||||
};
|
||||
buf.set_span(
|
||||
rx,
|
||||
y,
|
||||
&Span::styled(crate::glyphs::enlarge_button(), view_style),
|
||||
3,
|
||||
);
|
||||
self.view_button_rects.push((
|
||||
TaskEntryId::Scheduled(task_id.to_string()),
|
||||
Rect::new(rx, y, 3, 1),
|
||||
));
|
||||
}
|
||||
|
||||
if rx > area.x {
|
||||
buf.set_span(rx - 1, y, &Span::raw(" "), 1);
|
||||
}
|
||||
|
|
@ -2899,6 +2943,7 @@ mod tests {
|
|||
created_at: std::time::Instant::now(),
|
||||
next_fire_at: next.map(|s| s.to_string()),
|
||||
tag: "loop".into(),
|
||||
last_subagent_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue