Skip to content

libghostty-vt: program status protocol (OSC 7501) - #14560

Merged
mitchellh merged 1 commit into
mainfrom
osc7501
Oct 6, 2026
Merged

mitchellh merged 1 commit into
mainfrom
osc7501

Conversation

@mitchellh

@mitchellh mitchellh commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

This adds support for OSC 7501, the program status protocol. A program uses it to tell the terminal what it is doing (idle, working, done, blocked on the user, or failed) and why.

OSC7501 is the first net-new terminal specification to be pioneered by the Ghostty project. In full transparency, this work was motivated by my work on Superlogical, but the specification was carefully written in a way that doesn't bias towards that use case. The specification is broadly useful across a wide variety of applications. I also distributed the specification with a diverse group of application and terminal developers to solicit feedback.

The terminal doesn't keep records itself, the same as OSC 9;4. The embedder keeps one record per id and applies the spec's lifetime rules. A full reset reports a clear of every record. The terminal only answers the support query (OSC 7501 ; ?) while the effect is set, so programs don't send reports that nothing reads. The Ghostty app ignores these reports for now.

References:

@mitchellh mitchellh added this to the 1.4.0 milestone Oct 6, 2026
@mitchellh

Copy link
Copy Markdown
Contributor Author

Note this does NOT add it to terminfo because Ghostty itself doesn't do anything with this yet.

This adds support for OSC 7501, the program status protocol. A program
uses it to tell the terminal what it is doing (idle, working, done,
blocked on the user, or failed) and why. 

The terminal doesn't keep records itself, the same as OSC 9;4. The
embedder keeps one record per id and applies the spec's lifetime rules.
A full reset reports a clear of every record. The terminal only answers
the support query (`OSC 7501 ; ?`) while the effect is set, so programs
don't send reports that nothing reads. The Ghostty app ignores these
reports for now.

References:

- Program Status Protocol (OSC 7501) specification:
  https://www.superlogical.com/rex/docs/build/program-status
Lockyc added a commit to Lockyc/warden that referenced this pull request Oct 6, 2026
…unlock

Hashimoto published OSC 7501 (Program Status Protocol) on 2026-10-06.
Upstream PR ghostty-org/ghostty#14560 implements it in libghostty-vt
only and the apprt ignores it, so warden's surface API has no action to
consume until upstream adds one.
@mitchellh
mitchellh marked this pull request as ready for review October 6, 2026 21:52
@mitchellh
mitchellh requested a review from a team as a code owner October 6, 2026 21:52
@mitchellh
mitchellh merged commit a4aacd9 into main Oct 6, 2026
@mitchellh
mitchellh deleted the osc7501 branch October 6, 2026 21:59
lawrencecchen added a commit to manaflow-ai/ghostty-next that referenced this pull request Oct 7, 2026
Port of ghostty-org/ghostty#14560 (upstream commit bae2c3c, merged
as a4aacd9). A program reports what it is doing (idle, working, done,
blocked, error) with OSC 7501; the terminal answers the support query
only while the program_status effect is set and reports a clear of
every record on a full reset.

Conflict resolution: GHOSTTY_TERMINAL_OPT_PROGRAM_STATUS is 47 in this
fork because upstream's 46 collides with the manaflow-ai Kitty option
additions (44-46). The XTCHECKSUM stream actions that sit next to the
upstream change are not part of this fork yet and are left out.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Gaurav-Gosain added a commit to Gaurav-Gosain/tuios that referenced this pull request Oct 7, 2026
ghostty-org/ghostty#14560 merged OSC 7501 parsing into libghostty-vt on 2026-10-06; the Ghostty app does not show it yet, and the libghostty-vt tuios pins predates it. The lifetime table and the skill now say a record ends with its foreground process group, name the race of a program that exits as it reports, and say the exit rule does not run with the agent features off.
hualet pushed a commit to hualet/deepin-terminal-ghostty that referenced this pull request Oct 8, 2026
Pin libghostty-vt and its public headers to the upstream merge of
the program status protocol (OSC 7501, ghostty-org/ghostty#14560) so
the Qt wrapper can receive program status reports.

The existing wrapper builds unchanged against the new headers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
victorarias pushed a commit to SolenesInc/attn that referenced this pull request Oct 9, 2026
Moves the shared pin to upstream's current tip, which adds the OSC 7501
program status protocol (ghostty-org/ghostty#14560).

Upstream also made bytes 0x80-0xFF payload data inside DCS strings, so a
UTF-8 "Ü" (0xC3 0x9C) no longer ends one through its 8-bit ST. The kitty
segmenters in the PTY worker and the shared host now track the DCS head
and string separately to stay in step with ghostty's parser.

Claude-Session: https://claude.ai/code/session_011TmULuw1XcALkCiYRK9DN5
victorarias added a commit to SolenesInc/attn that referenced this pull request Oct 9, 2026
* chore(vt): bump libghostty-vt to ce63fcb

Moves the shared pin to upstream's current tip, which adds the OSC 7501
program status protocol (ghostty-org/ghostty#14560).

Upstream also made bytes 0x80-0xFF payload data inside DCS strings, so a
UTF-8 "Ü" (0xC3 0x9C) no longer ends one through its 8-bit ST. The kitty
segmenters in the PTY worker and the shared host now track the DCS head
and string separately to stay in step with ghostty's parser.

Claude-Session: https://claude.ai/code/session_011TmULuw1XcALkCiYRK9DN5

* feat(agents): read agent state from OSC 7501 program status

attn now answers the OSC 7501 support query in every terminal, through
libghostty-vt, and turns a terminal's root program status record into
session evidence. Claude Code (2.1.295+) reports working, blocked on
permission or a question, done and error directly, so its state no longer
depends on reading title glyphs. Once a terminal has a root record, its
title heartbeats are ignored until the record clears.

- PTY worker and shared Rust host both capture reports and publish them
  as program_status observations; the host replays its last signal with
  that source.
- The resolver treats a record as lasting evidence: working, blocked and
  error map straight to states, done and idle defer to the classifier,
  and a newer harness edge still wins.
- The fake Claude probes and reports like the real one.

Claude-Session: https://claude.ai/code/session_011TmULuw1XcALkCiYRK9DN5

* fix(vt): map program status by header names and test the shared host path

Claude-Session: https://claude.ai/code/session_011TmULuw1XcALkCiYRK9DN5

* test(pty): probe DCS ignore transitions in the ground parity corpus

Claude-Session: https://claude.ai/code/session_011TmULuw1XcALkCiYRK9DN5

* fix(state): let a newer program status record retire older harness edges

Claude-Session: https://claude.ai/code/session_011TmULuw1XcALkCiYRK9DN5

* ci: run the frontend suite when the ghostty pin or wasm lock moves

Claude-Session: https://claude.ai/code/session_011TmULuw1XcALkCiYRK9DN5

* docs(harnesses): a session is shown in at most one terminal

Claude-Session: https://claude.ai/code/session_011TmULuw1XcALkCiYRK9DN5

---------

Co-authored-by: Victor Arias <victora@spotify.com>
onevcat added a commit to onevcat/ghostty that referenced this pull request Oct 10, 2026
The parser and the libghostty-vt callback landed upstream in ghostty-org#14560, but
the app path still ignored `.program_status`. Answer the support query
(always: the embedder consumes the reports), copy each validated report
body into the surface mailbox, decode it on the app thread, and hand it
to the apprt as GHOSTTY_ACTION_PROGRAM_STATUS. A full reset sends a
clear report with an empty id, as the vt layer does.

The C struct mirrors GhosttyTerminalProgramStatus from the vt API and is
passed by pointer so ghostty_action_u keeps its size. GTK lists the
action as unimplemented.
kovidgoyal pushed a commit to kovidgoyal/kitty that referenced this pull request Oct 13, 2026
Implement revision 0.4 of the Program Status Protocol, which lets a
program report whether it is idle, working, done, blocked on the user
or failed, and why.

- Parse and validate reports per the spec: limits, base64 text without
  control characters, malformed ids discard the report instead of
  falling back to the root record, unknown states are ignored
- Keep up to 256 records per window (LRU eviction), with clear removing
  a record and its descendants and app inherited from ancestors
- Reply to the feature detection query with supported states and kinds
- Lifetime: working/blocked/idle records are dropped at a new shell
  prompt (OSC 133;A), done/error are dropped when the window gets focus,
  a full reset (RIS) removes every record
- Expose records via tab.program_status in tab_title_template, the
  output of kitten @ ls and a new on_program_status_change watcher

Spec: https://www.superlogical.com/rex/docs/build/program-status
Ref: ghostty-org/ghostty#14560, ghostty-org/ghostty#14630

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
simonklee added a commit to anomalyco/opentui that referenced this pull request Oct 13, 2026
OSC 7501, the [Program Status
Protocol](https://www.superlogical.com/rex/docs/build/program-status),
lets a program tell the terminal whether it is idle, working, done,
blocked on the user, or failed, and why. opencode wants to report agent
state this way, so OpenTUI now sends these reports.

`renderer.setProgramStatus(status)` sends one report:

```ts
renderer.setProgramStatus({ state: "blocked", kind: "permission", app: "opencode", message: "Allow bash?" })
renderer.setProgramStatus({ state: "working", id: "subagent-1", title: "Explore", progress: 40 })
renderer.setProgramStatus({ state: "clear", id: "subagent-1" })
```

Native code validates and encodes each report through a new Session
control kind, `OT_CONTROL_PROGRAM_STATUS`, so C hosts get the same
checks. It rejects every report that a terminal would discard or partly
ignore:

- an unknown state
- `kind` without `blocked`
- `progress` without `working` or `blocked`, or above 100
- a malformed id, or an `app` with bytes outside `[A-Za-z0-9_.+-]`
- a title or message with control characters, or longer than 192 or 2048
bytes
- any field other than `id` on `clear`

The checks run before lifecycle admission, so an invalid report throws
in every phase. Title and message are encoded as base64.
`program_status_bytes_max` derives the longest valid report, 3220 bytes,
from the limits. A comptime assertion checks that it fits one 4096-byte
control packet, so the encoder cannot fail. The method returns `false`
when the report is not queued: the renderer is destroyed, the terminal
is not active, or output is full.

Startup detection sends `OSC 7501 ; ?` with the other capability
queries, and `capabilities.program_status` becomes true when the
terminal replies. Foot does not get the query, because its reduced probe
omits OSC queries. Reports do not wait for detection: terminals ignore
OSC sequences that they do not implement.

Reports are never wrapped in tmux or GNU Screen passthrough. The outer
terminal cannot tell panes apart or see the program exit, so its
`working` records would never clear. The renderer does not clear records
on destroy or suspend. The spec has the terminal drop `working` and
`blocked` records when the process exits, and keep `done` and `error`
records for the user.

Changes to existing APIs:

- `Session.control` and `ot_session_control` now check payloads before
the phase. An invalid title sent while the terminal is inactive returns
`OT_INVALID_ARGUMENT` instead of `OT_INVALID_PHASE`. Both throw in
TypeScript, and no caller depends on the difference.
- `TerminalCapabilities` has a new required field, `program_status`.
- Startup output includes the OSC 7501 query.

Testing:

- Native table tests pin the exact output, including the spec's own
examples and the 3220-byte longest report. They cover every rejection
rule, rejection while inactive or under output pressure with state left
unchanged, and no allocation in the control path. The ABI test covers
record decoding.
- The renderer test covers what TypeScript can break: field packing
order, inherited fields, UTF-8 encoding, and its own type errors. It
checks `false` before setup, while suspended, with full output, and
after destroy, and detection from end to end with libghostty-vt's exact
reply. It also runs under Node.
- I checked the output against the libghostty-vt parser from
ghostty-org/ghostty#14560 and #14630. All 15 reports that OpenTUI
emitted parsed, with every field decoded as sent. They cover every state
and kind, progress at 0 and 100, the deepest and longest ids, non-ASCII
text, all base64 padding cases, and the largest report. The Ghostty app
itself does not answer the query yet, so `program_status` stays false
there.
- `bun run test:native`, `bun run test`, `bun run test:js:node`, `bun
run fmt:check`, `bun run lint`, and `bun run validate:docs` pass.

Docs: a new `application-apis/program-status` page, with links from the
renderer and terminal capabilities pages. Example:
`packages/examples/src/program-status-demo.ts`.
jemdiggity pushed a commit to jemdiggity/ghostty that referenced this pull request Oct 15, 2026
This adds support for OSC 7501, the program status protocol. A program
uses it to tell the terminal what it is doing (idle, working, done,
blocked on the user, or failed) and why.

The terminal doesn't keep records itself, the same as OSC 9;4. The
embedder keeps one record per id and applies the spec's lifetime rules.
A full reset reports a clear of every record. The terminal only answers
the support query (`OSC 7501 ; ?`) while the effect is set, so programs
don't send reports that nothing reads. The Ghostty app ignores these
reports for now.

References:

- Program Status Protocol (OSC 7501) specification:
  https://www.superlogical.com/rex/docs/build/program-status

Cherry-picked from ghostty-org#14560 (bae2c3c, merged as a4aacd9), without the XTCHECKSUM and mouse_shape_reset options kanna4 doesn't have; the option keeps upstream's number, 46.
jemdiggity added a commit to jemdiggity/ghostty that referenced this pull request Oct 16, 2026
jemdiggity added a commit to jemdiggity/ghostty that referenced this pull request Oct 16, 2026
Brings in libghostty-vt's program status protocol (OSC 7501, ghostty-org#14560, revision 0.4). A snapshot load's stream handler and terminal take upstream's new XTCHECKSUM settings from the config, as a new surface's do.
wanggang316 added a commit to wanggang316/codans that referenced this pull request Oct 17, 2026
Bump the Ghostty fork to backport the upstream OSC 7501 parser
(ghostty-org/ghostty#14560) and add an app-path action: the terminal
answers the `OSC 7501 ; ?` support query and delivers each report as
GHOSTTY_ACTION_PROGRAM_STATUS. Upstream only wires the parser into
libghostty-vt and drops reports on the app path.

Bump zmx so a daemon with no attached client answers the support query
next to DA1. Claude Code probes once at startup and sends no reports
when DA1 answers first.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant