fix(terminal): harden relay lifecycle and session cleanup

- bound relay input and detach saturated output transports
- drain PTY output after child exit
- make terminal close lifecycle race-safe
- prune expired CC sessions and audit closure reasons
- require matching agent hello/auth identities
- validate terminal IDs and PTY dimensions
This commit is contained in:
lda committed 2026-07-16 08:51:55 +07:00 Verified
1 parent 536b50220f
commit 3476892cd1
10 files changed
+408 -115

No files matched your search

+29 -1
View File
@@ -17,7 +17,7 @@ impl RequestId {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
#[serde(transparent)]
pub struct TerminalId(String);
@@ -41,6 +41,24 @@ impl fmt::Display for TerminalId {
}
}
impl TryFrom<String> for TerminalId {
type Error = String;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl<'de> Deserialize<'de> for TerminalId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let raw = String::deserialize(deserializer)?;
Self::try_from(raw).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AgentCapability {
@@ -297,6 +315,16 @@ mod tests {
assert!(err.contains("must not be empty"));
}
#[test]
fn terminal_id_deserialization_enforces_validation() {
let valid: TerminalId = serde_json::from_str(r#""term-1""#).expect("valid terminal id");
assert_eq!(valid.as_str(), "term-1");
let error = serde_json::from_str::<TerminalId>(r#"" ""#)
.expect_err("empty terminal id must fail");
assert!(error.to_string().contains("must not be empty"));
}
#[test]
fn client_result_with_devs_serializes() {
let msg = ClientMessage::Result {