Synced from monorepo

Synced from monorepo

Changes:
- grok-shell: send an expired external-provider credential to the sign-in flow, not a 401 loop
- pager: clickable ▲ jumps to the top of the response being read
- grok-shell: keep a large task log from making the completion message too long
- Plan viewer scrollbar: widen grab zone to the border column; fix striped thumb in Terminal.app
- pager: poll the tmux probe teardown grace instead of sleeping it
- security: vendor-compat MCP kill switch is now actually enforced when reported as on
- grok-shell: restore session eviction when a leader client disconnects
- Bump rust-toolchain to 1.93.0
- workspace: lexical-normalize permission path patterns before glob matching
- pager: reject garbage Enter in the /resume picker
- pager: show Mermaid affordances in plan mode preview
- pager: drop manage-account link from /session-info
- workspace: auto-approve read-only git queries; defer write floor to auto classifier
- Add free-form pattern editor to the "Always allow" command prompt
- grok-shell: fix /btw caching
- pager: Tab walks answers in the ask_user_question card
- External-provider auth refresh: single 7s attempt instead of 3×5s
- pager: don't resurrect finished background tasks as Running when completion arrives first
- pager: report tmux truecolor clamping in Doctor
- Fix plan viewer scrollbar click+drag hijacked by comment gutter
- pager/shell: stop double Recap after the same last turn
- sampler: preserve x-should-retry through stream collection
- pager: clear plan-mode indicator immediately when the user approves a plan
- pager: tmux does not re-read its config on reattach

Source-Revision: 64c4de99cc822b25ce9c54ab5a4f372093d0885d
This commit is contained in:
grokkybara[bot] 2026-08-03 08:17:57 +00:00
commit 780d1388ff
323 changed files with 12258 additions and 7226 deletions

View file

@ -92,25 +92,61 @@ pub fn truncate_str(s: &str, max_bytes: usize) -> &str {
&s[..end]
}
/// Text on hand, and the size of the output it came from. The two differ when
/// the caller holds only part of a larger output and the reader still needs
/// the real size.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PartialOutput<'a> {
text: &'a str,
total_bytes: usize,
}
impl<'a> PartialOutput<'a> {
pub fn whole(text: &'a str) -> Self {
Self {
text,
total_bytes: text.len(),
}
}
/// Part of an output of `total_bytes`.
pub fn part_of(text: &'a str, total_bytes: usize) -> Self {
Self {
text,
total_bytes: total_bytes.max(text.len()),
}
}
pub fn text(&self) -> &'a str {
self.text
}
pub fn total_bytes(&self) -> usize {
self.total_bytes
}
}
/// Truncate output to a UTF-8-safe preview plus a model-visible footer.
///
/// The cap decides whether truncation happens. When triggered, the returned
/// value contains the first `preview_bytes` bytes snapped to a char boundary
/// followed by `[Output truncated - <N> bytes total...]`.
/// followed by `[Output truncated - <N> bytes total...]`, where `N` is the
/// size of the whole output, not of the part on hand.
pub fn truncate_with_preview(
output: &str,
output: PartialOutput<'_>,
max_bytes: usize,
preview_bytes: usize,
footer_hint: Option<&str>,
) -> (String, bool) {
if output.len() <= max_bytes {
return (output.to_string(), false);
let PartialOutput { text, total_bytes } = output;
if text.len() <= max_bytes {
return (text.to_string(), false);
}
let preview = truncate_str(output, preview_bytes.min(output.len()));
let preview = truncate_str(text, preview_bytes.min(text.len()));
let footer = match footer_hint {
Some(hint) => format!("[Output truncated - {} bytes total. {hint}]", output.len()),
None => format!("[Output truncated - {} bytes total]", output.len()),
Some(hint) => format!("[Output truncated - {total_bytes} bytes total. {hint}]"),
None => format!("[Output truncated - {total_bytes} bytes total]"),
};
(format!("{preview}\n\n{footer}"), true)
}
@ -226,6 +262,9 @@ pub fn soft_wrap_lines(text: &str, wrap_width: usize) -> String {
result
}
/// Separator between the retained head and tail of a truncated output.
pub(crate) const FRONT_BACK_TRUNCATION_MARKER: &str = "\n\n... (output truncated) ...\n\n";
/// Truncate a string keeping the first half and last half of the character
/// budget, inserting a separator in the middle.
///
@ -252,7 +291,7 @@ pub fn truncate_front_and_back(s: &str, max_chars: usize) -> (String, bool) {
.unwrap_or(0)
}
};
let ellipsis = "\n\n... (output truncated) ...\n\n";
let ellipsis = FRONT_BACK_TRUNCATION_MARKER;
let mut result = String::with_capacity(front_end + ellipsis.len() + (s.len() - back_start));
result.push_str(&s[..front_end]);
result.push_str(ellipsis);
@ -505,7 +544,7 @@ mod tests {
#[test]
fn truncate_with_preview_short_output_unchanged() {
let (result, truncated) = truncate_with_preview("hello", 10, 5, None);
let (result, truncated) = truncate_with_preview(PartialOutput::whole("hello"), 10, 5, None);
assert_eq!(result, "hello");
assert!(!truncated);
}
@ -513,7 +552,8 @@ mod tests {
#[test]
fn truncate_with_preview_caps_large_output() {
let output = "x".repeat(5_000_000);
let (result, truncated) = truncate_with_preview(&output, 4_000, 2_000, None);
let (result, truncated) =
truncate_with_preview(PartialOutput::whole(&output), 4_000, 2_000, None);
assert!(truncated);
assert!(result.len() < 2_200, "result was {} bytes", result.len());
@ -524,7 +564,8 @@ mod tests {
#[test]
fn truncate_with_preview_utf8_boundary() {
let output = "😀".repeat(1_500);
let (result, truncated) = truncate_with_preview(&output, 4_000, 2_001, None);
let (result, truncated) =
truncate_with_preview(PartialOutput::whole(&output), 4_000, 2_001, None);
assert!(truncated);
assert!(result.starts_with(&"😀".repeat(500)));
@ -535,7 +576,7 @@ mod tests {
fn truncate_with_preview_with_footer_hint() {
let output = "x".repeat(10_000);
let (result, truncated) = truncate_with_preview(
&output,
PartialOutput::whole(&output),
4_000,
2_000,
Some("Use read_file for full content"),
@ -545,10 +586,22 @@ mod tests {
assert!(result.contains("Use read_file for full content"));
}
/// A caller holding part of a larger output states the real size.
#[test]
fn truncate_with_preview_reports_the_size_it_is_given() {
let held = "x".repeat(10_000);
let (result, truncated) =
truncate_with_preview(PartialOutput::part_of(&held, 5_000_000), 4_000, 2_000, None);
assert!(truncated);
assert!(result.contains("5000000 bytes total"), "{result}");
}
#[test]
fn truncate_with_preview_without_footer_hint() {
let output = "x".repeat(10_000);
let (result, truncated) = truncate_with_preview(&output, 4_000, 2_000, None);
let (result, truncated) =
truncate_with_preview(PartialOutput::whole(&output), 4_000, 2_000, None);
assert!(truncated);
assert!(result.contains("[Output truncated - 10000 bytes total]"));