From 50136668218ca66a06f71528eb91c8d0ebdfe774 Mon Sep 17 00:00:00 2001 From: Dotta Date: Tue, 29 Sep 2026 16:23:50 -0500 Subject: [PATCH] fix(runner): bind Cursor mode across Rust session recovery Require explicit Cursor modes, preserve them through open and checkpoints, and reject mode drift. Bind the current Cursor v5 identity while rejecting historical v4. Co-Authored-By: Paperclip --- .../runner-core/src/acpx_provider_backend.rs | 59 +++++- .../src/acpx_provider_checkpoint.rs | 7 +- .../runner-core/src/acpx_provider_session.rs | 193 ++++++++++++++++-- .../tests/acpx_provider_checkpoint.rs | 33 +++ .../tests/acpx_provider_resolutions.rs | 1 + .../tests/acpx_provider_session.rs | 2 + .../tests/acpx_provider_suspend.rs | 1 + .../runner-core/tests/acpx_provider_turns.rs | 1 + 8 files changed, 274 insertions(+), 23 deletions(-) diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs index 2760b29446..f420a0d989 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs @@ -14,7 +14,7 @@ use sha2::{Digest, Sha256}; use crate::acpx_provider_session::{ AcpxPermissionMode, AcpxProviderRuntimePolicy, AcpxProviderSession, AcpxProviderSessionConfig, - AcpxProviderSessionIdentity, AcpxTurnControlCapabilities, + AcpxProviderSessionIdentity, AcpxTurnControlCapabilities, CursorMode, }; use crate::acpx_sidecar_transport::AcpxSidecarTransportConfig; #[cfg(test)] @@ -135,6 +135,8 @@ struct AcpxProviderDescriptor { #[serde(default)] instructions: String, permission_mode: AcpxPermissionMode, + #[serde(default, skip_serializing_if = "Option::is_none")] + cursor_mode: Option, permission_mode_pinned: bool, #[serde(default)] provider_policy: Option, @@ -213,7 +215,7 @@ impl AcpxProviderDescriptor { "2026.09.26-dd393fe", None, None, - "sha256:b1440d559ebc4eef5c7a582f1c81fc153270cfbafa1731a8ee76d83713bdf61b", + "sha256:aa8c0b2b84786982bcd06b7634bf95be6f2bc42bb8def48a8751dc50cf739b6b", ), "copilot" => ( self.model.as_str(), @@ -246,6 +248,7 @@ impl AcpxProviderDescriptor { || self.model.trim().is_empty() || self.model.len() > 240 || self.model.contains('\0') + || ((self.agent == "cursor") != self.cursor_mode.is_some()) || (matches!(self.agent.as_str(), "pi" | "cursor" | "copilot") && self.provider_policy.is_none()) || self.agent_server_package != expected.1 @@ -341,6 +344,7 @@ impl AcpxProviderDescriptor { normalized_session_id: self.normalized_session_id.clone(), working_directory: PathBuf::from(&self.cwd), permission_mode: self.permission_mode, + cursor_mode: self.cursor_mode, permission_mode_pinned: self.permission_mode_pinned, provider_policy: self.provider_policy.clone(), system_instructions: self.instructions.clone(), @@ -424,7 +428,7 @@ impl AcpxProviderDescriptor { } fn public_descriptor(&self, identity: Option<&AcpxProviderSessionIdentity>) -> Value { - json!({ + let mut descriptor = json!({ "provider": "acpx", "driver": "acpx_runtime", "providerVersion": self.provider_version, @@ -440,7 +444,11 @@ impl AcpxProviderDescriptor { "providerSessionId": identity.map(|value| value.agent_session_id.as_str()), "acpxRecordId": identity.map(|value| value.acpx_record_id.as_str()), "permissionMode": self.permission_mode, - }) + }); + if let Some(mode) = self.cursor_mode { + descriptor["cursorMode"] = json!(mode); + } + descriptor } } @@ -570,9 +578,11 @@ impl AcpxDurableState { identity .validate() .map_err(|error| DurableRunnerError::invalid(error.to_string()))?; - if identity.profile_digest != self.descriptor.command_digest { + if identity.profile_digest != self.descriptor.command_digest + || identity.cursor_mode != self.descriptor.cursor_mode + { return Err(DurableRunnerError::invalid( - "ACPX durable identity no longer matches its qualified profile digest", + "ACPX durable identity no longer matches its qualified profile or Cursor mode", )); } } @@ -2510,6 +2520,7 @@ mod tests { requested_model: descriptor.model.clone(), effective_model: descriptor.model.clone(), permission_mode: Some(descriptor.permission_mode), + cursor_mode: descriptor.cursor_mode, provider_lifetime_fence_candidates: [60_001, 60_002, 60_003], }; let operations = Vec::new(); @@ -2759,6 +2770,7 @@ mod tests { requested_model: "gpt-5.6-sol".to_owned(), effective_model: "gpt-5.6-sol".to_owned(), permission_mode: Some(AcpxPermissionMode::ApproveReads), + cursor_mode: None, provider_lifetime_fence_candidates: [60_001, 60_002, 60_003], }; @@ -2782,7 +2794,7 @@ mod tests { "cursor", "cursor-agent", "2026.09.26-dd393fe", - "sha256:b1440d559ebc4eef5c7a582f1c81fc153270cfbafa1731a8ee76d83713bdf61b", + "sha256:aa8c0b2b84786982bcd06b7634bf95be6f2bc42bb8def48a8751dc50cf739b6b", None, None, "explicit-model", @@ -2817,14 +2829,42 @@ mod tests { let missing: AcpxProviderDescriptor = serde_json::from_value(value.clone()).unwrap(); assert!(missing.validate(&context()).is_err()); value["providerPolicy"] = json!({"readOnly":true}); + if agent == "cursor" { + value["cursorMode"] = json!("agent"); + } let valid: AcpxProviderDescriptor = serde_json::from_value(value.clone()).unwrap(); valid.validate(&context()).unwrap(); + if agent == "cursor" { + assert_eq!(valid.public_descriptor(None)["cursorMode"], json!("agent")); + let mut previous_v4 = value.clone(); + previous_v4["commandDigest"] = json!( + "sha256:b1440d559ebc4eef5c7a582f1c81fc153270cfbafa1731a8ee76d83713bdf61b" + ); + let previous_v4: AcpxProviderDescriptor = + serde_json::from_value(previous_v4).unwrap(); + assert!(previous_v4.validate(&context()).is_err()); + let mut missing = value.clone(); + missing.as_object_mut().unwrap().remove("cursorMode"); + let missing: AcpxProviderDescriptor = serde_json::from_value(missing).unwrap(); + assert!(missing.validate(&context()).is_err()); + let mut unknown = value.clone(); + unknown["cursorMode"] = json!("autopilot"); + assert!(serde_json::from_value::(unknown).is_err()); + } else { + assert!(valid.public_descriptor(None).get("cursorMode").is_none()); + let mut wrong_agent = value.clone(); + wrong_agent["cursorMode"] = json!("plan"); + let wrong_agent: AcpxProviderDescriptor = + serde_json::from_value(wrong_agent).unwrap(); + assert!(wrong_agent.validate(&context()).is_err()); + } if agent == "copilot" { let mut previous_v4 = value.clone(); previous_v4["commandDigest"] = json!( "sha256:a119e436b4ad13ee70e1f58bedad4a0f20761b5fd982b752981f0cb1bc65f797" ); - let previous_v4: AcpxProviderDescriptor = serde_json::from_value(previous_v4).unwrap(); + let previous_v4: AcpxProviderDescriptor = + serde_json::from_value(previous_v4).unwrap(); assert!(previous_v4.validate(&context()).is_err()); let mut legacy = value.clone(); legacy["commandDigest"] = json!( @@ -2979,6 +3019,7 @@ mod tests { requested_model: original_descriptor.model.clone(), effective_model: original_descriptor.model.clone(), permission_mode: Some(original_descriptor.permission_mode), + cursor_mode: original_descriptor.cursor_mode, provider_lifetime_fence_candidates: [60_001, 60_002, 60_003], }; let operations = Vec::new(); @@ -3305,6 +3346,7 @@ mod tests { requested_model: descriptor.model.clone(), effective_model: descriptor.model.clone(), permission_mode: Some(descriptor.permission_mode), + cursor_mode: descriptor.cursor_mode, provider_lifetime_fence_candidates, }; let operations = Vec::new(); @@ -3476,6 +3518,7 @@ mod tests { requested_model: provider_descriptor.model.clone(), effective_model: provider_descriptor.model.clone(), permission_mode: Some(provider_descriptor.permission_mode), + cursor_mode: provider_descriptor.cursor_mode, provider_lifetime_fence_candidates, }); state.provider_exit_unconfirmed = true; diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_checkpoint.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_checkpoint.rs index 0e4c332ce3..ac9cdd83f7 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_checkpoint.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_checkpoint.rs @@ -10,7 +10,7 @@ use std::os::unix::fs::DirBuilderExt; use serde::{Deserialize, Serialize}; use crate::acpx_provider_session::{ - AcpxPermissionMode, AcpxProviderSessionConfig, AcpxProviderSessionIdentity, + AcpxPermissionMode, AcpxProviderSessionConfig, AcpxProviderSessionIdentity, CursorMode, }; use crate::durable::{ create_private_temporary_file, open_private_regular_file, verify_private_directory, @@ -48,6 +48,8 @@ struct PersistedAcpxProviderSessionIdentity { requested_model: String, effective_model: String, permission_mode: AcpxPermissionMode, + #[serde(default, skip_serializing_if = "Option::is_none")] + cursor_mode: Option, provider_lifetime_fence_candidates: [u16; 3], } @@ -70,6 +72,7 @@ impl PersistedAcpxProviderSessionIdentity { requested_model: identity.requested_model, effective_model: identity.effective_model, permission_mode, + cursor_mode: identity.cursor_mode, provider_lifetime_fence_candidates: identity.provider_lifetime_fence_candidates, }) } @@ -86,6 +89,7 @@ impl PersistedAcpxProviderSessionIdentity { requested_model: self.requested_model.clone(), effective_model: self.effective_model.clone(), permission_mode: Some(self.permission_mode), + cursor_mode: self.cursor_mode, provider_lifetime_fence_candidates: self.provider_lifetime_fence_candidates, } } @@ -106,6 +110,7 @@ impl AcpxSuspensionCheckpoint { || identity.requested_model != config.model || identity.effective_model != config.model || identity.permission_mode != Some(config.permission_mode) + || identity.cursor_mode != config.cursor_mode || config .expected_identity .as_ref() diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_session.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_session.rs index c92f044940..9958237a0d 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_session.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_session.rs @@ -34,6 +34,14 @@ pub enum AcpxPermissionMode { DenyAll, } +#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum CursorMode { + Agent, + Plan, + Ask, +} + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AcpxProviderSessionIdentity { @@ -48,6 +56,8 @@ pub struct AcpxProviderSessionIdentity { pub effective_model: String, #[serde(default)] pub permission_mode: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cursor_mode: Option, pub provider_lifetime_fence_candidates: [u16; 3], } @@ -68,6 +78,7 @@ pub struct AcpxProviderSessionConfig { pub normalized_session_id: String, pub working_directory: PathBuf, pub permission_mode: AcpxPermissionMode, + pub cursor_mode: Option, pub permission_mode_pinned: bool, pub provider_policy: Option, pub system_instructions: String, @@ -98,6 +109,11 @@ impl AcpxProviderSessionConfig { ))); } validate_text(&self.model, MAX_MODEL_CHARS, "ACPX model")?; + if (self.agent == "cursor") != self.cursor_mode.is_some() { + return Err(LocalRunnerError::invalid( + "ACPX Cursor mode must be explicit for Cursor and absent for other agents", + )); + } if matches!(self.agent.as_str(), "pi" | "cursor" | "copilot") && self.provider_policy.is_none() { @@ -169,6 +185,7 @@ impl AcpxProviderSessionConfig { || expected_identity.requested_model != self.model || expected_identity.effective_model != self.model || expected_identity.permission_mode != Some(self.permission_mode) + || expected_identity.cursor_mode != self.cursor_mode { return Err(LocalRunnerError::invalid( "ACPX expected identity conflicts with the requested session", @@ -1188,6 +1205,27 @@ impl Drop for AcpxProviderSession { } } +fn session_open_params(config: &AcpxProviderSessionConfig, sidecar_tools: &[Value]) -> Value { + let mut params = json!({ + "runtimeDirectory": config.runtime_directory, + "normalizedSessionId": config.normalized_session_id, + "workingDirectory": config.working_directory, + "agent": config.agent, + "model": config.model, + "permissionMode": config.permission_mode, + "permissionModePinned": config.permission_mode_pinned, + "providerPolicy": config.provider_policy, + "systemInstructions": config.system_instructions, + "runtimeContext": config.runtime_context, + "tools": &sidecar_tools, + "expectedIdentity": config.expected_identity, + }); + if let Some(mode) = config.cursor_mode { + params["cursorMode"] = json!(mode); + } + params +} + fn bootstrap( transport: &mut AcpxSidecarTransport, config: &AcpxProviderSessionConfig, @@ -1208,20 +1246,7 @@ fn bootstrap( let opened = transport.request( GeneratedAcpxSidecarCommand::SessionOpen, - json!({ - "runtimeDirectory": config.runtime_directory, - "normalizedSessionId": config.normalized_session_id, - "workingDirectory": config.working_directory, - "agent": config.agent, - "model": config.model, - "permissionMode": config.permission_mode, - "permissionModePinned": config.permission_mode_pinned, - "providerPolicy": config.provider_policy, - "systemInstructions": config.system_instructions, - "runtimeContext": config.runtime_context, - "tools": &sidecar_tools, - "expectedIdentity": config.expected_identity, - }), + session_open_params(config, &sidecar_tools), )?; let identity = verify_open_response(&opened, transport.process_id(), config)?; let turn_controls = verified_turn_controls(opened.get("turnControls"), &config.agent)?; @@ -1307,6 +1332,7 @@ fn verify_open_response( || identity.requested_model != config.model || identity.effective_model != config.model || identity.permission_mode != Some(config.permission_mode) + || identity.cursor_mode != config.cursor_mode || config .expected_identity .as_ref() @@ -1529,3 +1555,142 @@ mod permission_mode_tests { ); } } + +#[cfg(test)] +mod cursor_mode_tests { + use super::*; + + fn config() -> AcpxProviderSessionConfig { + let operations = Vec::new(); + AcpxProviderSessionConfig { + transport: AcpxSidecarTransportConfig { + command: std::env::current_exe().unwrap(), + args: Vec::new(), + verified_launch: None, + request_timeout: Duration::from_secs(1), + shutdown_grace: Duration::from_millis(100), + }, + agent: "cursor".to_owned(), + model: "explicit-model".to_owned(), + run_id: "run-1".to_owned(), + catalog_revision: 1, + runtime_directory: std::env::temp_dir(), + normalized_session_id: "session-1".to_owned(), + working_directory: std::env::temp_dir(), + permission_mode: AcpxPermissionMode::ApproveReads, + cursor_mode: Some(CursorMode::Plan), + permission_mode_pinned: true, + provider_policy: Some(AcpxProviderRuntimePolicy { read_only: false }), + system_instructions: String::new(), + runtime_context: Value::Null, + tool_set: AuthorizedToolSet { + schema: TOOL_SET_SCHEMA.to_owned(), + schema_version: 1, + catalog_digest: authorized_tool_catalog_digest(&operations).unwrap(), + operations, + }, + expected_identity: None, + } + } + fn identity() -> AcpxProviderSessionIdentity { + AcpxProviderSessionIdentity { + kind: "acpx".to_owned(), + normalized_session_id: "session-1".to_owned(), + acpx_record_id: "record-1".to_owned(), + backend_session_id: "backend-1".to_owned(), + agent_session_id: "agent-1".to_owned(), + profile_digest: format!("sha256:{}", "1".repeat(64)), + workspace_digest: format!("sha256:{}", "2".repeat(64)), + requested_model: "explicit-model".to_owned(), + effective_model: "explicit-model".to_owned(), + permission_mode: Some(AcpxPermissionMode::ApproveReads), + cursor_mode: Some(CursorMode::Plan), + provider_lifetime_fence_candidates: [60_001, 60_002, 60_003], + } + } + #[test] + fn cursor_mode_is_closed_and_not_defaulted_in_rust() { + for (name, mode) in [ + ("agent", CursorMode::Agent), + ("plan", CursorMode::Plan), + ("ask", CursorMode::Ask), + ] { + assert_eq!( + serde_json::from_value::(json!(name)).unwrap(), + mode + ); + assert_eq!(serde_json::to_value(mode).unwrap(), json!(name)); + } + for value in [json!("Agent"), json!("autopilot"), json!(null), json!(1)] { + assert!(serde_json::from_value::(value).is_err()); + } + let mut config = config(); + config.validate().unwrap(); + config.cursor_mode = None; + assert!(config.validate().is_err()); + config.agent = "copilot".to_owned(); + config.cursor_mode = Some(CursorMode::Agent); + assert!(config.validate().is_err()); + config.cursor_mode = None; + config.validate().unwrap(); + } + #[test] + fn open_wire_and_identity_bind_exact_cursor_mode() { + let mut config = config(); + for mode in [CursorMode::Agent, CursorMode::Plan, CursorMode::Ask] { + config.cursor_mode = Some(mode); + assert_eq!(session_open_params(&config, &[])["cursorMode"], json!(mode)); + let mut identity = identity(); + identity.cursor_mode = Some(mode); + let response = json!({"sidecarPid": 100, "status": {}, "identity": identity}); + assert_eq!( + verify_open_response(&response, 100, &config) + .unwrap() + .cursor_mode, + Some(mode) + ); + for wrong in [ + None, + Some(CursorMode::Agent), + Some(CursorMode::Plan), + Some(CursorMode::Ask), + ] { + if wrong == Some(mode) { + continue; + } + let mut changed = response.clone(); + changed["identity"]["cursorMode"] = json!(wrong); + assert!(verify_open_response(&changed, 100, &config).is_err()); + } + } + config.agent = "copilot".to_owned(); + config.cursor_mode = None; + assert!(session_open_params(&config, &[]) + .get("cursorMode") + .is_none()); + let mut other = identity(); + other.cursor_mode = None; + assert!(serde_json::to_value(&other) + .unwrap() + .get("cursorMode") + .is_none()); + let response = json!({"sidecarPid": 100, "status": {}, "identity": other}); + verify_open_response(&response, 100, &config).unwrap(); + } + #[test] + fn warm_reopen_and_suspension_reject_mode_changes() { + let mut config = config(); + let identity = identity(); + config.expected_identity = Some(identity.clone()); + config.validate().unwrap(); + config.cursor_mode = Some(CursorMode::Ask); + assert!(config.validate().is_err()); + let mut changed = identity.clone(); + changed.cursor_mode = Some(CursorMode::Agent); + assert!(verify_suspend_response( + &json!({"suspended": true, "identity": changed}), + &identity + ) + .is_err()); + } +} diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_checkpoint.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_checkpoint.rs index f00049620d..a1886bd720 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_checkpoint.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_checkpoint.rs @@ -50,6 +50,7 @@ fn config(directory: &std::path::Path) -> AcpxProviderSessionConfig { normalized_session_id: "session-1".to_owned(), working_directory: directory.to_owned(), permission_mode: AcpxPermissionMode::ApproveReads, + cursor_mode: None, permission_mode_pinned: true, provider_policy: None, system_instructions: "Complete the supplied task.".to_owned(), @@ -76,6 +77,7 @@ fn identity() -> AcpxProviderSessionIdentity { requested_model: "gpt-5.6-sol".to_owned(), effective_model: "gpt-5.6-sol".to_owned(), permission_mode: Some(AcpxPermissionMode::ApproveReads), + cursor_mode: None, provider_lifetime_fence_candidates: [60_001, 60_002, 60_003], } } @@ -257,3 +259,34 @@ fn rejects_an_existing_runner_state_directory_that_is_not_private() { ); fs::remove_dir_all(directory).unwrap(); } + +#[test] +fn cursor_mode_round_trips_checkpoint_and_rejects_changed_or_missing_recovery_mode() { + use paperclip_runner_core::acpx_provider_session::{AcpxProviderRuntimePolicy, CursorMode}; + let directory = temporary_directory("cursor-mode"); + let mut config = config(&directory); + config.agent = "cursor".to_owned(); + config.cursor_mode = Some(CursorMode::Plan); + config.provider_policy = Some(AcpxProviderRuntimePolicy { read_only: false }); + let mut identity = identity(); + identity.cursor_mode = Some(CursorMode::Plan); + let checkpoint = AcpxSuspensionCheckpoint::from_suspension(&config, identity.clone()).unwrap(); + let wire = serde_json::to_value(&checkpoint).unwrap(); + assert_eq!(wire["identity"]["cursorMode"], json!("plan")); + let recovered: AcpxSuspensionCheckpoint = serde_json::from_value(wire.clone()).unwrap(); + assert_eq!(recovered.admit_recovery(&config).unwrap(), identity); + for mode in [None, Some(CursorMode::Agent), Some(CursorMode::Ask)] { + let mut changed = config.clone(); + changed.cursor_mode = mode; + assert!(recovered.admit_recovery(&changed).is_err()); + let mut changed = wire.clone(); + changed["identity"]["cursorMode"] = json!(mode); + let changed: AcpxSuspensionCheckpoint = serde_json::from_value(changed).unwrap(); + assert!(changed.admit_recovery(&config).is_err()); + } + let mut other = config.clone(); + other.agent = "copilot".to_owned(); + other.cursor_mode = None; + assert!(recovered.admit_recovery(&other).is_err()); + fs::remove_dir_all(directory).unwrap(); +} diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_resolutions.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_resolutions.rs index 89e4cdee12..9028e29798 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_resolutions.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_resolutions.rs @@ -49,6 +49,7 @@ fn config(mode: &str) -> AcpxProviderSessionConfig { normalized_session_id: "session-1".to_owned(), working_directory: std::env::temp_dir(), permission_mode: AcpxPermissionMode::ApproveReads, + cursor_mode: None, permission_mode_pinned: true, provider_policy: None, system_instructions: "Complete the supplied task.".to_owned(), diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_session.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_session.rs index c9b9ce5933..6a7cfe27a1 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_session.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_session.rs @@ -44,6 +44,7 @@ fn config(mode: &str) -> AcpxProviderSessionConfig { normalized_session_id: "session-1".to_owned(), working_directory: std::env::temp_dir(), permission_mode: AcpxPermissionMode::ApproveReads, + cursor_mode: None, permission_mode_pinned: true, provider_policy: None, system_instructions: "Complete the supplied task.".to_owned(), @@ -65,6 +66,7 @@ fn expected_identity() -> AcpxProviderSessionIdentity { requested_model: "gpt-5.6-sol".to_owned(), effective_model: "gpt-5.6-sol".to_owned(), permission_mode: Some(AcpxPermissionMode::ApproveReads), + cursor_mode: None, provider_lifetime_fence_candidates: [60_001, 60_002, 60_003], } } diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_suspend.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_suspend.rs index eebd2e237f..809c08808c 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_suspend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_suspend.rs @@ -25,6 +25,7 @@ fn config(mode: &str) -> AcpxProviderSessionConfig { normalized_session_id: "session-1".to_owned(), working_directory: std::env::temp_dir(), permission_mode: AcpxPermissionMode::ApproveReads, + cursor_mode: None, permission_mode_pinned: true, provider_policy: None, system_instructions: "Complete the supplied task.".to_owned(), diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_turns.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_turns.rs index f2897a5415..a3b94bc898 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_turns.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_turns.rs @@ -54,6 +54,7 @@ fn config(mode: &str) -> AcpxProviderSessionConfig { normalized_session_id: "session-1".to_owned(), working_directory: std::env::temp_dir(), permission_mode: AcpxPermissionMode::ApproveReads, + cursor_mode: None, permission_mode_pinned: true, provider_policy: if mode.starts_with("controls") { Some(