Retain prompt resume resize until route transition
This commit is contained in:
parent
46b52b4b43
commit
29bc02fd7b
2 changed files with 84 additions and 13 deletions
|
|
@ -188,12 +188,14 @@ struct PendingPrompt {
|
||||||
authority: VerifiedSession,
|
authority: VerifiedSession,
|
||||||
prompt: TerminalPrompt,
|
prompt: TerminalPrompt,
|
||||||
emitted: bool,
|
emitted: bool,
|
||||||
|
resumed_size: Option<TerminalResize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct RoutedPromptDecision {
|
pub struct RoutedPromptDecision {
|
||||||
pub signed_decision: Vec<u8>,
|
pub signed_decision: Vec<u8>,
|
||||||
pub decision: DeveloperDecision,
|
pub decision: DeveloperDecision,
|
||||||
|
pub resumed_size: Option<TerminalResize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SessionPromptTransport {
|
pub struct SessionPromptTransport {
|
||||||
|
|
@ -264,6 +266,7 @@ impl SessionPromptTransport {
|
||||||
authority,
|
authority,
|
||||||
prompt: prompt.clone(),
|
prompt: prompt.clone(),
|
||||||
emitted: false,
|
emitted: false,
|
||||||
|
resumed_size: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Ok(prompt)
|
Ok(prompt)
|
||||||
|
|
@ -288,7 +291,7 @@ impl SessionPromptTransport {
|
||||||
&self,
|
&self,
|
||||||
session_id: SessionId,
|
session_id: SessionId,
|
||||||
signed_decision: Vec<u8>,
|
signed_decision: Vec<u8>,
|
||||||
) -> Result<DeveloperDecision, TerminalError> {
|
) -> Result<RoutedPromptDecision, TerminalError> {
|
||||||
let mut prompts = self
|
let mut prompts = self
|
||||||
.pending
|
.pending
|
||||||
.lock()
|
.lock()
|
||||||
|
|
@ -304,19 +307,40 @@ impl SessionPromptTransport {
|
||||||
pending.prompt.request_sha256,
|
pending.prompt.request_sha256,
|
||||||
)
|
)
|
||||||
.map_err(|_| TerminalError::PromptValidation)?;
|
.map_err(|_| TerminalError::PromptValidation)?;
|
||||||
|
let resumed_size = pending.resumed_size;
|
||||||
prompts.remove(&session_id);
|
prompts.remove(&session_id);
|
||||||
drop(prompts);
|
Ok(RoutedPromptDecision {
|
||||||
|
signed_decision,
|
||||||
|
decision,
|
||||||
|
resumed_size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn retain_prompt_resize(&self, resize: TerminalResize) -> Result<bool, TerminalError> {
|
||||||
|
let mut prompts = self
|
||||||
|
.pending
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| TerminalError::PromptValidation)?;
|
||||||
|
let Some(pending) = prompts.get_mut(&resize.session_id) else {
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
|
if !pending.emitted {
|
||||||
|
return Err(TerminalError::InactiveRoute);
|
||||||
|
}
|
||||||
|
pending.resumed_size = Some(resize);
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn publish_decision(
|
||||||
|
&self,
|
||||||
|
session_id: SessionId,
|
||||||
|
decision: RoutedPromptDecision,
|
||||||
|
) -> Result<(), TerminalError> {
|
||||||
self.routed
|
self.routed
|
||||||
.lock()
|
.lock()
|
||||||
.map_err(|_| TerminalError::PromptValidation)?
|
.map_err(|_| TerminalError::PromptValidation)?
|
||||||
.insert(
|
.insert(session_id, decision);
|
||||||
session_id,
|
Ok(())
|
||||||
RoutedPromptDecision {
|
|
||||||
signed_decision,
|
|
||||||
decision: decision.clone(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Ok(decision)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restore_sandbox_route(&self, session_id: SessionId) -> Result<(), TerminalError> {
|
fn restore_sandbox_route(&self, session_id: SessionId) -> Result<(), TerminalError> {
|
||||||
|
|
@ -719,6 +743,19 @@ impl InteractiveControlSession for RunscInteractiveControlSession {
|
||||||
) -> Result<InteractiveSessionBatch, ControlError> {
|
) -> Result<InteractiveSessionBatch, ControlError> {
|
||||||
let mut engine = self.engine.lock().map_err(|_| ControlError::LockPoisoned)?;
|
let mut engine = self.engine.lock().map_err(|_| ControlError::LockPoisoned)?;
|
||||||
let mut messages = match event {
|
let mut messages = match event {
|
||||||
|
InteractiveSessionEvent::Terminal(SessionTerminalEvent::Resize(resize)) => {
|
||||||
|
if self
|
||||||
|
.prompt_transport
|
||||||
|
.retain_prompt_resize(resize)
|
||||||
|
.map_err(|error| terminal_control_error(&error))?
|
||||||
|
{
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
engine
|
||||||
|
.handle(self.generation_id, SessionTerminalEvent::Resize(resize))
|
||||||
|
.map_err(|error| terminal_control_error(&error))?
|
||||||
|
}
|
||||||
|
}
|
||||||
InteractiveSessionEvent::Terminal(event) => engine
|
InteractiveSessionEvent::Terminal(event) => engine
|
||||||
.handle(self.generation_id, event)
|
.handle(self.generation_id, event)
|
||||||
.map_err(|error| terminal_control_error(&error))?,
|
.map_err(|error| terminal_control_error(&error))?,
|
||||||
|
|
@ -733,14 +770,23 @@ impl InteractiveControlSession for RunscInteractiveControlSession {
|
||||||
.prompt_transport
|
.prompt_transport
|
||||||
.route_decision(session_id, signed_decision)
|
.route_decision(session_id, signed_decision)
|
||||||
.map_err(|error| terminal_control_error(&error))?;
|
.map_err(|error| terminal_control_error(&error))?;
|
||||||
if decision.decision == lazaret_core::records::DeveloperDecisionKind::Deny {
|
if decision.decision.decision == lazaret_core::records::DeveloperDecisionKind::Deny
|
||||||
|
{
|
||||||
self.prompt_transport
|
self.prompt_transport
|
||||||
.restore_sandbox_route(session_id)
|
.restore_sandbox_route(session_id)
|
||||||
.map_err(|error| terminal_control_error(&error))?;
|
.map_err(|error| terminal_control_error(&error))?;
|
||||||
engine
|
engine
|
||||||
.set_route(session_id, self.generation_id, TerminalRoute::Sandbox)
|
.set_route(session_id, self.generation_id, TerminalRoute::Sandbox)
|
||||||
.map_err(|error| terminal_control_error(&error))?;
|
.map_err(|error| terminal_control_error(&error))?;
|
||||||
|
if let Some(resize) = decision.resumed_size {
|
||||||
|
engine
|
||||||
|
.handle(self.generation_id, SessionTerminalEvent::Resize(resize))
|
||||||
|
.map_err(|error| terminal_control_error(&error))?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
self.prompt_transport
|
||||||
|
.publish_decision(session_id, decision)
|
||||||
|
.map_err(|error| terminal_control_error(&error))?;
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
InteractiveSessionEvent::PromptDecision { .. }
|
InteractiveSessionEvent::PromptDecision { .. }
|
||||||
|
|
|
||||||
|
|
@ -539,7 +539,7 @@ fn engine_for(fixture: &Fixture) -> (Arc<MockFactory>, SessionTerminalEngine) {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn production_prompt_adapter_routes_exact_authenticated_denial() {
|
fn production_prompt_adapter_retains_resume_resize_before_authenticated_denial() {
|
||||||
let generation = GenerationIdentity {
|
let generation = GenerationIdentity {
|
||||||
agent_profile: "trusted".into(),
|
agent_profile: "trusted".into(),
|
||||||
domain_id: DomainId::new(bytes(1)),
|
domain_id: DomainId::new(bytes(1)),
|
||||||
|
|
@ -607,7 +607,7 @@ fn production_prompt_adapter_routes_exact_authenticated_denial() {
|
||||||
let factory = Arc::new(MockFactory::default());
|
let factory = Arc::new(MockFactory::default());
|
||||||
let mut engine = SessionTerminalEngine::new(
|
let mut engine = SessionTerminalEngine::new(
|
||||||
Arc::clone(&fixture.store),
|
Arc::clone(&fixture.store),
|
||||||
factory,
|
factory.clone(),
|
||||||
prompt_transport.clone(),
|
prompt_transport.clone(),
|
||||||
);
|
);
|
||||||
engine.acquire(&acquisition(binding.session_id)).unwrap();
|
engine.acquire(&acquisition(binding.session_id)).unwrap();
|
||||||
|
|
@ -640,6 +640,18 @@ fn production_prompt_adapter_routes_exact_authenticated_denial() {
|
||||||
batch.messages,
|
batch.messages,
|
||||||
vec![InteractiveSessionMessage::Prompt(expected_prompt.clone())]
|
vec![InteractiveSessionMessage::Prompt(expected_prompt.clone())]
|
||||||
);
|
);
|
||||||
|
let resumed_size = TerminalResize {
|
||||||
|
session_id: binding.session_id,
|
||||||
|
rows: NonZeroU16::new(55).unwrap(),
|
||||||
|
columns: NonZeroU16::new(144).unwrap(),
|
||||||
|
pixel_width: 1_440,
|
||||||
|
pixel_height: 900,
|
||||||
|
};
|
||||||
|
control
|
||||||
|
.exchange(InteractiveSessionEvent::Terminal(
|
||||||
|
SessionTerminalEvent::Resize(resumed_size),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
let signed_decision = encode_signed(
|
let signed_decision = encode_signed(
|
||||||
&DeveloperDecision {
|
&DeveloperDecision {
|
||||||
protocol_version: ProtocolVersion::V1,
|
protocol_version: ProtocolVersion::V1,
|
||||||
|
|
@ -664,6 +676,19 @@ fn production_prompt_adapter_routes_exact_authenticated_denial() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(routed.signed_decision, signed_decision);
|
assert_eq!(routed.signed_decision, signed_decision);
|
||||||
assert_eq!(routed.decision.decision, DeveloperDecisionKind::Deny);
|
assert_eq!(routed.decision.decision, DeveloperDecisionKind::Deny);
|
||||||
|
assert_eq!(routed.resumed_size, Some(resumed_size));
|
||||||
|
assert_eq!(
|
||||||
|
factory.log.lock().unwrap().as_slice(),
|
||||||
|
["resize:55x144", "signal:WINCH"]
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!factory
|
||||||
|
.log
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.any(|event| event == "signal:TSTP")
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fixture
|
fixture
|
||||||
.store
|
.store
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue