Skip to content

fix: restore interactive sudo input and suppress duplicate bash output - #186

Merged
F16shen merged 3 commits into
AI-Shell-Team:rustfrom
F16shen:fix/sudo-bash-interactive-output
May 15, 2026
Merged

fix: restore interactive sudo input and suppress duplicate bash output#186
F16shen merged 3 commits into
AI-Shell-Team:rustfrom
F16shen:fix/sudo-bash-interactive-output

Conversation

@F16shen

@F16shen F16shen commented May 15, 2026

Copy link
Copy Markdown
Collaborator

Background

AI-triggered interactive bash commands had two regressions on the Rust shell path:

  • sudo password prompts could lose stdin ownership while the shell was still polling for AI events
  • interactive bash output could be rendered twice, once from the live PTY stream and again from the tool preview path

Changes

  • route interactive bash commands through the PTY-backed path when stdin takeover is required
  • propagate bash tool arguments through ToolExecutionEnd so the shell UI can recognize interactive bash completions
  • skip duplicate bash preview rendering when output was already shown live in the terminal
  • keep readline aligned with the PTY execution signature used by interactive commands
  • filter auth-interaction noise from captured PTY output before passing tool results back to the model

Validation

  • cargo test -p aish-pty test_clean_pty_output --lib -- --nocapture
  • cargo test -p aish-llm tool_execution_end_event_includes_tool_args --lib -- --nocapture
  • cargo test -p aish-shell collapsing_tests:: --lib -- --nocapture
  • cargo build -p aish-cli

Risk

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

    • Removed authentication and password-related noise from command output
    • Improved handling of interactive bash commands to prevent output interference
    • Tool output displays without multi-line return code blocks for cleaner formatting
  • New Features

    • Tool execution events now include command input arguments for better visibility
    • Commands requiring interactive input are automatically detected and handled appropriately

Review Change Stack

@github-actions

Copy link
Copy Markdown
Contributor

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

@github-actions

Copy link
Copy Markdown
Contributor

This pull request description looks incomplete. Please update the missing sections below before review.

Missing items:

  • Summary
  • User-visible Changes
  • Compatibility
  • Testing
  • Change Type
  • Scope

@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@F16shen has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 24 minutes and 33 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dada188f-0c36-4eb7-9c09-b3afe72a6bc5

📥 Commits

Reviewing files that changed from the base of the PR and between eaf7835 and 4cb9b38.

📒 Files selected for processing (5)
  • crates/aish-llm/src/session.rs
  • crates/aish-pty/src/persistent.rs
  • crates/aish-shell/src/app.rs
  • crates/aish-shell/src/readline.rs
  • crates/aish-tools/src/bash.rs
📝 Walkthrough

Walkthrough

This 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.

Changes

Interactive Bash Tool Execution

Layer / File(s) Summary
Interactive command detection and status tracking
crates/aish-tools/src/bash.rs
Exposes command_needs_interactive(command: &str) and interactive_input_active() backed by a global atomic flag. Both persistent PTY and one-shot executor paths compute interactive status from the command and manage the flag around execution.
PTY display control and auth-noise filtering
crates/aish-pty/src/persistent.rs
execute_command gains a display_output: bool parameter to optionally write fresh PTY bytes to stdout. clean_pty_output extends output filtering to strip password/auth-interaction noise for commands likely to prompt for authentication (sudo, su, ssh). Tests verify auth stripping and signature compliance.
Tool execution event enrichment
crates/aish-llm/src/session.rs
ToolExecutionEnd event payload now includes tool_args (the tool's input arguments). A test confirms bash tool command arguments are captured in the event.
Shell loop coordination and tool output handling
crates/aish-shell/src/app.rs, crates/aish-shell/src/readline.rs
Remote follow-up and main event loops pause briefly and retry when interactive_input_active() returns true, avoiding interference during interactive input. Tool preview output is suppressed for interactive bash commands. Completion queries and command syncing pass the display_output parameter. Multi-line return-code XML blocks are stripped from tool output before display.

Sequence Diagram

sequenceDiagram
  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
Loading

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • AI-Shell-Team/aish#132: Extends interactive input and retry behavior for bash execution, aligning with this PR's interactive stdin + loop-pausing fixes.
  • AI-Shell-Team/aish#139: Expands interactive-command detection and PTY handling in the same crates/aish-tools/src/bash.rs area.
  • AI-Shell-Team/aish#135: Addresses sudo/su interactive-auth behavior via PTY with controlling-terminal setup, complementing this PR's Rust-side interactive/auth-noise handling.

Suggested labels

size: M, tests, tools, experienced-contributor

Poem

🐰 A hopping tale of prompts that stay,
Auth noise stripped and loops that pause—
When bash needs input, out of the way,
Events enrich, without a fuss!
Interactive flows now clean and bright,

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the two main fixes: restoring interactive sudo input and suppressing duplicate bash output, which align with the core changes across multiple files in the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
crates/aish-pty/src/persistent.rs (1)

3571-3579: ⚡ Quick win

Narrow auth-prompt filtering trigger to command token, not substring.

command_may_prompt_for_auth matches with broad contains(...), so unrelated commands that merely include words like sudo/ssh in 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 win

Use 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

📥 Commits

Reviewing files that changed from the base of the PR and between 29ae67e and eaf7835.

📒 Files selected for processing (5)
  • crates/aish-llm/src/session.rs
  • crates/aish-pty/src/persistent.rs
  • crates/aish-shell/src/app.rs
  • crates/aish-shell/src/readline.rs
  • crates/aish-tools/src/bash.rs

Comment thread crates/aish-llm/src/session.rs
Comment thread crates/aish-pty/src/persistent.rs
Comment thread crates/aish-shell/src/app.rs Outdated
Comment thread crates/aish-shell/src/app.rs Outdated
Comment thread crates/aish-tools/src/bash.rs Outdated
Comment thread crates/aish-tools/src/bash.rs Outdated
@F16shen
F16shen force-pushed the fix/sudo-bash-interactive-output branch from eaf7835 to a7a16be Compare May 15, 2026 05:57
@github-actions github-actions Bot added size: M and removed size: S labels May 15, 2026
@F16shen
F16shen merged commit 352b631 into AI-Shell-Team:rust May 15, 2026
7 checks passed
@F16shen
F16shen deleted the fix/sudo-bash-interactive-output branch May 15, 2026 06:32
F16shen added a commit that referenced this pull request May 26, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant