fix: restore interactive sudo input and suppress duplicate bash output - #186
Conversation
|
Thanks for the pull request. A maintainer will review it when available. Please keep the PR focused, explain the why in the description, and make sure local checks pass before requesting review. Contribution guide: https://github.com/AI-Shell-Team/aish/blob/main/CONTRIBUTING.md |
|
This pull request description looks incomplete. Please update the missing sections below before review. Missing items:
|
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughThis PR enhances interactive bash command execution by adding detection, display control, and event enrichment. It exposes interactive-command detection to the shell, adds PTY display control with authentication-noise filtering, enriches tool events with command arguments, and coordinates shell loops with interactive input status to avoid interference during password prompts. ChangesInteractive Bash Tool Execution
Sequence DiagramsequenceDiagram
participant Shell as ShellApp
participant Bash as BashTool
participant PTY as PersistentPty
participant EventLoop as EventCallback
Shell->>Bash: execute bash tool command
Bash->>Bash: detect if command needs interactive
Bash->>Bash: set INTERACTIVE_INPUT_ACTIVE = true
Bash->>PTY: execute_command(..., display_output)
PTY->>PTY: clean output, strip auth noise
PTY-->>Bash: cleaned output + exit code
Bash->>Bash: reset INTERACTIVE_INPUT_ACTIVE = false
Bash-->>Shell: command result
Shell->>EventLoop: emit ToolExecutionEnd with tool_args
EventLoop->>EventLoop: suppress preview if interactive
EventLoop->>Shell: event handled
🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (2)
crates/aish-pty/src/persistent.rs (1)
3571-3579: ⚡ Quick winNarrow auth-prompt filtering trigger to command token, not substring.
command_may_prompt_for_authmatches with broadcontains(...), so unrelated commands that merely include words likesudo/sshin arguments can trigger auth-line stripping and lose valid output. Parsing first executable token (same style as interactive detection) would reduce false positives.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/aish-pty/src/persistent.rs` around lines 3571 - 3579, The auth prompt detector command_may_prompt_for_auth currently uses substring checks and should instead inspect only the command's first token (executable) to avoid false positives; change it to split_whitespace() (or the same token parsing used by the interactive detection) and run checks against that first token (e.g., token == "sudo" or token.starts_with("su") or token == "ssh") rather than using contains()/contains(" ssh "), so only the actual invoked program triggers auth-line stripping.crates/aish-shell/src/app.rs (1)
1708-1713: ⚡ Quick winUse
lock_pty()here for consistent poison recovery.This path bypasses the helper introduced earlier in the file, so a poisoned PTY mutex will panic here even though the normal execution paths recover and keep going.
♻️ Suggested change
- let _ = self.pty.lock().unwrap().execute_command( + let _ = self.lock_pty().execute_command( command, std::time::Duration::from_secs(5), None, false, );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/aish-shell/src/app.rs` around lines 1708 - 1713, The call uses self.pty.lock().unwrap().execute_command(...) which will panic on a poisoned mutex; replace this direct lock with the helper lock_pty() used elsewhere to get the PTY guard (so poison recovery is applied) and then call execute_command on that guard with the same arguments; specifically, locate the occurrence of self.pty.lock().unwrap().execute_command and change it to use lock_pty() to obtain the guard before calling execute_command.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/aish-llm/src/session.rs`:
- Around line 1463-1467: The new test block has formatting drift around the
let-binding for result at session.execute_tool_external(&tool_call).await and
the seen_event lock/unwrap clone lines; run rustfmt (cargo fmt) to reformat the
file so the let-binding wrapping and subsequent assertions match project style,
or manually adjust the indentation/line-wrapping in the test (around
session.execute_tool_external,
seen_event.lock().unwrap().clone().expect("missing ToolExecutionEnd event"), and
the assert_eq! block) to match rustfmt output.
In `@crates/aish-pty/src/persistent.rs`:
- Around line 292-300: The display_output branch currently calls libc::write and
ignores its return value (involving tmp, n, and libc::STDOUT_FILENO), which can
drop data on short writes; replace that call with a small write-all loop that
retries on EINTR and advances the buffer by the number of bytes written until
all n bytes are written (handling partial writes and returning or breaking on
unrecoverable errors). Implement the loop around libc::write in the same unsafe
block used now, check for negative returns to map errno, treat EINTR as retry,
subtract the bytes_written from the remaining count and advance the pointer (or
index) accordingly, and only exit when total bytes written equals n or an
unrecoverable error occurs.
In `@crates/aish-shell/src/app.rs`:
- Around line 3417-3418: The regex initializer for TOOL_XML_RETURN_CODE_RE
(assigned to re_return_code via get_or_init and calling
regex::Regex::new(r"(?s)<(?:return_code|exit-code)>.*?</(?:return_code|exit-code)>").unwrap())
is not formatted to Rustfmt standards; run rustfmt (or `cargo fmt`) to reformat
this block so the expression, method chaining, and indentation meet `cargo fmt
--check`, then commit the formatted change so CI passes.
- Around line 3416-3419: The current regex TOOL_XML_RETURN_CODE_RE (used to
initialize re_return_code) is applied to the entire preview string (cleaned) and
can remove legitimate <return_code> or <exit-code> fragments from command
stdout; instead, first isolate the tool metadata block (the trailing wrapper
content inside the <stdout> or the metadata section) and apply the regex only to
that substring, or move the replace_all call to after you peel off the <stdout>
wrapper; update the logic around the variable cleaned and the use of
re_return_code so you only strip return-code/exit-code tags within the tool
metadata region rather than the full preview.
In `@crates/aish-tools/src/bash.rs`:
- Line 269: The CI failure is due to formatting at the call site of
pty.execute_command(command, command_timeout, Some(&cancel_token), interactive);
— run rustfmt (or cargo fmt) to reformat crates/aish-tools/src/bash.rs so the
call wraps/aligns per rustfmt rules (or manually adjust the call to a
rustfmt-friendly layout, e.g., place args on separate indented lines) and re-run
cargo fmt --check to ensure the formatting issue is resolved.
- Around line 266-272: Replace the global boolean INTERACTIVE_INPUT_ACTIVE
toggles with an atomic reference count: increment (fetch_add(1)) before calling
pty.execute_command(...) and decrement (fetch_sub(1)) after it (ensuring you
never underflow) so overlapping interactive commands keep the flag active until
the last one finishes; update both occurrences where you currently call
INTERACTIVE_INPUT_ACTIVE.store(true/false, Ordering::SeqCst) around
pty.execute_command (the blocks using interactive, INTERACTIVE_INPUT_ACTIVE,
pty.execute_command, command_timeout and cancel_token) to use the atomic counter
instead and use Ordering::SeqCst for increments/decrements to preserve ordering.
---
Nitpick comments:
In `@crates/aish-pty/src/persistent.rs`:
- Around line 3571-3579: The auth prompt detector command_may_prompt_for_auth
currently uses substring checks and should instead inspect only the command's
first token (executable) to avoid false positives; change it to
split_whitespace() (or the same token parsing used by the interactive detection)
and run checks against that first token (e.g., token == "sudo" or
token.starts_with("su") or token == "ssh") rather than using
contains()/contains(" ssh "), so only the actual invoked program triggers
auth-line stripping.
In `@crates/aish-shell/src/app.rs`:
- Around line 1708-1713: The call uses
self.pty.lock().unwrap().execute_command(...) which will panic on a poisoned
mutex; replace this direct lock with the helper lock_pty() used elsewhere to get
the PTY guard (so poison recovery is applied) and then call execute_command on
that guard with the same arguments; specifically, locate the occurrence of
self.pty.lock().unwrap().execute_command and change it to use lock_pty() to
obtain the guard before calling execute_command.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: e23a3366-5d34-44b5-a1fe-c96018010d2f
📒 Files selected for processing (5)
crates/aish-llm/src/session.rscrates/aish-pty/src/persistent.rscrates/aish-shell/src/app.rscrates/aish-shell/src/readline.rscrates/aish-tools/src/bash.rs
eaf7835 to
a7a16be
Compare
* feat: complete Rust rewrite of aish Rewrite the entire aish (AI Shell) project from Python to Rust using a multi-crate workspace architecture: - aish-cli: CLI entry point with update, uninstall, models-auth subcommands - aish-config: TOML config loading with env overrides - aish-context: context window management and token budgets - aish-core: plan mode state machine, error types, shared types - aish-i18n: internationalization (en/zh/ja/de/es/fr) - aish-llm: multi-provider LLM client with OAuth, streaming, tool calls, Langfuse tracing, token usage tracking, and diagnose agent - aish-memory: memory manager with auto-retain/recall and deduplication - aish-prompts: prompt template engine - aish-pty: persistent PTY sessions, command state machine, output offload - aish-scripts: script loading, hooks (precmd/postcmd/greeting) - aish-security: policy engine, bubblewrap sandbox, fallback rules - aish-session: SQLite-backed session storage - aish-shell: REPL loop, TUI dialogs, richrs markdown rendering, setup wizard, autosuggest, readline integration - aish-skills: skill hot-reload and validation - aish-tools: 13 built-in tools (bash, fs, grep, glob, plan, etc.) * fix(shell): route history command to PTY for full cross-session history Before the Rust rewrite, `history` was passed to bash via PTY and showed the full persistent history. After the rewrite it became a Rust builtin that only displayed the in-memory Vec (empty on startup, current session only). Remove it from builtins so it falls through to PTY execution, restoring the original behavior. * fix(pty): reduce spurious blank lines and clean up Ctrl+C display - Disable readline (emacs/vi mode) in PTY bash to prevent extra newlines on Enter from the simple line reader interfering with output - Drain stale PTY output before sending a new command and skip leading newline preamble in the forwarding loop - Remove leading newline from Ctrl+C interruption hints for cleaner display * Rust rewrite (#119) * fix(shell): use POSIX sigaction for reliable Ctrl+C during AI streaming Replace tokio::signal::ctrl_c() with a direct POSIX SIGINT handler via nix::sys::signal::sigaction. The Tokio signal driver conflicted with rustyline's terminal handling, preventing cancellation during AI calls. The new approach: - Installs a SIGINT handler that atomically sets the CancellationToken - Uses tokio::select! with a 50ms poll to detect cancellation and drop the in-flight HTTP future - Resets the token before each AI call to avoid stale state - Stops the spinner animation on cancellation Also improves tab completion by using bash compgen -c for command discovery instead of scanning PATH directories manually. * feat(completion): delegate tab completion to PTY bash for full bash-completion support Replace hardcoded command lists and compgen -c with direct PTY queries that leverage bash's programmable completion system. This enables context-aware completions like `systemctl status`, `git add`, and `sudo systemctl` that were previously impossible. - Add __aish_query_completions() to bash_rc_wrapper.sh that uses _completion_loader, COMP_WORDS/COMPREPLY for full bash completion - Wrap PersistentPty in Arc<Mutex<>> to share between AishShell and ShellHelper for completion queries during readline - Rewrite ShellHelper::complete() to query PTY via execute_command, falling back to FilenameCompleter on timeout/error - Export shell_quote_escape from aish-pty for safe command escaping * fix(diagnose): propagate sub-session events and handle final_answer tool (#120) * feat: add system diagnose tool, Codex OAuth adapter, and complete CLI update/uninstall - Implement SystemDiagnoseTool with isolated ReAct sub-session and panic protection via catch_unwind in Tool trait's execute_async default impl - Add Codex provider adapter with OAuth browser/device-code login, token persistence, JWT parsing, SSE stream collection, and request/response conversion between Chat Completions and Codex Responses API formats - Register CodexProviderAdapter in ProviderRegistry alongside OpenAI-compat - Fix Qwen provider naming (alibaba → qwen) in provider detection - Complete update_manager rewrite: version comparison, platform detection, GitHub release check with mirror fallback, SHA256 verification, progress display, and archive extraction with install.sh execution - Complete uninstall_manager rewrite: multi-method detection (archive/cargo/ pip/dpkg/rpm), safe purge path validation, XDG directory cleanup - Fix update GitHub repo URL (xzx-idc → AI-Shell-Team) - Add sha2 dependency for install script verification * style: apply cargo fmt * style: apply cargo fmt * fix(shell): use POSIX sigaction for reliable Ctrl+C during AI streaming Replace tokio::signal::ctrl_c() with a direct POSIX SIGINT handler via nix::sys::signal::sigaction. The Tokio signal driver conflicted with rustyline's terminal handling, preventing cancellation during AI calls. The new approach: - Installs a SIGINT handler that atomically sets the CancellationToken - Uses tokio::select! with a 50ms poll to detect cancellation and drop the in-flight HTTP future - Resets the token before each AI call to avoid stale state - Stops the spinner animation on cancellation Also improves tab completion by using bash compgen -c for command discovery instead of scanning PATH directories manually. * feat(completion): delegate tab completion to PTY bash for full bash-completion support Replace hardcoded command lists and compgen -c with direct PTY queries that leverage bash's programmable completion system. This enables context-aware completions like `systemctl status`, `git add`, and `sudo systemctl` that were previously impossible. - Add __aish_query_completions() to bash_rc_wrapper.sh that uses _completion_loader, COMP_WORDS/COMPREPLY for full bash completion - Wrap PersistentPty in Arc<Mutex<>> to share between AishShell and ShellHelper for completion queries during readline - Rewrite ShellHelper::complete() to query PTY via execute_command, falling back to FilenameCompleter on timeout/error - Export shell_quote_escape from aish-pty for safe command escaping * fix(crates): fmt pass, clone event_callback, mark dirs in completion, fix leading newline - Run rustfmt across all crates for consistent formatting - Fix event_callback not being cloned in LlmSession::spawn_sub_agent - Mark directory entries with trailing / in tab completion output - Simplify leading newline stripping in PersistentPty to avoid eating real output * Rust rewrite (#121) * fix(shell): use POSIX sigaction for reliable Ctrl+C during AI streaming Replace tokio::signal::ctrl_c() with a direct POSIX SIGINT handler via nix::sys::signal::sigaction. The Tokio signal driver conflicted with rustyline's terminal handling, preventing cancellation during AI calls. The new approach: - Installs a SIGINT handler that atomically sets the CancellationToken - Uses tokio::select! with a 50ms poll to detect cancellation and drop the in-flight HTTP future - Resets the token before each AI call to avoid stale state - Stops the spinner animation on cancellation Also improves tab completion by using bash compgen -c for command discovery instead of scanning PATH directories manually. * feat(completion): delegate tab completion to PTY bash for full bash-completion support Replace hardcoded command lists and compgen -c with direct PTY queries that leverage bash's programmable completion system. This enables context-aware completions like `systemctl status`, `git add`, and `sudo systemctl` that were previously impossible. - Add __aish_query_completions() to bash_rc_wrapper.sh that uses _completion_loader, COMP_WORDS/COMPREPLY for full bash completion - Wrap PersistentPty in Arc<Mutex<>> to share between AishShell and ShellHelper for completion queries during readline - Rewrite ShellHelper::complete() to query PTY via execute_command, falling back to FilenameCompleter on timeout/error - Export shell_quote_escape from aish-pty for safe command escaping * fix(diagnose): propagate sub-session events and handle final_answer tool (#120) * feat: add system diagnose tool, Codex OAuth adapter, and complete CLI update/uninstall - Implement SystemDiagnoseTool with isolated ReAct sub-session and panic protection via catch_unwind in Tool trait's execute_async default impl - Add Codex provider adapter with OAuth browser/device-code login, token persistence, JWT parsing, SSE stream collection, and request/response conversion between Chat Completions and Codex Responses API formats - Register CodexProviderAdapter in ProviderRegistry alongside OpenAI-compat - Fix Qwen provider naming (alibaba → qwen) in provider detection - Complete update_manager rewrite: version comparison, platform detection, GitHub release check with mirror fallback, SHA256 verification, progress display, and archive extraction with install.sh execution - Complete uninstall_manager rewrite: multi-method detection (archive/cargo/ pip/dpkg/rpm), safe purge path validation, XDG directory cleanup - Fix update GitHub repo URL (xzx-idc → AI-Shell-Team) - Add sha2 dependency for install script verification * style: apply cargo fmt * style: apply cargo fmt * fix(shell): use POSIX sigaction for reliable Ctrl+C during AI streaming Replace tokio::signal::ctrl_c() with a direct POSIX SIGINT handler via nix::sys::signal::sigaction. The Tokio signal driver conflicted with rustyline's terminal handling, preventing cancellation during AI calls. The new approach: - Installs a SIGINT handler that atomically sets the CancellationToken - Uses tokio::select! with a 50ms poll to detect cancellation and drop the in-flight HTTP future - Resets the token before each AI call to avoid stale state - Stops the spinner animation on cancellation Also improves tab completion by using bash compgen -c for command discovery instead of scanning PATH directories manually. * feat(completion): delegate tab completion to PTY bash for full bash-completion support Replace hardcoded command lists and compgen -c with direct PTY queries that leverage bash's programmable completion system. This enables context-aware completions like `systemctl status`, `git add`, and `sudo systemctl` that were previously impossible. - Add __aish_query_completions() to bash_rc_wrapper.sh that uses _completion_loader, COMP_WORDS/COMPREPLY for full bash completion - Wrap PersistentPty in Arc<Mutex<>> to share between AishShell and ShellHelper for completion queries during readline - Rewrite ShellHelper::complete() to query PTY via execute_command, falling back to FilenameCompleter on timeout/error - Export shell_quote_escape from aish-pty for safe command escaping * fix(crates): fmt pass, clone event_callback, mark dirs in completion, fix leading newline - Run rustfmt across all crates for consistent formatting - Fix event_callback not being cloned in LlmSession::spawn_sub_agent - Mark directory entries with trailing / in tab completion output - Simplify leading newline stripping in PersistentPty to avoid eating real output * feat: remove old python * fix(shell): reset streamed flag on GenerationStart to prevent silent response drop (#122) When the AI sends interleaved text and tool calls in a streaming response, the preview text sets the streamed_content flag to true. After tool execution, the next API call returns a text-only response, but content deltas are not emitted (tool_calls_seen is false). The stale did_stream flag causes the final response to be silently dropped. Also introduces PtyExecutor::new_silent() for bash tool execution, which skips raw mode and stdin forwarding since AI tool calls don't need interactive terminal behavior. * feat: complete Rust rewrite with i18n, sandbox, and CI/CD migration (#125) * feat: complete Rust rewrite with i18n, sandbox, and CI/CD migration - Rewrite sandbox subsystem from Python to Rust with overlayfs support, systemd socket activation, and Python-compatible IPC protocol - Internationalize all CLI commands and shell/tool modules via aish-i18n - Migrate CI/CD workflows from Python/PyInstaller to Rust/cargo with musl static binary builds - Update packaging scripts (build_bundle, release_metadata, update_release_files) for Cargo.toml-based version management * fix(i18n): use translated exit message instead of hardcoded string * feat: enhance Langfuse integration, error correction, and tool system (#131) - Refactor langfuse module to use langfuse-ergonomic crate with env var support - Add structured JSON error correction response parsing with fallback - Defer SystemDiagnoseTool registration to wire skill callbacks - Improve PTY offload command handling - Add i18n translations for exit message across locales - Streamline bash tool and tool registry construction * fix(tools): support interactive stdin for sudo/su and eliminate pointless retries (#132) * fix(tools): support interactive stdin for sudo/su and eliminate pointless retries Two fixes to the bash tool: 1. Use interactive PtyExecutor (with stdin forwarding) for sudo/su commands so users can type passwords when AI tools execute them. Previously the silent executor ignored stdin, causing sudo to hang until timeout (exit code 143). 2. Always report ok=true when PTY execution succeeds regardless of exit code. Non-zero exits are normal command outcomes — the LLM sees <return_code> and decides what to do. Returning ok=false triggered a pointless retry of the same failing command. * fix(tools): fmt pass and extend su detection in compound commands * fix: close rust parity gaps for update streaming and timeouts * fix(pty): clear stale line-discipline input before each command (#134) When the interactive forwarding loop receives PromptReady and stdin keystrokes in the same select() iteration, the keystrokes are still forwarded to the PTY master and linger in the line-discipline canonical buffer. On the next command, these stale bytes get prepended to the actual command, corrupting it (e.g. "ip a" becomes "sip a"). Fix: prepend Ctrl-U (NAK, 0x15) before every command written to the PTY and clear write_buf at the PromptReady transition so stale stdin bytes never reach the PTY during the drain phase. Also includes readline CJK boundary fixes and python tool improvements (output truncation with safe UTF-8, sys.exit(1) in exception handler). * Revert "fix: close rust parity gaps for update streaming and timeouts" This reverts commit d8e431f. * fix: improve interactive command handling and tool cancellation (#139) - Add cancellation token bridging from AI handler to BashTool so Ctrl+C during AI tool execution actually terminates the PTY child process - Expand interactive command detection with explicit lists for TUI programs (vim, htop, less) and session commands (ssh, telnet, mosh) - Re-enable echo and output processing (OPOST/ONLCR) for session commands so remote PTY displays correctly - Extract basename in bash DEBUG trap so absolute paths like /usr/bin/ssh are correctly matched - Improve PTY initialization: wait_for_session_ready now reports whether PromptReady co-arrived, add drain_control_pipe_raw to prevent stale events from shifting exit codes - Enhance readline: gray hint highlighting, pre-populate autosuggest from saved history (oldest-first for correct recency), increase autosuggest capacity to 5000 * feat: enable Ctrl+Z job control for bash commands Port Python commit d193a72 to Rust: - Add set -m to bash rc wrapper for native job control - Route AI tool commands through PersistentPty for Ctrl+Z/bg/fg support - Rewrite execute_command() with select-based I/O loop and stdin forwarding - Ctrl+Z suspends foreground job, Ctrl+C cancels AI operation - Move CancelToken to types.rs for shared use between executor and persistent PTY * fix: stop thinking spinner on API error When the LLM API returns an error (e.g. 503), the chat_completion call propagated the error via ? without emitting OpEnd, leaving the thinking spinner running. Now both session.rs and app.rs properly stop the animation on error. * fix: avoid duplicate error message on API failure The LlmEventType::Error callback already prints the translated error via i18n. The Err(e) handler was printing it again. Now only non-LLM errors (which bypass the event system) are printed in the Err handler. * fix: suppress duplicate LLM error in handle_question handler The second Err(e) handler at the handle_question call site was also printing the error, duplicating the LlmEventType::Error callback output. Apply the same AishError::Llm guard here too. * fix: echo reasoning_content back to DeepSeek thinking mode API DeepSeek thinking mode requires reasoning_content from previous assistant messages to be included in subsequent API calls. The streaming handler emitted ReasoningDelta events for display but never stored them in the assistant message, causing 400 Bad Request errors during the tool-calling loop. - Add reasoning_content field to ChatMessage with skip_serializing_if - Accumulate reasoning deltas in streaming path and set on assistant msg - Extract reasoning_content from JSON responses via parse_response - Propagate reasoning_content in ReActAgent tool-calling and text paths - Account for reasoning_content in trim_messages token estimation * feat: add channel-based AI tools and multi-round tool chaining for SSH sessions (#152) * feat(pty): add OutputBuffer for session output capture * feat(pty): add SessionInterceptor state machine for SSH AI interception * feat(pty): register session_interceptor module and exports * feat(pty): integrate SessionInterceptor into select loop * feat(shell): provide AI callback for session command interception * fix: resolve clippy warnings in session interceptor modules * feat: match SSH AI display with local aish UI Move display responsibility from aish-pty to aish-shell callback. SSH session AI now uses the same blue dots spinner, green separator, 🤖 prefix, and markdown rendering as local aish. * fix: robust at_line_start tracking and preserve user input line - Add mark_prompt_ready() on select timeout to recover lost line-start state after several commands in SSH session - Replace \r\x1b[2K (erase input line) with \r\n (preserve input line) - Show bash_exec-style command display before injecting into remote shell * fix: support editing and cancellation during SSH AI input mode - Backspace/Delete erases last character from buffer and screen - Ctrl+C / Escape cancels AI input, returns to passthrough - Ctrl+U clears the input line content (keeps ; prefix) * feat: add channel-based AI tools and multi-round tool chaining for SSH sessions Introduce ChannelBashTool and ChannelAskUserTool that communicate via channels instead of direct terminal control, enabling AI tool use in SSH/telnet sessions. Implement followup callback mechanism for multi-round LLM interactions where tool output feeds back into the model for further analysis. * fix: address CodeRabbit review feedback - Add zero-capacity guard in OutputBuffer::new() - Honor timeout parameter in ChannelBashTool instead of hardcoded 120s - Remove unimplemented placeholder/required from ChannelAskUserTool schema - Drain trailing stdin bytes after confirmation read to prevent leaking - Replace hardcoded Chinese strings with i18n in ask_user and min-length - Actually execute BashExec commands in handle_ask_user instead of faking - Preserve branch-selected system prompt in session AI callback - Require N consecutive idle polls before treating shell as idle * fix: support multi-round bash tool calls and handle cancellation in SSH sessions - Preserve event_receiver via Arc<Mutex<Option<Receiver>>> to keep the LLM channel alive across multiple tool calls (make_chain_followup) - Call followup with cancellation message when user rejects a command, preventing "Channel closed" errors in both pending_response handlers - Suppress remote shell echo of injected commands * feat: integrate rust sandbox runtime * chore: package rust sandbox daemon * fix: reduce prompts for no-change sandbox probe failures * fix: address first sandbox review batch * chore: remove dead security service module * fix: harden sandbox daemon and worker cleanup * fix: resolve rustfmt CI drift * fix: restore rust workspace ci on stable 1.95 * chore: finalize rust release prep * chore: prepare 0.3.0-beta.1 release * fix: relax rhel ci musl dependency * fix: harden ci rust toolchain detection * fix: tolerate ci runners without rustup * feat: add beta cnd release channel flow * chore: prepare 0.3.0-beta.2 release (#162) * chore: prepare 0.3.0-beta.2 release * chore: refresh 0.3.0-beta.2 lockfile * fix: restore release preparation on rocky ci * fix: set musl target compiler env in ci * fix: preserve musl wrapper runtime args * fix: run musl release jobs on host runners * feat(ssh): nested SSH detection, host dossier system, and host_note AI tool (#157) * feat(ssh): nested SSH detection, host dossier system, and host_note AI tool Add nested SSH session detection with prompt pattern matching, Ctrl+C hard abort support, and interrupt grace handling. Introduce host dossier system (probe/profile/store) and host_note AI tool for persisting per-host context across sessions. * style: apply rustfmt formatting * fix: propagate save_profile errors in host_note tool closures The store and forget closures were discarding save_profile errors with `let _ =`, reporting success even when the write failed. * fix: resolve clippy warnings for CI - Replace useless format!() with .to_string() in probe_command - Use is_some_and() instead of map_or(false, ...) - Fix u32::MAX comparison in BASH_EXEC_IDLE_THRESHOLD check - Collapse nested if into single condition in extract_remote_host - Remove unused extract_command_from_isearch_line function - Allow dead_code for security_panel utilities (not yet wired up) * fix: collapse nested if into match guards for clippy 1.95 * fix: remove orphaned test for deleted extract_command_from_isearch_line * fix: install bundle systemd units under /etc * chore: prepare 0.3.0-beta.3 release * fix: align systemd unitdir paths * fix: remove invalid release smoke check * fix: restore multi-arch release flow * fix: use cdn for rust self-update * feat(ssh): channel-based AI tools, NL detection, and remote bash offload (#185) * feat(ssh): channel-based AI tools, NL detection, and local bash offload - Add channel-based AI tool system for SSH sessions with multi-round tool chaining (bash_exec, ask_user, host_note) - Add local offload for large bash_exec output, writing to /tmp/aish-offload/ instead of buffering in memory - Add NL detection for local aish shell; remove NL detection from SSH sessions to avoid false triggers in vim/less/top - Support bracketed paste mode in SessionInterceptor - Support followup offload with proper temp file cleanup on cancel * fix(ssh): offload UTF-8, skill/read_file tools, and cancellation chain - Fix PtyOutputOffload::finalize() losing internal buffer data (stdout_buf was never included in clean file output) - Ensure clean files are valid UTF-8 via from_utf8_lossy for PTY output - Prefer .clean path over .raw for offload (double defense in both persistent.rs and channel_bash.rs) - Propagate clean_error instead of silently discarding it - Register ReadFileTool in SSH sessions for reading local offload files - Register SkillTool in SSH sessions with skills snapshot and description - Add read_file tool execution display in SSH terminal (ToolExecution events) - Update SSH system prompt with read_file, skill usage, and offload rules - Cancel LLM session token on user rejection to prevent "Channel closed" retry loop in fire-and-forget followup threads * style: apply cargo fmt * fix(ssh): restrict read_file to offload paths and fix clean_path on write failure - SshReadFileTool: only allow reading files under aish-offload directory to prevent remote LLM from accessing arbitrary local files - offload.rs: return clean_path=None when clean file write fails instead of returning a non-existent path * style: suppress dead_code warnings for NlVerdict helpers * fix(ssh): harden SshReadFileTool and improve NL hyphen detection - SshReadFileTool: use starts_with(offload_root) for exact boundary check and pass canonical path to inner tool to avoid TOCTOU - NL detect: remove '-' from SHELL_SYNTAX_CHARS and instead check for leading-dash flag pattern, so hyphenated words like "well-known" are not penalized * fix: restore interactive sudo input and suppress duplicate bash output (#186) * fix: restore interactive sudo input handling * fix: harden interactive bash output handling * fix: format trailing metadata regex * feat: add context auto-compaction flow (#187) * fix: support array content in llm responses * feat: add context auto-compaction flow * fix: format context compaction ui handlers * fix: address context compaction clippy warnings * feat: rewrite prompt templates, add prompt caching and tool-loop trimming (#188) * feat: rewrite prompt templates, add prompt caching and tool-loop trimming - Overhaul oracle/cmd_error/error_detect prompts for better AI interaction - Add render_static_core/render_env_block for cache-friendly prompt splitting - Add inject_knowledge_stable for idempotent knowledge injection - Add Anthropic prompt caching (CacheControl) on system messages - Improve Langfuse observability: session-level trace, per-iteration spans - Add trim_tool_loop_messages to prevent unbounded context growth - New guess_command prompt template * fix: address CodeRabbit review issues (#188) - Fix potential slice panic in trim_messages when recent_start < system_count - Fix double-closing </long-term-memory> tag in recall text truncation - Fix tool name mismatch: bash_exec/python_exec → bash in prompt templates - Run cargo fmt to fix CI formatting failures * fix: remove context compaction from error correction path and unify content parsing The auto-compaction flow (#187) introduced compact_context_before_send into the error correction path, which could clear shell output containing error details before the LLM analyzes them. This made error correction unreliable. Also fixes extract_message_text to preserve original string content instead of trimming, and unifies the tool-call path in session.rs to use the same parser. * fix: restore rust welcome banner parity (#193) * fix: restore rust welcome banner parity * fix: restore rust branch formatting * fix: avoid awaiting while holding langfuse lock * fix: align rust ci and local toolchain baseline * feat: add secret detection with three-option dialog and vault redaction (#196) Add regex-based secret scanner (API keys, JWTs, passwords) and an in-memory SecretVault that redacts detected secrets to semantic placeholders ($SECRET_*) before sending to the LLM, then restores them for command execution and re-redacts command output. - SecretScanner with RegexSet for efficient multi-pattern matching - SecretVault with redact/restore/redact_output round-trip - Three-option dialog: redact to env vars, send plaintext, or abort - Both TUI (inquire) and SSH (raw terminal I/O) dialog paths - BashTool integration: restore before execution, redact output before returning to AI, restore in preflight for accurate security checks * feat: add ESC key interrupt for AI streaming responses (#197) Detect single ESC keypress to interrupt AI streaming and return to shell prompt. Works in both local mode (via EscWatcher thread with raw mode) and SSH sessions (inline detection in the forwarding loop). - New EscWatcher component: switches stdin to raw mode, spawns a listener thread that detects standalone ESC (0x1b with 50ms follow-up timeout to distinguish from arrow/function keys) - Integrated at 3 local AI call sites (handle_question, handle_error_correction, special command fallback) - Added inline ESC detection at 2 SSH session AI callback paths (build_session_ai_callback and build_followup_closure) - Graceful degradation: falls back to Ctrl+C-only if raw mode fails * feat: resume session * feat: resume panel * feat: unify rust terminal panels * fix: resolve rust clippy warnings * fix: align rustfmt output * fix: restore secret placeholder separators * fix: address resume panel review findings * refactor: remove stale select panel paths * fix: collapse markdown output gap * fix: handle non-standard streaming tool call formats from providers (#200) Some providers (e.g. DeepSeek via ai.getdeepin.org) send streaming tool call deltas where the id field is a number instead of a string, and subsequent chunks have function.name as an empty string ("") instead of null, overwriting the correct name from the first chunk. - Accept both string and number types for tool call id in SSE deltas - Skip empty-string id/name during accumulation to prevent overwrite - Generate fallback id (tc_{index}) when provider omits id entirely Closes #189 --------- Co-authored-by: xuezhixin <xuezhixin@uniontech.com> Co-authored-by: jex <82987891+jexShain@users.noreply.github.com>
Background
AI-triggered interactive bash commands had two regressions on the Rust shell path:
sudopassword prompts could lose stdin ownership while the shell was still polling for AI eventsChanges
ToolExecutionEndso the shell UI can recognize interactive bash completionsValidation
cargo test -p aish-pty test_clean_pty_output --lib -- --nocapturecargo test -p aish-llm tool_execution_end_event_includes_tool_args --lib -- --nocapturecargo test -p aish-shell collapsing_tests:: --lib -- --nocapturecargo build -p aish-cliRisk
The main risk is divergence between interactive and non-interactive bash execution behavior, especially around PTY cleanup and tool preview rendering.
Summary by CodeRabbit
Bug Fixes
New Features