diff options
| author | user <user@clank> | 2026-10-09 12:24:48 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-10-09 12:31:53 +0200 |
| commit | fcf305085343bcb5c5240ed642468461f247f1ed (patch) | |
| tree | da00e12658bc22fee0e9f25e634572cf2aec9b53 /docs | |
| parent | tab bar: a sidebar that fits the terminal, and gives up the edge last (diff) | |
| download | slosh-fcf305085343bcb5c5240ed642468461f247f1ed.tar.gz slosh-fcf305085343bcb5c5240ed642468461f247f1ed.tar.xz slosh-fcf305085343bcb5c5240ed642468461f247f1ed.zip | |
osc 7501: say what you are doing in somebody else's protocol
OSC 5577 only works for programs that know slosh exists. The program
status protocol is the same idea written down for everybody, by ghostty's
author and implemented by ghostty and Rex, so a program that already
speaks it needs to know nothing about us:
printf '\033]7501;state=working:app=cargo:msg=YnVpbGRpbmc=\033\\'
Implemented *as well as* 5577, not instead of it. 5577 is a two-way
control channel -- buttons answer on the program's stdin, shaders are
refused with a reason -- and 7501 deliberately is not, so neither absorbs
the other. What 7501 buys is the programs we do not control.
The scanner becomes one pass for four things: 5577, 7501, OSC 133 ; A and
RIS. The last two are here because 7501 ties record lifetime to them -- a
new prompt or a full reset is how a stale `working` dies without a
heartbeat -- and because a pane should pay for that scan once. osc5577_fn
gives way to osc_scan_cbs_t.
Reports are all-or-nothing, which is the spec's rule: no state, an unknown
state, a broken limit, base64 that will not decode, a control character in
decoded text or a malformed id discards the whole report rather than
writing a partial one. A malformed *pair* is skipped and the rest parsed.
A pane keeps up to PANE_PS_MAX records with LRU eviction, and composes the
one line it has from them by precedence -- blocked, error, done, working,
idle -- resolving `app` from the nearest ancestor that has one, the way
the hierarchy is allowed to mean something. It lands in the slot 5577's
`status` already had, because every reader of a pane wants "what is this
doing", not "which protocol said so"; `panes` reports program_state,
program_kind, program_app and progress beside it, with -1 for a progress
nobody reported, since a bar drawn at 0% says something false.
The feature-detection reply is rate-limited here. Note for the next
re-vendor: upstream lib-vt implements 7501 too, newer than our pin, and
answers the support query itself through write_pty without rate-limiting
it. See "What upstream did" under D1 in DESIGN.md before swapping this
half of the file for GHOSTTY_TERMINAL_OPT_PROGRAM_STATUS.
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/panes.md | 5 | ||||
| -rw-r--r-- | docs/scripting.md | 64 | ||||
| -rw-r--r-- | docs/slosh.5 | 4 |
3 files changed, 70 insertions, 3 deletions
diff --git a/docs/panes.md b/docs/panes.md index 6a4b586..91f1968 100644 --- a/docs/panes.md +++ b/docs/panes.md @@ -105,6 +105,7 @@ as one figure, the same way a divider running into the ring does. Set **Under each tab, the sidebar says what the panes inside are up to.** A pane that announced a status over [OSC 5577](scripting.md#a-pane-can-draw-its-own-chrome) +or [OSC 7501](scripting.md#or-say-what-it-is-doing-in-somebody-elses-protocol) puts that line under its tab — visible from every other tab, which is the point — and a pane that died shows how instead (`exited: status 3`). At most `tab_bar_status` rows per tab (3 by default, 0 turns it off); more than that @@ -126,7 +127,9 @@ after it. One frame in `busy_mark` is a static mark and costs nothing, several make it turn, and the session only keeps a frame clock while such a row is actually on screen. The flag dies with the program: a status left behind by something that exited -describes the past. +describes the past. A program reporting over OSC 7501 gets the same spinner for +`working` and for `blocked` — a question waiting on you is still happening — and +none for `done` or `error`, which are not. **`tab_bar_padding` is the air inside the strip**, between its frame and the list: 1, 2 or 4 values in CSS order, the shape [`padding`](config.md) takes for diff --git a/docs/scripting.md b/docs/scripting.md index 74c36d4..2b484ef 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -183,6 +183,70 @@ it is sent (`cat`, a shell with echo on, a REPL waiting for a line) would otherwise be answered into a loop, which is exactly what `hello` used to do: 4 MB of hellos in a second and a half. +## ...or say what it is doing in somebody else's protocol + +OSC 5577 only works for programs that know slosh exists. **OSC 7501, the +[program status protocol](https://www.superlogical.com/rex/docs/build/program-status), +is the same idea written down for everybody** — by the author of ghostty, and +implemented by it and by Rex — so a program that already speaks it needs to know +nothing about us: + +```bash +status() { + printf '\033]7501;state=%s:msg=%s\033\\' "$1" "$(printf '%s' "$2" | base64 | tr -d '\n')" +} +status working "Syncing photos" +rsync -a ~/Photos backup:/photos && status done "Photos synced" || status error "rsync failed" +``` + +The body is `key=value` pairs separated by `:`. `state` is the only required +key, and is one of `idle`, `working`, `done`, `blocked`, `error`, or `clear` to +remove the record. The rest: `msg` and `title` (base64 UTF-8), `app` (a stable +name like `cargo`), `progress` (0–100), `kind` (`permission`, `question` or +`auth`, with `blocked`), and `id` to report more than one thing at a time. + +It lands in the same place 5577's `status` does — the pane's frame, a row in the +sidebar, `status` and `busy` in `panes` — because every reader of a pane wants +"what is this doing", not "which protocol said so". What the structure buys on +top of that is reported alongside: `program_state`, `program_kind`, +`program_app` and `progress`. + +```bash +$ slosh cmd '{"cmd":"panes"}' | jq -c '.panes[] | select(.program_state == "blocked")' +{"id":4,...,"status":"EU West: Approve deploy to production?","busy":true, + "program_state":"blocked","program_kind":"permission","program_app":"deploy","progress":-1} +``` + +The two protocols share that one slot, **last writer wins**: a 5577 +`status`/`busy`/`clear` drops the 7501 records, because a `program_state` +describing text that is no longer on screen is worse than none. Neither +protocol replaces the other — 7501 is deliberately one-way (a program reports, +the terminal decides what to show), so `buttons`, `click` and `shader` have +nowhere to live in it; and 5577 has no idea what `done` or `blocked` mean. + +**A program with several things going at once** gives each an `id`, which may be +a path: `build/test` is a child of `build`. Clearing a record clears everything +under it, a record with no `app` inherits one from its nearest ancestor that has +one, and when several records are true at the same time the pane shows the one +nearest to needing a person: `blocked`, then `error`, `done`, `working`, `idle`, +ties going to whichever was written last. So a deploy that is `working` at the +root while `eu-west` waits for approval shows the approval. + +**Records die without anybody saying so**, because there is no heartbeat in this +protocol and a status nobody retracted is the failure mode it exists to fix. A +`working` or `blocked` record is dropped when the program exits or a new shell +prompt begins (`OSC 133 ; A`); `done` and `error` survive both, which is how a +result is still there when you come back to the pane; `idle` stays until it is +replaced; and a full reset (`ESC c`) removes all of them. + +To find out whether any of this is listened to, ask: `printf +'\033]7501;?\033\\'` is answered with the same bytes, `\033]7501;?\033\\`, and +by nothing at all in a terminal that does not implement it. That reply *is* a +request — the one shape 5577 forbids — so it is rate-limited to one answer per +250 ms per pane: a program that asks once is answered at once, and a pane that +echoes our answer back into our own scanner stops there instead of trading +megabytes with us. + ## Driving it from an agent Everything above is what an agent needs, and none of it says which parts matter. diff --git a/docs/slosh.5 b/docs/slosh.5 index 6226cf9..5fab826 100644 --- a/docs/slosh.5 +++ b/docs/slosh.5 @@ -192,8 +192,8 @@ panes keep the same rectangle whatever it is. Sidebars only. .TP .BI tab_bar_status\ n Under each of a sidebar's tabs, up to \fIn\fR rows of what the panes inside -said through OSC 5577 status \(em or how they died \(em with an ellipsis when -there was more. Clicking a status row jumps to the pane that said it. +said through OSC 5577 status or OSC 7501 program status \(em or how they died +\(em with an ellipsis when there was more. Clicking a status row jumps to the pane that said it. 0 turns it off; \fBtop\fR has no room for it either way. The statuses give way before the tabs do when the sidebar is short of rows: the list is how you navigate, and a talkative pane must not cost you a tab you can click. |
