Skip to content

feat(status): OSC 7501 program status — working state, exact waiting dots, heuristic fallback - #187

Closed
mortenaslo wants to merge 2 commits into
mainfrom
feat/osc7501-program-status
Closed

mortenaslo wants to merge 2 commits into
mainfrom
feat/osc7501-program-status

Conversation

@mortenaslo

Copy link
Copy Markdown
Contributor

Part of #154 (Part 1). Part 2, the timeline and dashboard, is still open.

Adds the Program Status Protocol (OSC 7501), so a program running in a session can report its own state: working (with progress), blocked on permission or input, done, or error. The existing regex detection stays as the fallback for sessions that never send it.

⚠️ Claude Code does not report under the inbox ConPTY yet

Claude Code 2.1.319 only starts reporting after its OSC 7501 ; ? query is answered. Measured against the inbox conhost (10.0.26100), two things prevent that:

  1. Conhost drops OSC sequences written to its input. Key events in win32-input-mode do get through.
  2. Conhost answers the follow-up CSI c (DA1) query itself, immediately. Claude treats that answer as "no more replies", so by the time our reply arrives it has already concluded there is no support. The reply then lands as late keyboard input.

So answering the query is off by default (AnswerProgramStatusProbe, settable in state.json only). When it is enabled for experiments, a reply is sent only when all of these hold:

  • the query is exactly ?;
  • the ConPTY has requested win32-input-mode;
  • at most one reply per 5 s and 3 per session;
  • the write happens off the UI thread.

Programs that report without querying first work now. Possible routes for Claude: upstream accepting a late reply or adding an opt-in, or CSM bundling a newer ConPTY.

What changes

  • terminal-init.js forwards OSC 7501 to the host, and watches OSC 133 A (prompt start) without consuming it.

  • Services/ProgramStatusPayload is a strict, WPF-free parser. A report that breaks any rule is dropped whole:

    • allowed characters, and id/app syntax
    • size limits
    • base64, UTF-8, control characters, U+2028/2029

    Invisible format characters are stripped per code point.

  • Services/ProgramStatusRecords is the record tree:

    • each report replaces its record; clear also removes children
    • 256-record cap with eviction
    • lifetime rules on exit and prompt start
    • acknowledge, and attention transitions judged per record
  • The session shows one combined state. Precedence is blocked > working > error > done > idle.

    • working: a pulsing ⟳ working · 40% in the sidebar and ⟳ 40% on the toolbar. These live in the WPF chrome and animate only while visible.
    • blocked + permission: the orange approval dot. Any other blocked: the green input dot.
    • entering blocked, done or error: the attention badge and a toast, at most one per session every 5 s. This is judged per record, so a root done still badges while a background child keeps working.
  • Fallback: while a session holds any 7501 record, AlertDetector stands down. It re-arms when the records are cleared, when a prompt start or exit leaves none, or via a new sidebar item, Clear status.

  • A real keystroke or focusing the session acknowledges done/error. xterm's automatic replies do not.

  • Live-state reports that arrive after the PTY has exited are ignored.

  • Runtime-only; nothing is written to state.json.

Review

A read-only review of the first commit found eight issues, all fixed in the second commit. The main ones:

  • The query reply could be triggered by any output, such as cat of a file, and could loop with an echoing program.
  • A background child record hid the root's done.
  • Working/blocked could stick forever, with the fallback permanently off.
  • The parser deviated from the spec in a few places.

Tests

917 unit tests pass. New coverage:

  • parser edge cases
  • records and precedence, per-record attention
  • re-arming, the exit race
  • the probe gate, acknowledge rules, rate limiting

Manual verification (pwsh session, $e=[char]27)

  • "$e]7501;state=working:progress=40$e\": shows ⟳ working · 40% in the sidebar and ⟳ 40% on the toolbar.
  • "$e]7501;state=blocked:kind=permission:msg=$([Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes('Run tests?')))$e\": shows the orange dot, the badge, and a toast with "Run tests?".
  • "$e]7501;state=working:id=bg$e\", then "$e]7501;state=done$e\": shows the badge and a toast while the working indicator stays.
  • Right-click the session → Clear status: everything clears and the regex detection is active again.
  • Minimize the window, or switch to a layout that hides the pane: the pulse stops, and resumes on restore.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NsYm9iLc2aRnRDfV2uZRQX

mortenaslo and others added 2 commits November 6, 2026 17:02
…dots, heuristic fallback

Support the Program Status Protocol (OSC 7501) so programs report their own
state. Part 1 of #154.

- terminal-init.js: OSC 7501 handler (query -> programStatusProbe, report ->
  programStatus) and an observe-only OSC 133 A handler (promptStart).
- TerminalBridge answers the `OSC 7501 ; ?` probe with fixed, app-authored
  bytes, encoded as win32-input-mode key events: the inbox conhost drops OSC
  sequences written to the ConPTY input as plain VT (measured).
- Services/ProgramStatusPayload: strict WPF-free parser/validator (alphabet,
  base64 + UTF-8, control chars, spec limits; bad reports dropped whole).
- Services/ProgramStatusRecords: record tree with replace/clear/eviction,
  exit lifetime, acknowledge, and blocked > working > error > done > idle.
- SessionViewModel maps the aggregate: working -> new pulsing
  "working · N%" indicator (sidebar + toolbar), blocked+permission ->
  approval, other blocked -> input, done/error -> attention badge with a
  rate-limited toast. AlertDetector stands down once 7501 is seen; user
  interaction acknowledges done/error but keeps live states.
- Docs: CLAUDE.md section incl. the measured ConPTY limitation (conhost
  answers DA1 itself, so Claude 2.1.319's probe still settles to "no").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsYm9iLc2aRnRDfV2uZRQX
… attention, re-arm

Review fixes for the OSC 7501 program-status work (#154 Part 1).

- Probe reply OFF by default (AppSettings.AnswerProgramStatusProbe). Under the
  inbox ConPTY it always loses to conhost's own DA1 answer and only reaches the
  program as late stdin starting with ESC. When enabled, ProgramStatusProbeGate
  requires an exact "?" query, win32-input-mode requested by this session's
  ConPTY (ESC[?9001h/l tracked on output), 1 reply per 5s, 3 per session; the
  write goes to the thread pool. Reports are always parsed.
- Attention judged per record: a root done/error/blocked badges and toasts even
  while a working child holds the aggregate; the badge clears only when no
  record is in an attention state.
- Re-arm the heuristic when the store empties via clear / prompt start / exit,
  plus a sidebar "Clear status" item. Not on acknowledge.
- Exit race: working/blocked/idle reports after PTY exit are ignored.
- Parser: id segments and app use [A-Za-z0-9_.+-]{1,32} (bad app = absent),
  a key over 16 bytes drops the report, format chars stripped by rune (tag
  block), U+2028/U+2029 rejected.
- Working pulse runs only while visible (IsVisibleChanged, Loaded/Unloaded,
  minimize).
- Acknowledge on real key events (onKey) or focus, not on every onData;
  allocation-free per-keystroke guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsYm9iLc2aRnRDfV2uZRQX
@mortenaslo

Copy link
Copy Markdown
Contributor Author

Superseded by #189. Same work, rebased onto the bundled ConPTY in #188, plus the review fixes. This branch couldn't be force-pushed, so the rebased version lives on feat/osc7501-program-status-v2.

@mortenaslo mortenaslo closed this Nov 6, 2026
@mortenaslo
mortenaslo deleted the feat/osc7501-program-status branch November 6, 2026 18:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant