mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] Add interruptible sleep tool (#28429)
## Why Models sometimes need to pause briefly while waiting for external work, but using a shell command for that delay ties the wait to a process and does not naturally resume when new turn input arrives. ## What changed - add a built-in `sleep` tool behind the under-development `sleep_tool` feature - accept a bounded `duration_ms` argument, matching the millisecond convention used by unified exec - end the sleep early when either steered user input or mailbox input arrives - include elapsed wall-clock time in completed and interrupted outputs - emit a dedicated core `SleepItem` through `item/started` and `item/completed` - expose the sleep item as app-server v2 `ThreadItem::Sleep` and retain it in reconstructed thread history - regenerate the configuration schema for the new feature flag - regenerate app-server JSON and TypeScript schema fixtures ## Test plan - `just test -p codex-core sleep_tool_follows_feature_gate` - `just test -p codex-core any_new_input_interrupts_sleep` - `just test -p codex-app-server-protocol` - `just test -p codex-app-server sleep_emits_started_and_completed_items`
This commit is contained in:
committed by
GitHub
Unverified
parent
022f1221e8
commit
08901fc8e1
@@ -367,6 +367,12 @@ impl ThreadHistoryBuilder {
|
||||
ThreadItem::from(payload.item.clone()),
|
||||
);
|
||||
}
|
||||
codex_protocol::items::TurnItem::Sleep(_) => {
|
||||
self.upsert_item_in_turn_id(
|
||||
&payload.turn_id,
|
||||
ThreadItem::from(payload.item.clone()),
|
||||
);
|
||||
}
|
||||
codex_protocol::items::TurnItem::UserMessage(_)
|
||||
| codex_protocol::items::TurnItem::HookPrompt(_)
|
||||
| codex_protocol::items::TurnItem::AgentMessage(_)
|
||||
@@ -391,6 +397,12 @@ impl ThreadHistoryBuilder {
|
||||
ThreadItem::from(payload.item.clone()),
|
||||
);
|
||||
}
|
||||
codex_protocol::items::TurnItem::Sleep(_) => {
|
||||
self.upsert_item_in_turn_id(
|
||||
&payload.turn_id,
|
||||
ThreadItem::from(payload.item.clone()),
|
||||
);
|
||||
}
|
||||
codex_protocol::items::TurnItem::UserMessage(_)
|
||||
| codex_protocol::items::TurnItem::HookPrompt(_)
|
||||
| codex_protocol::items::TurnItem::AgentMessage(_)
|
||||
@@ -1234,6 +1246,7 @@ mod tests {
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynamicToolCallOutputContentItem;
|
||||
use codex_protocol::items::HookPromptFragment as CoreHookPromptFragment;
|
||||
use codex_protocol::items::SleepItem as CoreSleepItem;
|
||||
use codex_protocol::items::TurnItem as CoreTurnItem;
|
||||
use codex_protocol::items::UserMessageItem as CoreUserMessageItem;
|
||||
use codex_protocol::items::build_hook_prompt_message;
|
||||
@@ -1251,6 +1264,7 @@ mod tests {
|
||||
use codex_protocol::protocol::DynamicToolCallResponseEvent;
|
||||
use codex_protocol::protocol::ExecCommandEndEvent;
|
||||
use codex_protocol::protocol::ExecCommandSource;
|
||||
use codex_protocol::protocol::ItemCompletedEvent;
|
||||
use codex_protocol::protocol::ItemStartedEvent;
|
||||
use codex_protocol::protocol::McpInvocation;
|
||||
use codex_protocol::protocol::McpToolCallEndEvent;
|
||||
@@ -1420,7 +1434,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_non_plan_item_lifecycle_events() {
|
||||
fn ignores_user_message_item_lifecycle_events() {
|
||||
let turn_id = "turn-1";
|
||||
let thread_id = ThreadId::new();
|
||||
let events = vec![
|
||||
@@ -1478,6 +1492,53 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rebuilds_sleep_item_from_persisted_completion() {
|
||||
let turn_id = "turn-1";
|
||||
let thread_id = ThreadId::new();
|
||||
let sleep_item = CoreTurnItem::Sleep(CoreSleepItem {
|
||||
id: "sleep-1".to_string(),
|
||||
duration_ms: 1_000,
|
||||
});
|
||||
let events = vec![
|
||||
EventMsg::TurnStarted(TurnStartedEvent {
|
||||
turn_id: turn_id.to_string(),
|
||||
trace_id: None,
|
||||
started_at: None,
|
||||
model_context_window: None,
|
||||
collaboration_mode_kind: Default::default(),
|
||||
}),
|
||||
EventMsg::ItemCompleted(ItemCompletedEvent {
|
||||
thread_id,
|
||||
turn_id: turn_id.to_string(),
|
||||
item: sleep_item,
|
||||
completed_at_ms: 1_000,
|
||||
}),
|
||||
EventMsg::TurnComplete(TurnCompleteEvent {
|
||||
turn_id: turn_id.to_string(),
|
||||
last_agent_message: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
time_to_first_token_ms: None,
|
||||
}),
|
||||
];
|
||||
|
||||
let items = events
|
||||
.into_iter()
|
||||
.map(RolloutItem::EventMsg)
|
||||
.collect::<Vec<_>>();
|
||||
let turns = build_turns_from_rollout_items(&items);
|
||||
|
||||
assert_eq!(turns.len(), 1);
|
||||
assert_eq!(
|
||||
turns[0].items,
|
||||
vec![ThreadItem::Sleep {
|
||||
id: "sleep-1".to_string(),
|
||||
duration_ms: 1_000,
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_user_message_client_id_from_legacy_event() {
|
||||
let turn_id = "turn-1";
|
||||
|
||||
@@ -356,6 +356,13 @@ pub enum ThreadItem {
|
||||
ImageView { id: String, path: AbsolutePathBuf },
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Sleep {
|
||||
id: String,
|
||||
#[ts(type = "number")]
|
||||
duration_ms: u64,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
ImageGeneration {
|
||||
id: String,
|
||||
status: String,
|
||||
@@ -400,6 +407,7 @@ impl ThreadItem {
|
||||
| ThreadItem::SubAgentActivity { id, .. }
|
||||
| ThreadItem::WebSearch { id, .. }
|
||||
| ThreadItem::ImageView { id, .. }
|
||||
| ThreadItem::Sleep { id, .. }
|
||||
| ThreadItem::ImageGeneration { id, .. }
|
||||
| ThreadItem::EnteredReviewMode { id, .. }
|
||||
| ThreadItem::ExitedReviewMode { id, .. }
|
||||
@@ -837,6 +845,10 @@ impl From<CoreTurnItem> for ThreadItem {
|
||||
id: image.id,
|
||||
path: image.path,
|
||||
},
|
||||
CoreTurnItem::Sleep(sleep) => ThreadItem::Sleep {
|
||||
id: sleep.id,
|
||||
duration_ms: sleep.duration_ms,
|
||||
},
|
||||
CoreTurnItem::ImageGeneration(image) => ThreadItem::ImageGeneration {
|
||||
id: image.id,
|
||||
status: image.status,
|
||||
|
||||
Reference in New Issue
Block a user