mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
78bab04116
## Stack 1. **[1 of 3] Support long raw TUI goal objectives** - this PR 2. [2 of 3] Support long pasted text in TUI goals - #27509 3. [3 of 3] Support images in TUI goals - #27510 ## Why `thread/goal/set` limits persisted objective text to 4000 characters. The TUI used to reject raw `/goal` objectives above that limit, even though the client can make them usable by writing the long text to a file and storing a short objective that points at that file. This also needs to work for remote app-server sessions: filesystem API calls must create files on the app-server host, and the stored path must be meaningful to the agent on that host. ## What Changed - Adds an app-server-host path helper so TUI code can build paths that are resolved on the app-server host rather than the TUI host. - Adds TUI app-server session helpers for `fs/createDirectory`, `fs/writeFile`, `fs/readFile`, and `fs/remove` that work for embedded and remote app-server sessions without changing the app-server protocol. - Materializes oversized raw `/goal` objectives into `$CODEX_HOME/attachments/<uuid>/goal-objective.md` through the app-server filesystem APIs, then stores a short, readable objective that directs the agent to that file. - Reads managed objective files back for `/goal edit`. Other goal UI renders the readable stored objective normally, without managed-file-specific presentation logic. - Recognizes managed references only when they name the expected generated file under the app server's reported `$CODEX_HOME`, and cleans up newly materialized files when goal replacement or setting does not complete. ## Verification - Added/updated TUI tests for raw oversized `/goal` submission, large inline-paste expansion, queued oversized goals, app-facing materialization before `thread/goal/set`, managed-path validation, editing, and cleanup. - Added/updated app-server-client remote coverage for initialized remote Codex home handling. ## Manual Testing - Ran the real TUI against a Unix-socket app server with different local and server `$CODEX_HOME` directories. Oversized goals wrote only under the server home, and persisted references used the server-canonical path rather than the TUI path. - Exercised 3,999-, 4,000-, and 4,001-character raw objectives. The first two stayed inline without new files; the 4,001-character objective became a managed objective file. - Submitted a larger 8,275-character objective, verified its full contents on the app-server host, and observed the goal continuation open the referenced server-side file. - Opened `/goal edit` for a managed objective and verified the full text was restored through remote `fs/readFile`. - Submitted an oversized replacement while a goal was active, verified no file was written before confirmation, then canceled and confirmed that the existing goal and attachment count were unchanged.
59 lines
1.6 KiB
Rust
59 lines
1.6 KiB
Rust
//! Paths resolved using the app-server host's platform rules.
|
|
|
|
use std::fmt;
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct AppServerPath(String);
|
|
|
|
impl AppServerPath {
|
|
pub fn from_app_server(path: impl Into<String>) -> Self {
|
|
Self(path.into())
|
|
}
|
|
|
|
pub fn from_absolute_str(raw: &str) -> Option<Self> {
|
|
(raw.starts_with('/') || is_windows_absolute_path(raw)).then(|| Self(raw.to_string()))
|
|
}
|
|
|
|
pub fn as_str(&self) -> &str {
|
|
&self.0
|
|
}
|
|
|
|
pub fn components(&self) -> Vec<&str> {
|
|
let separators = if is_windows_absolute_path(&self.0) {
|
|
&['/', '\\'][..]
|
|
} else {
|
|
&['/'][..]
|
|
};
|
|
self.0
|
|
.split(separators)
|
|
.filter(|part| !part.is_empty())
|
|
.collect()
|
|
}
|
|
|
|
pub fn join(&self, segment: impl AsRef<str>) -> Self {
|
|
let is_windows = is_windows_absolute_path(&self.0);
|
|
let (path, separator) = if is_windows {
|
|
(self.0.trim_end_matches(['/', '\\']), '\\')
|
|
} else {
|
|
(self.0.trim_end_matches('/'), '/')
|
|
};
|
|
Self(format!("{path}{separator}{}", segment.as_ref()))
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for AppServerPath {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
self.0.fmt(f)
|
|
}
|
|
}
|
|
|
|
fn is_windows_absolute_path(path: &str) -> bool {
|
|
let bytes = path.as_bytes();
|
|
(bytes.len() >= 3
|
|
&& bytes[0].is_ascii_alphabetic()
|
|
&& bytes[1] == b':'
|
|
&& matches!(bytes[2], b'\\' | b'/'))
|
|
|| path.starts_with("\\\\")
|
|
|| path.starts_with("//")
|
|
}
|