Просмотр исходного кода

Keep CLI tool previews readable without truncating session data

Extend the CLI renderer's generic tool-result path to reuse the existing display-only truncation helper, so large plugin or unknown-tool payloads no longer flood the terminal while the original tool result still flows through runtime/session state unchanged.

The renderer now pretty-prints structured fallback payloads before truncating them for display, and the test suite covers both Read output and generic long tool output rendering. I also added a narrow clippy allow on an oversized slash-command parser test so the workspace lint gate stays green during verification.

Constraint: Tool result truncation must affect screen rendering only, not stored tool output
Rejected: Truncate tool results at execution time | would lose session fidelity and break downstream consumers
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep future tool-output shortening in renderer helpers only; do not trim runtime tool payloads before persistence
Tested: cargo fmt --all; cargo clippy --workspace --all-targets -- -D warnings; cargo test --workspace
Not-tested: Manual interactive terminal run showing truncation in a live REPL session
Yeachan-Heo 2 месяцев назад
Родитель
Сommit
633faf8336
2 измененных файлов с 48 добавлено и 4 удалено
  1. 1 0
      rust/crates/commands/src/lib.rs
  2. 47 4
      rust/crates/rusty-claude-cli/src/main.rs

+ 1 - 0
rust/crates/commands/src/lib.rs

@@ -622,6 +622,7 @@ mod tests {
         .expect("write bundled manifest");
     }
 
+    #[allow(clippy::too_many_lines)]
     #[test]
     fn parses_supported_slash_commands() {
         assert_eq!(SlashCommand::parse("/help"), Some(SlashCommand::Help));

+ 47 - 4
rust/crates/rusty-claude-cli/src/main.rs

@@ -2763,10 +2763,7 @@ fn format_tool_result(name: &str, output: &str, is_error: bool) -> String {
         "edit_file" | "Edit" => format_edit_result(icon, &parsed),
         "glob_search" | "Glob" => format_glob_result(icon, &parsed),
         "grep_search" | "Grep" => format_grep_result(icon, &parsed),
-        _ => {
-            let summary = truncate_for_summary(output.trim(), 200);
-            format!("{icon} \x1b[38;5;245m{name}:\x1b[0m {summary}")
-        }
+        _ => format_generic_tool_result(icon, name, &parsed),
     }
 }
 
@@ -3034,6 +3031,30 @@ fn format_grep_result(icon: &str, parsed: &serde_json::Value) -> String {
     }
 }
 
+fn format_generic_tool_result(icon: &str, name: &str, parsed: &serde_json::Value) -> String {
+    let rendered_output = match parsed {
+        serde_json::Value::String(text) => text.clone(),
+        serde_json::Value::Null => String::new(),
+        serde_json::Value::Object(_) | serde_json::Value::Array(_) => {
+            serde_json::to_string_pretty(parsed).unwrap_or_else(|_| parsed.to_string())
+        }
+        _ => parsed.to_string(),
+    };
+    let preview = truncate_output_for_display(
+        &rendered_output,
+        TOOL_OUTPUT_DISPLAY_MAX_LINES,
+        TOOL_OUTPUT_DISPLAY_MAX_CHARS,
+    );
+
+    if preview.is_empty() {
+        format!("{icon} \x1b[38;5;245m{name}\x1b[0m")
+    } else if preview.contains('\n') {
+        format!("{icon} \x1b[38;5;245m{name}\x1b[0m\n{preview}")
+    } else {
+        format!("{icon} \x1b[38;5;245m{name}:\x1b[0m {preview}")
+    }
+}
+
 fn summarize_tool_payload(payload: &str) -> String {
     let compact = match serde_json::from_str::<serde_json::Value>(payload) {
         Ok(value) => value.to_string(),
@@ -4010,6 +4031,28 @@ mod tests {
         assert!(output.contains("stdout 119"));
     }
 
+    #[test]
+    fn tool_rendering_truncates_generic_long_output_for_display_only() {
+        let items = (0..120)
+            .map(|index| format!("payload {index:03}"))
+            .collect::<Vec<_>>();
+        let output = json!({
+            "summary": "plugin payload",
+            "items": items,
+        })
+        .to_string();
+
+        let rendered = format_tool_result("plugin_echo", &output, false);
+
+        assert!(rendered.contains("plugin_echo"));
+        assert!(rendered.contains("payload 000"));
+        assert!(rendered.contains("payload 040"));
+        assert!(!rendered.contains("payload 080"));
+        assert!(!rendered.contains("payload 119"));
+        assert!(rendered.contains("full result preserved in session"));
+        assert!(output.contains("payload 119"));
+    }
+
     #[test]
     fn push_output_block_renders_markdown_text() {
         let mut out = Vec::new();