Add request_user_input auto-resolution window contract (#27256)

## Why

`request_user_input` is moving beyond its original plan-mode-only
workflow, and future default/goal-mode usage needs a way for the model
to ask helpful but non-blocking questions without forcing the turn to
wait forever. This PR adds an explicit `autoResolutionMs` contract so a
later client/runtime change can auto-resolve unanswered prompts after a
bounded window while leaving truly blocking questions unchanged.

This is contract plumbing only; it does not implement the client-side
timer or auto-selection behavior, and the model-facing description
treats the field as reserved unless the current runtime explicitly
supports auto-resolution.

## What Changed

- Added optional `autoResolutionMs` to the model-facing
`request_user_input` args and core `RequestUserInputEvent`.
- Added model-facing schema text for `autoResolutionMs` while marking it
reserved for runtimes that explicitly support auto-resolution.
- Bounds `autoResolutionMs` to `60_000..=240_000` ms during argument
normalization by clamping out-of-range model-provided values.
- Propagated the field through app-server v2
`ToolRequestUserInputParams`, app-server request forwarding, generated
TypeScript, and JSON schema fixtures.
- Updated app-server, core, protocol, and TUI call sites/tests so
omitted values preserve existing `None`/`null` behavior and coverage
verifies a `Some(60_000)` round trip.

## Verification

- `just test -p codex-app-server-protocol`
- `just test -p codex-core request_user_input`
- `just test -p codex-app-server request_user_input_round_trip`
- `just test -p codex-tui request_user_input`
- `just test -p codex-protocol`
This commit is contained in:
Shijie Rao
2026-06-11 22:30:41 -07:00
committed by GitHub
Unverified
parent 78bab04116
commit 216ce03031
25 changed files with 243 additions and 16 deletions
+2
View File
@@ -1888,6 +1888,7 @@ mod tests {
is_secret: false,
options: Some(vec![]),
}],
auto_resolution_ms: None,
})
.expect("params should serialize"),
),
@@ -1949,6 +1950,7 @@ mod tests {
is_secret: false,
options: Some(vec![]),
}],
auto_resolution_ms: None,
})
.expect("params should serialize"),
),
@@ -1690,6 +1690,15 @@
"ToolRequestUserInputParams": {
"description": "EXPERIMENTAL. Params sent with a request_user_input event.",
"properties": {
"autoResolutionMs": {
"default": null,
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"itemId": {
"type": "string"
},
@@ -57,6 +57,15 @@
},
"description": "EXPERIMENTAL. Params sent with a request_user_input event.",
"properties": {
"autoResolutionMs": {
"default": null,
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"itemId": {
"type": "string"
},
@@ -5626,6 +5626,15 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "EXPERIMENTAL. Params sent with a request_user_input event.",
"properties": {
"autoResolutionMs": {
"default": null,
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"itemId": {
"type": "string"
},
@@ -6,4 +6,4 @@ import type { ToolRequestUserInputQuestion } from "./ToolRequestUserInputQuestio
/**
* EXPERIMENTAL. Params sent with a request_user_input event.
*/
export type ToolRequestUserInputParams = { threadId: string, turnId: string, itemId: string, questions: Array<ToolRequestUserInputQuestion>, };
export type ToolRequestUserInputParams = { threadId: string, turnId: string, itemId: string, questions: Array<ToolRequestUserInputQuestion>, autoResolutionMs: number | null, };
@@ -1464,6 +1464,9 @@ pub struct ToolRequestUserInputParams {
pub turn_id: String,
pub item_id: String,
pub questions: Vec<ToolRequestUserInputQuestion>,
#[serde(default)]
#[ts(type = "number | null")]
pub auto_resolution_ms: Option<u64>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -687,6 +687,7 @@ pub(crate) async fn apply_bespoke_event_handling(
turn_id: request.turn_id,
item_id: request.call_id,
questions,
auto_resolution_ms: request.auto_resolution_ms,
};
let (pending_request_id, rx) = outgoing
.send_request(ServerRequestPayload::ToolRequestUserInput(params))
@@ -1259,6 +1259,7 @@ mod tests {
turn_id: "turn-1".to_string(),
item_id: "call-1".to_string(),
questions: vec![],
auto_resolution_ms: None,
},
))
.await;
@@ -1321,6 +1322,7 @@ mod tests {
turn_id: "turn-1".to_string(),
item_id: "call-1".to_string(),
questions: vec![],
auto_resolution_ms: None,
},
))
.await;
@@ -1140,6 +1140,7 @@ mod thread_processor_behavior_tests {
turn_id: "turn-1".to_string(),
item_id: "call-1".to_string(),
questions: vec![],
auto_resolution_ms: None,
},
))
.await;
@@ -2,7 +2,6 @@ use anyhow::Result;
use app_test_support::TestAppServer;
use app_test_support::create_final_assistant_message_sse_response;
use app_test_support::create_mock_responses_server_sequence;
use app_test_support::create_request_user_input_sse_response;
use app_test_support::to_response;
use codex_app_server_protocol::JSONRPCMessage;
use codex_app_server_protocol::JSONRPCResponse;
@@ -18,15 +17,46 @@ use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::Settings;
use codex_protocol::openai_models::ReasoningEffort;
use core_test_support::responses;
use serde_json::json;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
fn create_request_user_input_sse_response_with_auto_resolution(
call_id: &str,
auto_resolution_ms: u64,
) -> anyhow::Result<String> {
let tool_call_arguments = serde_json::to_string(&json!({
"questions": [{
"id": "confirm_path",
"header": "Confirm",
"question": "Proceed with the plan?",
"options": [{
"label": "Yes (Recommended)",
"description": "Continue the current plan."
}, {
"label": "No",
"description": "Stop and revisit the approach."
}]
}],
"autoResolutionMs": auto_resolution_ms
}))?;
Ok(responses::sse(vec![
responses::ev_response_created("resp-1"),
responses::ev_function_call(call_id, "request_user_input", &tool_call_arguments),
responses::ev_completed("resp-1"),
]))
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn request_user_input_round_trip() -> Result<()> {
let codex_home = tempfile::TempDir::new()?;
let responses = vec![
create_request_user_input_sse_response("call1")?,
create_request_user_input_sse_response_with_auto_resolution(
"call1", /*auto_resolution_ms*/ 60_000,
)?,
create_final_assistant_message_sse_response("done")?,
];
let server = create_mock_responses_server_sequence(responses).await;
@@ -89,6 +119,7 @@ async fn request_user_input_round_trip() -> Result<()> {
assert_eq!(params.turn_id, turn.id);
assert_eq!(params.item_id, "call1");
assert_eq!(params.questions.len(), 1);
assert_eq!(params.auto_resolution_ms, Some(60_000));
let resolved_request_id = request_id.clone();
mcp.send_response(
@@ -128,7 +159,6 @@ async fn request_user_input_round_trip() -> Result<()> {
Ok(())
}
fn create_config_toml(codex_home: &std::path::Path, server_uri: &str) -> std::io::Result<()> {
let config_toml = codex_home.join("config.toml");
std::fs::write(
+1
View File
@@ -655,6 +655,7 @@ async fn handle_request_user_input(
let args = RequestUserInputArgs {
questions: event.questions,
auto_resolution_ms: event.auto_resolution_ms,
};
let response_fut =
parent_session.request_user_input(parent_ctx, parent_ctx.sub_id.clone(), args);
@@ -433,6 +433,7 @@ async fn delegated_mcp_guardian_abort_returns_synthetic_decline_answer() {
is_secret: false,
options: None,
}],
auto_resolution_ms: None,
},
&cancel_token,
)
@@ -476,6 +477,7 @@ async fn delegated_mcp_user_reviewer_returns_none_without_metadata() {
is_secret: false,
options: None,
}],
auto_resolution_ms: None,
};
let response = maybe_auto_review_mcp_request_user_input(
&parent_session,
@@ -254,6 +254,7 @@ async fn should_install_mcp_dependencies(
};
let args = RequestUserInputArgs {
questions: vec![question],
auto_resolution_ms: None,
};
let sub_id = &turn_context.sub_id;
let call_id = format!("mcp-deps-{sub_id}");
+1
View File
@@ -1297,6 +1297,7 @@ async fn maybe_request_mcp_tool_approval(
let args = RequestUserInputArgs {
questions: vec![question],
auto_resolution_ms: None,
};
let response = sess
.request_user_input(turn_context.as_ref(), call_id.to_string(), args)
+1
View File
@@ -2391,6 +2391,7 @@ impl Session {
call_id,
turn_id: turn_context.sub_id.clone(),
questions: args.questions,
auto_resolution_ms: args.auto_resolution_ms,
});
turn_context
.turn_metadata_state
@@ -6,6 +6,8 @@ use codex_tools::ToolSpec;
use std::collections::BTreeMap;
pub const REQUEST_USER_INPUT_TOOL_NAME: &str = "request_user_input";
pub const MIN_AUTO_RESOLUTION_MS: u64 = 60_000;
pub const MAX_AUTO_RESOLUTION_MS: u64 = 240_000;
pub fn create_request_user_input_tool(description: String) -> ToolSpec {
let option_props = BTreeMap::from([
@@ -66,7 +68,14 @@ pub fn create_request_user_input_tool(description: String) -> ToolSpec {
Some("Questions to show the user. Prefer 1 and do not exceed 3".to_string()),
);
let properties = BTreeMap::from([("questions".to_string(), questions_schema)]);
let auto_resolution_ms_schema = JsonSchema::number(Some(format!(
"Optional auto-resolution window in milliseconds, from {MIN_AUTO_RESOLUTION_MS} to {MAX_AUTO_RESOLUTION_MS}. Include this only when the question is useful but non-blocking and continuing with best judgment is acceptable if the user does not answer; omit it when explicit user input is required before continuing. Use {MIN_AUTO_RESOLUTION_MS} for lightly helpful context and up to {MAX_AUTO_RESOLUTION_MS} when the answer would materially unblock better work."
)));
let properties = BTreeMap::from([
("questions".to_string(), questions_schema),
("autoResolutionMs".to_string(), auto_resolution_ms_schema),
]);
ToolSpec::Function(ResponsesApiTool {
name: REQUEST_USER_INPUT_TOOL_NAME.to_string(),
@@ -111,13 +120,26 @@ pub fn normalize_request_user_input_args(
question.is_other = true;
}
if let Some(auto_resolution_ms) = args.auto_resolution_ms {
let clamped_auto_resolution_ms =
auto_resolution_ms.clamp(MIN_AUTO_RESOLUTION_MS, MAX_AUTO_RESOLUTION_MS);
if clamped_auto_resolution_ms != auto_resolution_ms {
tracing::warn!(
auto_resolution_ms,
clamped_auto_resolution_ms,
"clamped request_user_input autoResolutionMs to supported range"
);
args.auto_resolution_ms = Some(clamped_auto_resolution_ms);
}
}
Ok(args)
}
pub fn request_user_input_tool_description(available_modes: &[ModeKind]) -> String {
let allowed_modes = format_allowed_modes(available_modes);
format!(
"Request user input for one to three short questions and wait for the response. This tool is only available in {allowed_modes}."
"Request user input for one to three short questions and wait for the response. Set autoResolutionMs, from {MIN_AUTO_RESOLUTION_MS} to {MAX_AUTO_RESOLUTION_MS} milliseconds, only when the question is useful but non-blocking and continuing with best judgment is acceptable if the user does not answer; omit it when explicit user input is required. This tool is only available in {allowed_modes}."
)
}
@@ -2,6 +2,8 @@ use super::*;
use codex_features::Feature;
use codex_features::Features;
use codex_protocol::config_types::ModeKind;
use codex_protocol::request_user_input::RequestUserInputQuestion;
use codex_protocol::request_user_input::RequestUserInputQuestionOption;
use codex_tools::JsonSchema;
use codex_tools::request_user_input_available_modes;
use pretty_assertions::assert_eq;
@@ -26,7 +28,15 @@ fn request_user_input_tool_includes_questions_schema() {
description: "Ask the user to choose.".to_string(),
strict: false,
defer_loading: None,
parameters: JsonSchema::object(BTreeMap::from([(
parameters: JsonSchema::object(BTreeMap::from([
(
"autoResolutionMs".to_string(),
JsonSchema::number(Some(
"Optional auto-resolution window in milliseconds, from 60000 to 240000. Include this only when the question is useful but non-blocking and continuing with best judgment is acceptable if the user does not answer; omit it when explicit user input is required before continuing. Use 60000 for lightly helpful context and up to 240000 when the answer would materially unblock better work."
.to_string(),
)),
),
(
"questions".to_string(),
JsonSchema::array(
JsonSchema::object(
@@ -96,12 +106,97 @@ fn request_user_input_tool_includes_questions_schema() {
"Questions to show the user. Prefer 1 and do not exceed 3".to_string(),
),
),
)]), Some(vec!["questions".to_string()]), Some(false.into())),
),
]), Some(vec!["questions".to_string()]), Some(false.into())),
output_schema: None,
})
);
}
#[test]
fn normalize_request_user_input_args_clamps_out_of_range_auto_resolution_ms() {
let args = RequestUserInputArgs {
questions: vec![RequestUserInputQuestion {
id: "confirm".to_string(),
header: "Confirm".to_string(),
question: "Proceed?".to_string(),
is_other: false,
is_secret: false,
options: Some(vec![RequestUserInputQuestionOption {
label: "Yes (Recommended)".to_string(),
description: "Continue.".to_string(),
}]),
}],
auto_resolution_ms: Some(MIN_AUTO_RESOLUTION_MS - 1),
};
assert_eq!(
normalize_request_user_input_args(args.clone()),
Ok(RequestUserInputArgs {
questions: vec![RequestUserInputQuestion {
is_other: true,
..args.questions[0].clone()
}],
auto_resolution_ms: Some(MIN_AUTO_RESOLUTION_MS),
})
);
assert_eq!(
normalize_request_user_input_args(RequestUserInputArgs {
auto_resolution_ms: Some(MAX_AUTO_RESOLUTION_MS + 1),
..args.clone()
}),
Ok(RequestUserInputArgs {
questions: vec![RequestUserInputQuestion {
is_other: true,
..args.questions[0].clone()
}],
auto_resolution_ms: Some(MAX_AUTO_RESOLUTION_MS),
})
);
}
#[test]
fn normalize_request_user_input_args_accepts_auto_resolution_boundaries() {
let args = RequestUserInputArgs {
questions: vec![RequestUserInputQuestion {
id: "confirm".to_string(),
header: "Confirm".to_string(),
question: "Proceed?".to_string(),
is_other: false,
is_secret: false,
options: Some(vec![RequestUserInputQuestionOption {
label: "Yes (Recommended)".to_string(),
description: "Continue.".to_string(),
}]),
}],
auto_resolution_ms: Some(MIN_AUTO_RESOLUTION_MS),
};
assert_eq!(
normalize_request_user_input_args(args.clone()),
Ok(RequestUserInputArgs {
questions: vec![RequestUserInputQuestion {
is_other: true,
..args.questions[0].clone()
}],
auto_resolution_ms: Some(MIN_AUTO_RESOLUTION_MS),
})
);
assert_eq!(
normalize_request_user_input_args(RequestUserInputArgs {
auto_resolution_ms: Some(MAX_AUTO_RESOLUTION_MS),
..args.clone()
}),
Ok(RequestUserInputArgs {
questions: vec![RequestUserInputQuestion {
is_other: true,
..args.questions[0].clone()
}],
auto_resolution_ms: Some(MAX_AUTO_RESOLUTION_MS),
})
);
}
#[test]
fn request_user_input_unavailable_messages_respect_default_mode_feature_flag() {
assert_eq!(
@@ -136,10 +231,10 @@ fn request_user_input_unavailable_messages_respect_default_mode_feature_flag() {
fn request_user_input_tool_description_mentions_available_modes() {
assert_eq!(
request_user_input_tool_description(&default_available_modes()),
"Request user input for one to three short questions and wait for the response. This tool is only available in Plan mode.".to_string()
"Request user input for one to three short questions and wait for the response. Set autoResolutionMs, from 60000 to 240000 milliseconds, only when the question is useful but non-blocking and continuing with best judgment is acceptable if the user does not answer; omit it when explicit user input is required. This tool is only available in Plan mode.".to_string()
);
assert_eq!(
request_user_input_tool_description(&default_mode_enabled_available_modes()),
"Request user input for one to three short questions and wait for the response. This tool is only available in Default or Plan mode.".to_string()
"Request user input for one to three short questions and wait for the response. Set autoResolutionMs, from 60000 to 240000 milliseconds, only when the question is useful but non-blocking and continuing with best judgment is acceptable if the user does not answer; omit it when explicit user input is required. This tool is only available in Default or Plan mode.".to_string()
);
}
@@ -76,10 +76,18 @@ fn call_output_content_and_success(
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn request_user_input_round_trip_resolves_pending() -> anyhow::Result<()> {
request_user_input_round_trip_for_mode(ModeKind::Plan).await
request_user_input_round_trip_for_mode(ModeKind::Plan, /*auto_resolution_ms*/ None).await
}
async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn request_user_input_round_trip_emits_auto_resolution_ms() -> anyhow::Result<()> {
request_user_input_round_trip_for_mode(ModeKind::Plan, Some(60_000)).await
}
async fn request_user_input_round_trip_for_mode(
mode: ModeKind,
auto_resolution_ms: Option<u64>,
) -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
@@ -104,7 +112,7 @@ async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Resul
.await?;
let call_id = "user-input-call";
let request_args = json!({
let mut request_args = json!({
"questions": [{
"id": "confirm_path",
"header": "Confirm",
@@ -117,8 +125,14 @@ async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Resul
"description": "Stop and revisit the approach."
}]
}]
})
.to_string();
});
if let Some(auto_resolution_ms) = auto_resolution_ms {
let Some(request_args) = request_args.as_object_mut() else {
panic!("request_user_input args should be a JSON object");
};
request_args.insert("autoResolutionMs".to_string(), json!(auto_resolution_ms));
}
let request_args = request_args.to_string();
let first_response = sse(vec![
ev_response_created("resp-1"),
@@ -171,6 +185,7 @@ async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Resul
.await;
assert_eq!(request.call_id, call_id);
assert_eq!(request.questions.len(), 1);
assert_eq!(request.auto_resolution_ms, auto_resolution_ms);
assert_eq!(request.questions[0].is_other, true);
assert!(
timeout(Duration::from_millis(200), async {
@@ -445,7 +460,7 @@ async fn request_user_input_rejected_in_default_mode_by_default() -> anyhow::Res
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn request_user_input_round_trip_in_default_mode_with_feature() -> anyhow::Result<()> {
request_user_input_round_trip_for_mode(ModeKind::Default).await
request_user_input_round_trip_for_mode(ModeKind::Default, /*auto_resolution_ms*/ None).await
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -31,6 +31,9 @@ pub struct RequestUserInputQuestion {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
pub struct RequestUserInputArgs {
pub questions: Vec<RequestUserInputQuestion>,
#[serde(rename = "autoResolutionMs", skip_serializing_if = "Option::is_none")]
#[schemars(rename = "autoResolutionMs")]
pub auto_resolution_ms: Option<u64>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
@@ -52,4 +55,7 @@ pub struct RequestUserInputEvent {
#[serde(default)]
pub turn_id: String,
pub questions: Vec<RequestUserInputQuestion>,
#[serde(rename = "autoResolutionMs", skip_serializing_if = "Option::is_none")]
#[schemars(rename = "autoResolutionMs")]
pub auto_resolution_ms: Option<u64>,
}
@@ -509,6 +509,7 @@ mod tests {
turn_id: "turn-2".to_string(),
item_id: "tool-1".to_string(),
questions: Vec::new(),
auto_resolution_ms: None,
},
}),
None
@@ -798,6 +799,7 @@ mod tests {
turn_id: "turn-1".to_string(),
item_id: "tool-1".to_string(),
questions: Vec::new(),
auto_resolution_ms: None,
},
});
@@ -820,6 +822,7 @@ mod tests {
turn_id: "turn-1".to_string(),
item_id: item_id.to_string(),
questions: Vec::new(),
auto_resolution_ms: None,
},
});
}
@@ -597,6 +597,7 @@ mod tests {
turn_id: turn_id.to_string(),
item_id: call_id.to_string(),
questions: Vec::new(),
auto_resolution_ms: None,
},
}
}
+1
View File
@@ -4586,6 +4586,7 @@ fn request_user_input_request(thread_id: ThreadId, turn_id: &str, item_id: &str)
turn_id: turn_id.to_string(),
item_id: item_id.to_string(),
questions: Vec::new(),
auto_resolution_ms: None,
},
}
}
@@ -1538,6 +1538,7 @@ mod tests {
item_id: "call-1".to_string(),
turn_id: turn_id.to_string(),
questions,
auto_resolution_ms: None,
}
}
@@ -1600,12 +1601,14 @@ mod tests {
item_id: "call-2".to_string(),
turn_id: "turn-2".to_string(),
questions: vec![question_with_options("q2", "Second")],
auto_resolution_ms: None,
});
overlay.try_consume_user_input_request(ToolRequestUserInputParams {
thread_id: "thread-1".to_string(),
item_id: "call-3".to_string(),
turn_id: "turn-3".to_string(),
questions: vec![question_with_options("q3", "Third")],
auto_resolution_ms: None,
});
overlay.handle_key_event(KeyEvent::from(KeyCode::Esc));
@@ -1623,6 +1626,7 @@ mod tests {
item_id: "call-1".to_string(),
turn_id: "turn-1".to_string(),
questions: vec![question_with_options("q1", "First")],
auto_resolution_ms: None,
},
tx,
/*has_input_focus*/ true,
@@ -1651,6 +1655,7 @@ mod tests {
item_id: "call-1".to_string(),
turn_id: "turn-1".to_string(),
questions: vec![question_with_options("q1", "First")],
auto_resolution_ms: None,
},
tx,
/*has_input_focus*/ true,
@@ -1662,6 +1667,7 @@ mod tests {
item_id: "call-2".to_string(),
turn_id: "turn-1".to_string(),
questions: vec![question_with_options("q2", "Second")],
auto_resolution_ms: None,
});
assert!(
@@ -1689,6 +1695,7 @@ mod tests {
item_id: "call-1".to_string(),
turn_id: "turn-1".to_string(),
questions: vec![question_with_options("q1", "First")],
auto_resolution_ms: None,
},
tx,
/*has_input_focus*/ true,
@@ -1700,12 +1707,14 @@ mod tests {
item_id: "call-2".to_string(),
turn_id: "turn-1".to_string(),
questions: vec![question_with_options("q2", "Second")],
auto_resolution_ms: None,
});
overlay.try_consume_user_input_request(ToolRequestUserInputParams {
thread_id: "thread-1".to_string(),
item_id: "call-3".to_string(),
turn_id: "turn-1".to_string(),
questions: vec![question_with_options("q3", "Third")],
auto_resolution_ms: None,
});
assert!(
@@ -152,6 +152,7 @@ mod tests {
item_id: call_id.to_string(),
turn_id: turn_id.to_string(),
questions: Vec::new(),
auto_resolution_ms: None,
}
}
@@ -597,6 +597,7 @@ async fn request_user_input_notification_overrides_pending_agent_turn_complete_n
description: "Update only Plan mode.".to_string(),
}]),
}],
auto_resolution_ms: None,
});
assert_matches!(
@@ -626,6 +627,7 @@ async fn handle_request_user_input_sets_pending_notification() {
description: "Update only Plan mode.".to_string(),
}]),
}],
auto_resolution_ms: None,
});
assert_matches!(