aboutsummaryrefslogtreecommitdiff
path: root/src/client/shell/state.rs
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-02 02:45:20 +0300
committerGitHub <noreply@github.com>2026-09-02 02:45:20 +0300
commitcc88b3b8e5bb9f7d9f23ed6ae85a52fd7b5b9ed6 (patch)
tree4d37c760099070f1a1488b61c7b7271875aafef8 /src/client/shell/state.rs
parent18e69891dca486d669a584facd80644bb51f54a2 (diff)
feat: add stable client endpoint compatibility (#3509)
* feat: add stable client endpoint compatibility * fix: keep portable-pty patch checks composable * ci: run maintenance checks in pull requests * fix: reject endpoint errors from terminal clients * feat: keep compatible servers running across updates
Diffstat (limited to 'src/client/shell/state.rs')
-rw-r--r--src/client/shell/state.rs49
1 files changed, 48 insertions, 1 deletions
diff --git a/src/client/shell/state.rs b/src/client/shell/state.rs
index 81173c18..69e77dc5 100644
--- a/src/client/shell/state.rs
+++ b/src/client/shell/state.rs
@@ -636,10 +636,33 @@ pub(super) enum PendingEndpointKind {
pub(super) struct PendingEndpointRequest {
pub(super) boot_id: String,
+ pub(super) method_name: String,
pub(super) confirmation_workspace_id: Option<String>,
pub(super) kind: PendingEndpointKind,
}
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub(super) enum ClientEndpointNoticeKind {
+ Unsupported,
+ Rejected,
+ Timeout,
+ Unavailable,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+pub(super) struct ClientEndpointNoticeKey {
+ pub(super) boot_id: String,
+ pub(super) kind: ClientEndpointNoticeKind,
+ pub(super) code: String,
+}
+
+pub(super) struct ClientVisibleEndpointNotice {
+ pub(super) key: ClientEndpointNoticeKey,
+ pub(super) title: String,
+ pub(super) body: String,
+ pub(super) deadline: std::time::Instant,
+}
+
pub(crate) struct ClientShellEndpointError {
pub code: Option<String>,
pub message: String,
@@ -836,10 +859,16 @@ pub(crate) struct ClientShellState {
pub(super) popup_pending: bool,
pub(super) popup_pending_deadline: Option<std::time::Instant>,
pub(super) next_request_id: u64,
+ /// Methods advertised by this endpoint. `None` is used only by local tests
+ /// and legacy construction paths; negotiated endpoint connections always
+ /// install an explicit set.
+ pub(super) endpoint_methods: Option<HashSet<String>>,
pub(super) pending_requests: HashMap<String, PendingEndpointRequest>,
pub(super) pending_integration_installs: usize,
pub(super) pending_notifications: Vec<ClientPendingNotification>,
pub(super) visible_notification: Option<ClientVisibleNotification>,
+ pub(super) endpoint_notice_seen: HashSet<ClientEndpointNoticeKey>,
+ pub(super) visible_endpoint_notice: Option<ClientVisibleEndpointNotice>,
pub(super) outer_focused: Option<bool>,
pub(super) ascii_input_source_active: bool,
pub(super) pending_input_source_changes: Vec<bool>,
@@ -971,10 +1000,13 @@ impl ClientShellState {
popup_pending: false,
popup_pending_deadline: None,
next_request_id: 1,
+ endpoint_methods: None,
pending_requests: HashMap::new(),
pending_integration_installs: 0,
pending_notifications: Vec::new(),
visible_notification: None,
+ endpoint_notice_seen: HashSet::new(),
+ visible_endpoint_notice: None,
outer_focused: None,
ascii_input_source_active: false,
pending_input_source_changes: Vec::new(),
@@ -987,6 +1019,16 @@ impl ClientShellState {
}
}
+ pub(crate) fn set_endpoint_methods(&mut self, methods: Option<Vec<String>>) {
+ self.endpoint_methods = methods.map(|methods| methods.into_iter().collect());
+ }
+
+ pub(super) fn supports_endpoint_method(&self, method: &crate::api::schema::Method) -> bool {
+ self.endpoint_methods
+ .as_ref()
+ .is_none_or(|methods| methods.contains(crate::api::api_method_name(method)))
+ }
+
fn focused_tab_count(&self) -> usize {
let Some(snapshot) = self.snapshot.as_deref() else {
return 0;
@@ -1072,7 +1114,10 @@ impl ClientShellState {
}
}
- pub(crate) fn set_snapshot(&mut self, snapshot: Box<ClientShellSnapshot>) {
+ pub(crate) fn set_snapshot(&mut self, mut snapshot: Box<ClientShellSnapshot>) {
+ snapshot
+ .commands
+ .retain(|command| command.action != crate::protocol::ClientShellCommandAction::Unknown);
if self.snapshot.as_ref().is_some_and(|current| {
current.boot_id == snapshot.boot_id && snapshot.revision < current.revision
}) {
@@ -1154,6 +1199,8 @@ impl ClientShellState {
self.pending_integration_installs = 0;
self.pending_notifications.clear();
self.visible_notification = None;
+ self.endpoint_notice_seen.clear();
+ self.visible_endpoint_notice = None;
self.endpoint_error = None;
self.navigate_workspace_id = None;
self.overlay = self