Skip to main content

vtcode_core/tools/exec_session/
mod.rs

1use std::path::{Path, PathBuf};
2use std::sync::Arc;
3use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
4
5use anyhow::{Context, Result, anyhow, bail};
6use chrono::Utc;
7use hashbrown::HashMap;
8use parking_lot::Mutex as ParkingMutex;
9use sha2::{Digest, Sha256};
10use tokio::io::AsyncWriteExt;
11use tokio::sync::{Mutex, Notify, RwLock, broadcast, watch};
12use tokio::task::JoinHandle;
13#[cfg(windows)]
14use vtcode_bash_runner::GracefulTerminationResult;
15use vtcode_bash_runner::{
16    PipeSpawnOptions, PipeStdinMode, ProcessHandle, graceful_kill_process_group_default_async,
17    spawn_pipe_process_with_options,
18};
19
20use crate::sandboxing::build_sanitized_env;
21use crate::tools::ExecSessionId;
22use crate::tools::output_spooler::{SpoolIntegrity, SpoolLineCounter, encode_digest_hex};
23use crate::tools::pty::{PtyCloseMode, PtySize};
24use crate::tools::registry::{PtySessionGuard, PtySessionManager};
25use crate::tools::types::VTCodeExecSession;
26use crate::utils::path::{canonicalize_workspace, ensure_path_within_workspace};
27use crate::zsh_exec_bridge::ZshExecBridgeSession;
28
29const PIPE_OUTPUT_HEAD_BYTES: usize = 8 * 1024;
30const PIPE_OUTPUT_TAIL_BYTES: usize = 8 * 1024;
31const EXEC_SESSION_PREVIEW_HEAD_BYTES: usize = 8 * 1024;
32const EXEC_SESSION_PREVIEW_TAIL_BYTES: usize = 8 * 1024;
33const EXEC_SESSION_COMPLETION_COMMAND_MAX_BYTES: usize = 512;
34const EXEC_SESSION_COMPLETION_DRAIN_TIMEOUT: tokio::time::Duration = tokio::time::Duration::from_secs(1);
35const EXEC_SESSION_COMPLETION_DRAIN_POLL: tokio::time::Duration = tokio::time::Duration::from_millis(15);
36/// Upper bound for watcher abort + backend close. Must cover the sum of inner
37/// bounds (2s watch abort x2 + 2s child reap + 5s reader join) plus margin so
38/// the outer timeout is not spurious while `spawn_blocking` finishes residual work.
39const EXEC_SESSION_CLOSE_TIMEOUT: tokio::time::Duration = tokio::time::Duration::from_secs(12);
40/// Budget for aborting a lifecycle watcher before abandoning it.
41const EXEC_SESSION_WATCH_ABORT_TIMEOUT: tokio::time::Duration = tokio::time::Duration::from_secs(1);
42/// Budget for acquiring the output-read lock during close. An in-flight peek
43/// that never releases must not park close (and therefore the runloop).
44const EXEC_SESSION_OUTPUT_READ_LOCK_TIMEOUT: tokio::time::Duration = tokio::time::Duration::from_secs(1);
45
46/// A missing runtime handle does not establish whether its command failed.
47#[derive(Debug, thiserror::Error)]
48#[error(
49    "exec session '{session_id}' not found in this runtime. Call `write_stdin` with the exact `session_id` copied from the original run response (`next_wait_args.session_id` or `next_continue_args` `s`/`session_id`); never guess the id. If a background completion or turn diagnostics already recorded output for this id, reuse its output. Missing session state does not prove failure; rerun only if fresh execution is still needed."
50)]
51pub(crate) struct ExecSessionNotFound {
52    pub(crate) session_id: crate::types::CompactStr,
53}
54
55fn missing_exec_session_error(session_id: &str) -> anyhow::Error {
56    ExecSessionNotFound { session_id: session_id.into() }.into()
57}
58
59/// Maximum number of live background command sessions owned by one runtime.
60pub const MAX_BACKGROUND_PROCESSES: usize = 3;
61
62pub use manager::ExecSessionManager;
63pub use output_buffer::BackgroundShortcutResult;
64pub use record::{ExecSessionBackend, ExecSessionCompletionEvent, ExecSessionUiSnapshot};
65
66mod manager;
67mod output_buffer;
68mod pipe;
69mod record;
70
71pub(crate) use manager::*;
72pub(crate) use output_buffer::*;
73pub(crate) use pipe::*;
74pub(crate) use record::*;
75
76#[cfg(all(test, unix))]
77mod tests;