diff options
| author | user <user@clank> | 2026-08-12 17:05:34 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-12 17:05:34 +0200 |
| commit | df052db4608f4d835fe7b4ca76dbb4059e78943b (patch) | |
| tree | 639c58957611a01b8674ebd0ce9c123482d6b846 /zellij-utils/src/plugin_api | |
| parent | sl0p: propose per-pane workspace hints (diff) | |
| download | sl0ppij-df052db4608f4d835fe7b4ca76dbb4059e78943b.tar.gz sl0ppij-df052db4608f4d835fe7b4ca76dbb4059e78943b.tar.xz sl0ppij-df052db4608f4d835fe7b4ca76dbb4059e78943b.zip | |
sl0p: per-pane workspace hints (purpose=)
Implements the zellij half of sl0p/workspace-hints-proposal.md: a pane can carry
an opaque agent-facing hint for what it is FOR, so a project can ship a layout
that an agent is able to navigate instead of guessing.
zellij never interprets a purpose. The consuming agent owns the entire vocabulary,
which is what keeps this to one string threaded along the existing status_bar path
rather than a policy engine, and lets the vocabulary change without a rebuild.
Two ways in, because declared panes and agent-created panes are different:
layout: pane name="server" purpose="dev-server"
in-band: printf '\033]5577;1;purpose;agent:build\033\\'
The second exists because tagging at spawn time would mean threading a field
through 28 SpawnTerminal call sites, and a new action variant is impossible while
protobuf enums cannot be regenerated. Wrapping the command costs one printf.
A declared purpose outranks an in-band one and cannot be overridden or cleared by
it. OSC arrives from whatever the pane happens to be showing, so otherwise a
`cat hostile.txt` in the human's editor pane could relabel it as the agent's
scratch pane and invite an agent to run commands where the human is working. A
layout is written by a human in a file they can read; in-band data only fills a
blank.
Exposed on PaneListEntry (`list-panes --json`) rather than PaneInfo, which crosses
the plugin protobuf API we cannot regenerate; resolved in the screen thread where
panes live rather than via route.rs's per-pane PTY round-trips. Values are
sanitised to a bounded [A-Za-z0-9_.:/-] token at both entry points, because a
purpose is never rendered -- it is matched against and pasted into consumers'
shell and JSON pipelines.
Verified end to end rather than by inspection: purposes from a layout appear in
list-panes --json against the right pane ids, an agent-spawned pane tags itself,
a declared purpose survives an in-band override attempt, all of it comes back
after kill + resurrect, an unknown pane property is still a parse error, and a
layout with no purpose leaves dump-layout unchanged.
Diffstat (limited to 'zellij-utils/src/plugin_api')
| -rw-r--r-- | zellij-utils/src/plugin_api/action.rs | 5 |
1 files changed, 5 insertions, 0 deletions
diff --git a/zellij-utils/src/plugin_api/action.rs b/zellij-utils/src/plugin_api/action.rs index edc9ce66..e3cd29f8 100644 --- a/zellij-utils/src/plugin_api/action.rs +++ b/zellij-utils/src/plugin_api/action.rs @@ -3096,7 +3096,11 @@ impl TryFrom<ProtobufTiledPaneLayout> for TiledPaneLayout { } }); let run_instructions_to_ignore = vec![]; // Not serialized in protobuf + // Not serialized in protobuf: plugins have no use for a pane purpose and + // adding a proto field would force regenerating the committed assets. + let purpose = None; Ok(TiledPaneLayout { + purpose, children_split_direction, name: protobuf.name, children, @@ -3162,6 +3166,7 @@ impl TryFrom<ProtobufFloatingPaneLayout> for FloatingPaneLayout { let y = protobuf.y.map(|y| y.try_into()).transpose()?; let run = protobuf.run.map(|r| r.try_into()).transpose()?; Ok(FloatingPaneLayout { + purpose: None, // no protobuf representation name: protobuf.name, height, width, |
