aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-utils/src/session_serialization.rs
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-12 18:44:24 +0200
committeruser <user@clank>2026-08-12 18:44:24 +0200
commit2c67579398835d51e625085ce3b22c22bc8f0cba (patch)
tree2b22d137d988f0bbf658ec4d37336f813933bfa1 /zellij-utils/src/session_serialization.rs
parentsl0p: document workspace hints for consumers (diff)
downloadsl0ppij-2c67579398835d51e625085ce3b22c22bc8f0cba.tar.gz
sl0ppij-2c67579398835d51e625085ce3b22c22bc8f0cba.tar.xz
sl0ppij-2c67579398835d51e625085ce3b22c22bc8f0cba.zip
sl0p: --purpose flag and tab-level purpose
Completes the two pieces deferred from the first workspace-hints pass. `zellij run --purpose X` / `zellij action new-pane --purpose X` tag a pane at spawn time, which is what an agent wants for panes it creates itself; the OSC wrapper still works for programs that declare their own role. Not supported with --in-place, which replaces another pane rather than being a pane of its own: clap rejects the combination rather than accepting a flag that would do nothing. `tab name="work" purpose="agent-work"` gives a whole tab a role, reported by `list-tabs --json`. It rides the tab's ROOT layout node, reusing the pane field because a tab's root is the tab container and never itself a pane -- asserted by a test for the tightest case (a tab holding exactly one pane), since a leak there would silently mislabel a pane. The interesting part is why the flag first shipped broken. There are TWO protobuf boundaries -- the plugin API and the client->server IPC contract -- and a CLI flag must cross the second one. Defaulting the field in the conversion (what "no protoc" usually forces) compiles cleanly and then silently drops the value in transit, so --purpose parsed, printed a pane id, and did nothing. Fixed by adding the field to both contracts by hand, in the .proto AND the committed generated prost, kept in lockstep so a future regeneration reproduces it: protoc is only needed to regenerate, not to build. Both boundaries now have a round-trip test, and the IPC one was confirmed to fail when the wire read is removed. Sanitising moved to Grid::set_authoritative_purpose, the single choke point every out-of-band source passes through, after the CLI path was observed delivering `bad purpose; rm -rf /` intact into list-panes --json.
Diffstat (limited to 'zellij-utils/src/session_serialization.rs')
-rw-r--r--zellij-utils/src/session_serialization.rs9
1 files changed, 9 insertions, 0 deletions
diff --git a/zellij-utils/src/session_serialization.rs b/zellij-utils/src/session_serialization.rs
index 2b3f651c..75cd4ece 100644
--- a/zellij-utils/src/session_serialization.rs
+++ b/zellij-utils/src/session_serialization.rs
@@ -25,6 +25,8 @@ pub struct TabLayoutManifest {
pub floating_panes: Vec<PaneLayoutManifest>,
pub is_focused: bool,
pub hide_floating_panes: bool,
+ /// Opaque agent-facing hint for what this TAB is for.
+ pub purpose: Option<String>,
}
#[derive(Default, Debug, Clone)]
@@ -87,6 +89,7 @@ pub fn serialize_session_layout(
}
fn serialize_tab(
+ tab_purpose: Option<String>,
tab_name: String,
is_focused: bool,
hide_floating_panes: bool,
@@ -115,6 +118,11 @@ fn serialize_tab(
.entries_mut()
.push(KdlEntry::new_prop("focus", KdlValue::Bool(true)));
}
+ if let Some(purpose) = tab_purpose {
+ serialized_tab
+ .entries_mut()
+ .push(KdlEntry::new_prop("purpose", purpose));
+ }
if hide_floating_panes {
serialized_tab.entries_mut().push(KdlEntry::new_prop(
"hide_floating_panes",
@@ -652,6 +660,7 @@ fn serialize_multiple_tabs(
let floating_panes = tab_layout_manifest.floating_panes;
let hide_floating_panes = tab_layout_manifest.hide_floating_panes;
let serialized = serialize_tab(
+ tab_layout_manifest.purpose.clone(),
tab_name.clone(),
tab_layout_manifest.is_focused,
hide_floating_panes,