1#[cfg(feature = "tui")]
10mod command_utils;
11#[cfg(feature = "tui")]
12mod formatting;
13#[cfg(feature = "tui")]
14mod manager;
15#[cfg(feature = "tui")]
16mod manager_utils;
17#[cfg(feature = "tui")]
18mod preview;
19#[cfg(feature = "tui")]
20mod screen_backend;
21#[cfg(feature = "tui")]
22mod scrollback;
23#[cfg(feature = "tui")]
24mod session;
25#[cfg(feature = "tui")]
26mod types;
27
28#[cfg(feature = "tui")]
31pub use command_utils::{
32 is_cargo_command, is_cargo_command_string, is_development_toolchain_command,
33};
34#[cfg(feature = "tui")]
35pub use manager::PtyManager;
36#[cfg(feature = "tui")]
37pub use portable_pty::PtySize;
38#[cfg(feature = "tui")]
39pub use preview::PtyPreviewRenderer;
40#[cfg(feature = "tui")]
41pub use types::{PtyCommandRequest, PtyCommandResult, PtyOutputCallback};
42
43#[cfg(not(feature = "tui"))]
46mod headless_pty {
47 use std::path::PathBuf;
48 use std::sync::Arc;
49 use std::time::Duration;
50
51 use anyhow::{Context, Result, anyhow};
52 use hashbrown::HashMap;
53
54 use crate::config::CommandsConfig;
55 use crate::config::PtyConfig;
56 use crate::tools::types::VTCodePtySession;
57 use crate::utils::path::ensure_path_within_workspace;
58 use crate::zsh_exec_bridge::ZshExecBridgeSession;
59
60 pub use portable_pty::PtySize;
61
62 pub type PtyOutputCallback = Arc<dyn Fn(&str) + Send + Sync>;
63
64 pub struct PtyCommandRequest {
65 pub command: Vec<String>,
66 pub working_dir: PathBuf,
67 pub timeout: Duration,
68 pub size: PtySize,
69 pub max_tokens: Option<usize>,
70 pub output_callback: Option<PtyOutputCallback>,
71 }
72
73 impl PtyCommandRequest {
74 pub fn with_streaming(
75 command: Vec<String>,
76 working_dir: PathBuf,
77 timeout: Duration,
78 callback: PtyOutputCallback,
79 ) -> Self {
80 Self {
81 command,
82 working_dir,
83 timeout,
84 size: PtySize {
85 rows: 24,
86 cols: 80,
87 pixel_width: 0,
88 pixel_height: 0,
89 },
90 max_tokens: None,
91 output_callback: Some(callback),
92 }
93 }
94 }
95
96 pub struct PtyCommandResult {
97 pub exit_code: i32,
98 pub output: String,
99 pub duration: Duration,
100 pub size: PtySize,
101 pub applied_max_tokens: Option<usize>,
102 }
103
104 #[derive(Clone, Default)]
106 pub struct PtyManager {
107 workspace_root: PathBuf,
108 _config: PtyConfig,
109 }
110
111 impl PtyManager {
112 pub fn new(workspace_root: PathBuf, config: PtyConfig) -> Self {
113 Self {
114 workspace_root,
115 _config: config,
116 }
117 }
118
119 pub async fn resolve_working_dir(&self, working_dir: Option<&str>) -> Result<PathBuf> {
120 let requested = match working_dir {
121 Some(dir) if !dir.trim().is_empty() => dir.trim(),
122 _ => return Ok(self.workspace_root.clone()),
123 };
124
125 let candidate = self.workspace_root.join(requested);
126 let normalized = ensure_path_within_workspace(&candidate, &self.workspace_root)
127 .map_err(|_| {
128 anyhow!(
129 "Working directory '{}' escapes the workspace root",
130 candidate.display()
131 )
132 })?;
133 let metadata = tokio::fs::metadata(&normalized).await.with_context(|| {
134 format!(
135 "Working directory '{}' does not exist",
136 normalized.display()
137 )
138 })?;
139 if !metadata.is_dir() {
140 return Err(anyhow!(
141 "Working directory '{}' is not a directory",
142 normalized.display()
143 ));
144 }
145 Ok(normalized)
146 }
147
148 pub fn apply_commands_config(&self, _commands_config: &CommandsConfig) {}
149
150 pub(crate) fn create_session_with_bridge(
151 &self,
152 _session_id: String,
153 _command: Vec<String>,
154 _working_dir: PathBuf,
155 _size: PtySize,
156 _extra_env: HashMap<String, String>,
157 _zsh_exec_bridge: Option<ZshExecBridgeSession>,
158 ) -> Result<VTCodePtySession> {
159 Err(anyhow!("PTY support disabled in headless build"))
160 }
161
162 pub fn snapshot_session(&self, _session_id: &str) -> Result<VTCodePtySession> {
163 Err(anyhow!("PTY support disabled in headless build"))
164 }
165
166 pub fn read_session_output(
167 &self,
168 _session_id: &str,
169 _drain: bool,
170 ) -> Result<Option<String>> {
171 Err(anyhow!("PTY support disabled in headless build"))
172 }
173
174 pub fn send_input_to_session(
175 &self,
176 _session_id: &str,
177 _data: &[u8],
178 _append_newline: bool,
179 ) -> Result<usize> {
180 Err(anyhow!("PTY support disabled in headless build"))
181 }
182
183 pub fn is_session_completed(&self, _session_id: &str) -> Result<Option<i32>> {
184 Err(anyhow!("PTY support disabled in headless build"))
185 }
186
187 pub fn terminate_session(&self, _session_id: &str) -> Result<()> {
188 Err(anyhow!("PTY support disabled in headless build"))
189 }
190
191 pub fn close_session(&self, _session_id: &str) -> Result<VTCodePtySession> {
192 Err(anyhow!("PTY support disabled in headless build"))
193 }
194
195 pub fn terminate_all_sessions(&self) {}
196 }
197
198 #[derive(Clone, Default)]
199 pub struct PtyPreviewRenderer;
200
201 pub fn is_cargo_command(_command: &PtyCommandRequest) -> bool {
202 false
203 }
204
205 pub fn is_cargo_command_string(_command: &str) -> bool {
206 false
207 }
208
209 pub fn is_development_toolchain_command(_command: &str) -> bool {
210 false
211 }
212}
213
214#[cfg(not(feature = "tui"))]
215pub use headless_pty::*;