Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion crates/aish-i18n/locales/en-US.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ shell:
plan_review_hint: "Use /plan to start a new planning session."

# Thinking animation
thinking_time: "Thinking: {time:.1}s"
thinking_time: "Thinking: {time}s"

# Analyzing environment
analyzing_environment: "Analyzing environment..."
Expand Down Expand Up @@ -384,6 +384,21 @@ shell:

command_cancelled: "Command cancelled"

session:
tool_bash: "🔧 Using tool: bash ({command})"
confirm_execute: "⚠ Execute? [Y/n]"
ask_user:
custom_input_label: "Enter custom answer"
default_hint: "[default: {default}]"
help_with_cancel: "Esc to cancel, Enter to select"
help_no_cancel: "Enter to select"
min_length_error: "Answer too short (minimum {min} characters)"
tool_banner: "🔧 Using tool: ask_user ({preview})"
choice_preview: "choice_or_text: {prompt} [{count} options]"
text_preview: "text_input: {prompt}"
thinking: "Thinking"
thinking_elapsed: "Thinking {elapsed}s"

error_correction:
press_semicolon_hint: "Command execution failed. Type ; and press Enter to auto-analyze and fix, or enter the next command directly."
corrected_command_label: "Suggested fix:"
Expand Down
17 changes: 16 additions & 1 deletion crates/aish-i18n/locales/zh-CN.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ shell:
plan_review_hint: "使用 /plan 开始新的规划会话。"

# 思考动画
thinking_time: "思考: {time:.1}s"
thinking_time: "思考: {time}s"

# 分析环境
analyzing_environment: "正在分析环境..."
Expand Down Expand Up @@ -384,6 +384,21 @@ shell:

command_cancelled: "命令已取消"

session:
tool_bash: "🔧 使用工具: bash ({command})"
confirm_execute: "⚠ 是否执行? [Y/n]"
ask_user:
custom_input_label: "输入自定义答案"
default_hint: "[default: {default}]"
help_with_cancel: "Esc 取消,Enter 选择"
help_no_cancel: "Enter 选择"
min_length_error: "答案太短(最少 {min} 个字符)"
tool_banner: "🔧 使用工具: ask_user ({preview})"
choice_preview: "choice_or_text: {prompt} [{count} 个选项]"
text_preview: "text_input: {prompt}"
thinking: "思考中"
thinking_elapsed: "思考中 {elapsed}s"

error_correction:
press_semicolon_hint: "命令执行失败。输入 ; 后按 Enter 自动分析修复,或直接输入下一条命令。"
corrected_command_label: "修复建议:"
Expand Down
1 change: 1 addition & 0 deletions crates/aish-pty/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ edition.workspace = true

[dependencies]
aish-core.workspace = true
aish-i18n.workspace = true
nix.workspace = true
tokio.workspace = true
serde.workspace = true
Expand Down
7 changes: 7 additions & 0 deletions crates/aish-pty/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ pub mod command_state;
pub mod control;
pub mod executor;
pub mod offload;
pub mod output_buffer;
pub mod persistent;
pub mod session_interceptor;
pub mod state_capture;
pub mod types;

Expand All @@ -29,6 +31,11 @@ pub use offload::{
BashOffloadResult, BashOffloadSettings, BashOutputOffload, OffloadResult, OffloadState,
PtyOutputOffload,
};
pub use output_buffer::OutputBuffer;
pub use session_interceptor::{
AiCallback, AiEvent, AiQuery, AiResponse, AskUserAnswer, AskUserChannel, AskUserOption,
AskUserRequest, FollowupCallback, InterceptorState, SessionInterceptor, StdinAction,
};
pub use persistent::{is_interactive_command, shell_quote_escape, PersistentPty};
pub use state_capture::StateChanges;
pub use types::{CommandSource, CommandSubmission, PtyCommandResult, StreamName};
116 changes: 116 additions & 0 deletions crates/aish-pty/src/output_buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//! Circular buffer that keeps the most recent N bytes of PTY output.
//! Used to provide context for AI error correction during SSH sessions.

pub struct OutputBuffer {
data: Vec<u8>,
capacity: usize,
write_pos: usize,
len: usize,
}

impl OutputBuffer {
pub fn new(capacity: usize) -> Self {
assert!(capacity > 0, "OutputBuffer capacity must be > 0");
Self {
data: vec![0u8; capacity],
capacity,
write_pos: 0,
len: 0,
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/// Append bytes, overwriting oldest data when full.
pub fn append(&mut self, input: &[u8]) {
for &byte in input {
self.data[self.write_pos] = byte;
self.write_pos = (self.write_pos + 1) % self.capacity;
if self.len < self.capacity {
self.len += 1;
}
}
}

/// Return the most recent bytes up to `max_len`, in order.
pub fn recent(&self, max_len: usize) -> Vec<u8> {
let count = max_len.min(self.len);
let mut result = Vec::with_capacity(count);
let actual_start = if self.len < self.capacity {
self.len.saturating_sub(count)
} else {
(self.write_pos + self.capacity - count) % self.capacity
};
for i in 0..count {
result.push(self.data[(actual_start + i) % self.capacity]);
}
result
}

/// Clear the buffer.
pub fn clear(&mut self) {
self.write_pos = 0;
self.len = 0;
}

/// Current number of bytes stored.
pub fn len(&self) -> usize {
self.len
}

/// Whether the buffer is empty.
pub fn is_empty(&self) -> bool {
self.len == 0
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_basic_append_and_read() {
let mut buf = OutputBuffer::new(100);
buf.append(b"hello world");
assert_eq!(buf.recent(100), b"hello world");
}

#[test]
fn test_circular_overwrite() {
let mut buf = OutputBuffer::new(10);
buf.append(b"0123456789");
assert_eq!(buf.recent(10), b"0123456789");
buf.append(b"AB");
assert_eq!(buf.recent(10), b"23456789AB");
}

#[test]
fn test_recent_with_max_len() {
let mut buf = OutputBuffer::new(100);
buf.append(b"hello world");
assert_eq!(buf.recent(5), b"world");
}

#[test]
fn test_clear() {
let mut buf = OutputBuffer::new(100);
buf.append(b"data");
buf.clear();
assert!(buf.is_empty());
assert_eq!(buf.len(), 0);
}

#[test]
fn test_wrap_around_multiple_times() {
let mut buf = OutputBuffer::new(5);
buf.append(b"ABCDE");
buf.append(b"FGHIJ");
buf.append(b"KLMNO");
assert_eq!(buf.recent(5), b"KLMNO");
}

#[test]
fn test_empty_buffer() {
let buf = OutputBuffer::new(100);
assert!(buf.is_empty());
assert_eq!(buf.recent(100), b"");
}
}
Loading
Loading