mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
[1 of 3] Support long raw TUI goal objectives (#27508)
## 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.
This commit is contained in:
1 parent
d61dfeb23a
commit
78bab04116
17 files changed
+561
-249
No files matched your search
@@ -5,7 +5,6 @@
|
||||
//! dispatch step and records the staged entry once the command has been handled, so
|
||||
//! slash-command recall follows the same submitted-input rule as ordinary text.
|
||||
|
||||
use super::goal_validation::GoalObjectiveValidationSource;
|
||||
use super::*;
|
||||
use crate::app_event::ThreadGoalSetMode;
|
||||
use crate::bottom_pane::prompt_args::parse_slash_name;
|
||||
@@ -570,12 +569,6 @@ impl ChatWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
if cmd == SlashCommand::Goal
|
||||
&& !self.goal_objective_with_pending_pastes_is_allowed(&args, &text_elements)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let Some((prepared_args, prepared_elements)) =
|
||||
self.prepare_live_inline_args(args, text_elements)
|
||||
else {
|
||||
@@ -607,6 +600,12 @@ impl ChatWidget {
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_live_goal_submission(&mut self) {
|
||||
self.bottom_pane
|
||||
.set_composer_text(String::new(), Vec::new(), Vec::new());
|
||||
self.bottom_pane.drain_pending_submission_state();
|
||||
}
|
||||
|
||||
fn prepared_inline_user_message(
|
||||
&mut self,
|
||||
args: String,
|
||||
@@ -714,6 +713,9 @@ impl ChatWidget {
|
||||
}
|
||||
SlashCommand::Goal if !trimmed.is_empty() => {
|
||||
if !self.config.features.enabled(Feature::Goals) {
|
||||
if source == SlashCommandDispatchSource::Live {
|
||||
self.clear_live_goal_submission();
|
||||
}
|
||||
return;
|
||||
}
|
||||
enum GoalControlCommand {
|
||||
@@ -727,7 +729,7 @@ impl ChatWidget {
|
||||
thread_id: self.thread_id,
|
||||
});
|
||||
if source == SlashCommandDispatchSource::Live {
|
||||
self.bottom_pane.drain_pending_submission_state();
|
||||
self.clear_live_goal_submission();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -743,6 +745,9 @@ impl ChatWidget {
|
||||
"The session must start before you can change a goal.".to_string(),
|
||||
),
|
||||
);
|
||||
if source == SlashCommandDispatchSource::Live {
|
||||
self.clear_live_goal_submission();
|
||||
}
|
||||
return;
|
||||
};
|
||||
match command {
|
||||
@@ -757,29 +762,11 @@ impl ChatWidget {
|
||||
}
|
||||
self.append_message_history_entry(format!("/goal {trimmed}"));
|
||||
if source == SlashCommandDispatchSource::Live {
|
||||
self.bottom_pane.drain_pending_submission_state();
|
||||
self.clear_live_goal_submission();
|
||||
}
|
||||
return;
|
||||
}
|
||||
let objective = args.trim();
|
||||
if objective.is_empty() {
|
||||
self.add_error_message("Goal objective must not be empty.".to_string());
|
||||
self.add_info_message(
|
||||
GOAL_USAGE.to_string(),
|
||||
Some(GOAL_USAGE_HINT.to_string()),
|
||||
);
|
||||
if source == SlashCommandDispatchSource::Live {
|
||||
self.bottom_pane.drain_pending_submission_state();
|
||||
}
|
||||
return;
|
||||
}
|
||||
let validation_source = match source {
|
||||
SlashCommandDispatchSource::Live => GoalObjectiveValidationSource::Live,
|
||||
SlashCommandDispatchSource::Queued => GoalObjectiveValidationSource::Queued,
|
||||
};
|
||||
if !self.goal_objective_is_allowed(objective, validation_source) {
|
||||
return;
|
||||
}
|
||||
let Some(thread_id) = self.thread_id else {
|
||||
if source == SlashCommandDispatchSource::Live {
|
||||
self.queue_user_message_with_options(
|
||||
@@ -792,7 +779,7 @@ impl ChatWidget {
|
||||
},
|
||||
QueuedInputAction::ParseSlash,
|
||||
);
|
||||
self.bottom_pane.drain_pending_submission_state();
|
||||
self.clear_live_goal_submission();
|
||||
} else {
|
||||
self.add_info_message(
|
||||
GOAL_USAGE.to_string(),
|
||||
@@ -808,7 +795,7 @@ impl ChatWidget {
|
||||
});
|
||||
self.append_message_history_entry(format!("/goal {trimmed}"));
|
||||
if source == SlashCommandDispatchSource::Live {
|
||||
self.bottom_pane.drain_pending_submission_state();
|
||||
self.clear_live_goal_submission();
|
||||
}
|
||||
}
|
||||
SlashCommand::Side | SlashCommand::Btw if !trimmed.is_empty() => {
|
||||
@@ -945,11 +932,6 @@ impl ChatWidget {
|
||||
rest_offset + leading_trimmed,
|
||||
&text_elements,
|
||||
);
|
||||
if cmd == SlashCommand::Goal
|
||||
&& !self.goal_objective_is_allowed(trimmed_rest, GoalObjectiveValidationSource::Queued)
|
||||
{
|
||||
return QueueDrain::Continue;
|
||||
}
|
||||
self.dispatch_prepared_command_with_args(
|
||||
cmd,
|
||||
PreparedSlashCommandArgs {
|
||||
|
||||
Reference in new issue
Block a user