-
Notifications
You must be signed in to change notification settings - Fork 396
feat(tools): execute_bash uses PTY for shell integrations #1340
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1340 +/- ##
==========================================
- Coverage 14.36% 13.92% -0.44%
==========================================
Files 2368 2350 -18
Lines 206342 203008 -3334
Branches 186706 183372 -3334
==========================================
- Hits 29633 28270 -1363
+ Misses 175251 173369 -1882
+ Partials 1458 1369 -89 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to_str_lossy ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where? it's already a UTF-8 type here though?
76424a7 to
6487fac
Compare
- Create a PTY and spawn the command inside that - Reads from user input for text and key events, allowing some use for interactive commands - Uses current program env vars, and users custom shell integrations (dot files e.g. .zshrc) - Works for both `execute_bash` tool and context hooks - Moved PTY creation code from `figterm` to `fig_util` instead. NOTES: - terminal is set to dumb mode as explained in the code. we should find a way around this. - we provde all command output which is expensive for some interactive commands that re-draw. Our next steps should involve writing more than a max output to a local file, then prompting Q to read from this file if it wants more context about what the tool did. - Not tested on windows (testable?) or other shells besides zsh (yet)
| // Regular character | ||
| c.to_string().into_bytes() | ||
| }, | ||
| KeyCode::Enter => vec![b'\r'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a non-exhaustive list. Is there an API can that do this for us?
|
|
||
| let stdout = stdout_lines.into_iter().collect::<String>(); | ||
| Ok(CommandResult { | ||
| exit_status: exit_status.exit_code(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
often the last few lines are important.
Consider:
First N lines
a sample of "important lines" -- . eg. with ERROR, WARNING, etc on them.
Last N lines
Which may then look like.
Started app blah
lots of startup stuff
[ skipped X rows of content ]
WARN: blah blah is wrong
[ skipped X rows of content ]
application is now existing with 49 warnings
exited
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also note, using the [ ] type of tagged lines, per how Claude likes to spit them out itself.
| default = [] | ||
|
|
||
| [dependencies] | ||
| anyhow.workspace = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really a fan of adding all of these dependencies to a foundational crate in our workspace like fig_util, e.g. nix doesn't make sense here considering it's not for Windows
| pub async fn invoke(&self, updates: impl Write) -> Result<InvokeOutput> { | ||
| let output = run_command(&self.command, MAX_TOOL_RESPONSE_SIZE / 3, Some(updates)).await?; | ||
| // Note: _updates is unused because `impl Write` cannot be shared across threads, so we write to | ||
| // stdout directly. A type refactor is needed to support this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can now use SharedWriter - https://github.com/aws/amazon-q-developer-cli/blob/main/crates/q_chat/src/shared_writer.rs
| let output = self.execute_pty_with_input(MAX_TOOL_RESPONSE_SIZE / 3, true).await?; | ||
| let result = serde_json::json!({ | ||
| "exit_status": output.exit_status.unwrap_or(0).to_string(), | ||
| "exit_status": output.exit_status.to_string(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing stderr?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would rather move this to a new crate for pty operations, e.g. fig_pty since it'll prevent adding a bunch of terminal deps to fig_util, and really a lot of it seems to just be copied from WezTerm
| const LINE_COUNT: usize = 1024; | ||
|
|
||
| // Open a new pseudoterminal | ||
| let pty_pair = open_pty(&get_terminal_size()).map_err(|e| eyre!("Failed to start PTY: {}", e))?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wondering why we can't just use the portable-pty crate here instead
| // This is all but required because otherwise the stdout from the PTY gets cluttered | ||
| // with escape characters and shell integrations (e.g. current directory, current user, hostname). | ||
| // We can clean the escape chars but the shell integrations are much harder. Is there a better way? | ||
| // What happens if we don't use this: Q can get confused on what the output is actually saying. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was seeing a lot of random junk when executing nvim for instance, and it wasn't being rendered properly.
Q can get confused on what the output is actually saying
It looks like we clean the output before sending to the model, why doesn't this work?
| // Create a command builder for the shell command | ||
| let shell = Shell::current_shell().map_or("bash", |s| s.as_str()); | ||
| let mut cmd_builder = CommandBuilder::new(shell); | ||
| cmd_builder.args(["-cli", &self.command]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what are these args? I believe -i is the only required option, we really just need an interactive shell.
This also seems to break with fish in my testing
| // Note: this reads one character a time basically. Which is fine for | ||
| // everything unless the user pastes a large amount of text. | ||
| // Could use an upgrade to avoid this (maybe read from stdin and events at the same time) | ||
| event = tokio::task::spawn_blocking(crossterm::event::read) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we use crossterm events instead of just reading straight from stdin? We're in raw mode, so reading from stdin and copying straight to the master fd should be sufficient right?
There's probably edge cases like with handling resizes but we could probably hold off on that for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where? it's already a UTF-8 type here though?
execute_bashtool and context hooksfigtermtofig_utilinstead.NOTES:
Issue #, if available:
#1030
Related: #1043
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.