Report the TUI's status to terminals that support OSC 7501 program status

Open
#46 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
56/100
Issue type
Feature
Clarity
Clearly specified
Activity status
Active
Tech stack
typescript

Research direction

Start with .agents/skills/write-tui/SKILL.md, then read daemon-tui.ts around openSession, renderActivityState, dispose, and run-shell.ts cleanup paths. Use terminal-focus.ts as the input-listener example and inspect pi-tui’s DA1 negotiation before deciding how to coordinate the probe. Add unit and pseudo-terminal coverage for probe handling, state reports, exit cleanup, and encoding limits; update both interface guides. Done means the listed scenarios pass and any pi-tui divergence is documented and guarded by a test.

Written by the indexing model from the issue text.

Description

enhancement

What feature would you like to see?

Terminals that show the state of the programs running in them (idle, working, waiting for the user, finished) cannot tell what Kiki's TUI is doing: Kiki does not report anything, so such a terminal can only guess from the screen. The OSC 7501 program-status protocol is a generic, vendor-neutral way for a program to tell its terminal its state. The TUI should probe for it and, where the terminal answers, report its state; in every other terminal (including tmux) nothing changes.

Decided
  • Use the OSC 7501 program-status protocol (revision 0.4) exactly as summarised below; nothing in the implementation is specific to any terminal product.
  • app=kiki on every report.
  • Report only after a successful probe; never emit any OSC 7501 sequence in a terminal that did not answer it.
  • Emit state=clear on every exit path that restores the terminal.
  • Coalesce: never emit two identical consecutive reports; no periodic re-emission.
  • This issue reports the standard keys only. The session hand-off keys (handoff, session, port, server) and ticket requests are a separate issue that depends on the server hand-off work.
Design

Protocol summary (what the TUI must implement).

  • Report: ESC ] 7501 ; key=value:key=value:… ST, where ST is ESC \ or BEL. Pairs are separated by :, key and value by =, so a value can contain neither : nor ;. The whole sequence is at most 4096 bytes. Receivers ignore unknown keys. Each report replaces the previous one completely, so every report carries every key the TUI wants kept.
  • Keys used by Kiki:
    • state (required): idle, working, done, blocked, error, clear.
    • kind (only with state=blocked): permission, question, auth.
    • app: kiki.
    • title: the session title as base64 of UTF-8, at most 192 bytes before encoding, no control characters; omitted when the session has no title.
    • msg: base64 of UTF-8, one line, no control characters, at most 2048 bytes before encoding.
    • progress and id are not used.
  • A report that breaks a limit is discarded whole by the terminal, so truncate title and msg on a UTF-8 boundary before encoding, strip control characters (newlines included) first, and if the whole sequence would still exceed 4096 bytes, shorten msg until it fits.
  • Probe: at start-up write ESC ] 7501 ; ? ST immediately followed by a DA1 query ESC [ c. A supporting terminal writes back an OSC 7501 reply whose body starts with ? (it may append further pairs, which the TUI ignores) before the DA1 reply. If the DA1 reply arrives first, the terminal does not support the protocol and the TUI never writes an OSC 7501 sequence in this process. (A body that starts with ?ticket= is a ticket request, handled by the follow-up issue, not a probe reply.)

State mapping, projected from session-core SessionViewState (the TUI already derives most of this in renderActivityState):

TUI situation report
no turn running, no pending approval or question state=idle
the main agent is in a turn state=working
a permission approval is pending state=blocked:kind=permission, msg = a short summary of what is being approved
a question is pending state=blocked:kind=question, msg = the question text
sign-in is required before work can continue state=blocked:kind=auth
a turn ended normally and the user has not looked yet state=done; where the TUI cannot tell whether the user has looked, state=idle is acceptable
a turn ended with an error state=error, msg = a short error summary

When to report: once after the probe succeeds; whenever the reported state, kind, title or msg changes; whenever the open session changes by any route (/new, /clear, /sessions, /resume, persona switch, deleting the current session, a fork that moves the TUI) — all of which go through openSession; and state=clear on exit.

Input handling. The probe reply must be consumed before it reaches the editor or any key handler, whatever screen is showing, and must survive being split across reads. StdinBuffer already assembles OSC sequences (ESC ] … ST) into single sequences, and TUI.addInputListener runs listeners before components; apps/kimi-code/src/tui/utils/terminal-focus.ts (installed from DaemonTUI.start) consumes focus reports this way and is the pattern to follow.

DA1 coordination. pi-tui already writes a DA1 query as the sentinel after its kitty keyboard query (terminal.ts:17, written at :256) and consumes DA1 replies in handleKeyboardProtocolNegotiationSequence. The probe's ordering decision needs a DA1 reply too; it must neither steal the reply the keyboard negotiation waits for nor be confused by it. packages/pi-tui is vendored: any change there must be minimal and registered as a local divergence in packages/pi-tui/AGENTS.md ("Local divergences from upstream"), with a guarding test (it takes the next free divergence number; another open issue adds divergence 11), and pnpm --filter @kiki/pi-tui test must pass in full (the package is @kiki/pi-tui; packages/pi-tui/AGENTS.md still names the old filter).

Exit. Write state=clear (only if the probe succeeded) on: the normal stop path (DaemonTUI.dispose, before ui.stop()), the double Ctrl-C / Ctrl-D exit, and the emergency and signal exits in run-shell.ts.

Placement. Follow .agents/skills/write-tui/SKILL.md: run-shell.ts owns process and signal cleanup and terminal restoration; daemon-tui.ts coordinates; status is projected from SessionViewState, not rebuilt from ad hoc events; put the encoder, probe state machine and coalescing in a pure helper under src/tui/utils/ with unit tests.

Docs (en and zh, mirrored): a short paragraph in guides/interface.md saying that Kiki reports its state to terminals that support the OSC 7501 program-status protocol and that other terminals are unaffected.

Where
  • apps/kimi-code/src/tui/daemon/daemon-tui.ts:890 — openSession (every session change); :1057 — renderActivityState (current state derivation, firstPendingInteraction, view.busy); :278-345 — dispose (normal exit; ui.stop() is last); :677 — confirmExit.
  • apps/kimi-code/src/cli/run-shell.ts:100 — emergencyExit; :126, :134, :146, :163 — its callers (crash and signal paths).
  • packages/pi-tui/src/tui.ts:710 — addInputListener; :830-845 — listeners run before components.
  • packages/pi-tui/src/stdin-buffer.ts:131-140 — OSC completion.
  • packages/pi-tui/src/terminal.ts:17, :240-280 — DA1 sentinel and keyboard negotiation.
  • apps/kimi-code/src/tui/utils/terminal-focus.ts:17-30 — a live input listener that consumes terminal reports; daemon-tui.ts:243 installs it.
  • packages/protocol/src/session.ts:173-188 — busy, main_turn_active, pending_interaction, last_turn_reason.
Done when
  • With a pseudo-terminal script that does not answer the probe: no OSC 7501 sequence appears in Kiki's output, and keyboard protocol negotiation still works (test).
  • With a script that answers the probe before DA1: after start-up there is one report with state and app=kiki; sending a prompt produces working and then done or idle; triggering an approval produces state=blocked:kind=permission with a msg; a question produces kind=question; exiting produces state=clear (test).
  • /new and /sessions each produce a new report with the new session's title (or none); identical consecutive reports never appear (test).
  • A title or message with :, ;, newlines, control characters or more than the byte limits produces a valid report within all limits (unit tests).
  • The probe reply never shows up in the editor, a dialog or a picker, including when it is split across two reads (test).
  • Any pi-tui change is listed as a divergence in packages/pi-tui/AGENTS.md with a guarding test; pnpm --filter @kiki/pi-tui test passes.
  • en and zh docs updated.

Additional information

  • Nothing of this exists yet: rg 7501 finds nothing in the repository, and the TUI writes no terminal title.
  • The extension keys and the ticket request (session hand-off) are the follow-up issue; keep the encoder and the input listener easy to extend with more keys and a second request type.
  • The protocol is published as the "Program Status Protocol (OSC 7501)", revision 0.4; the summary above is complete for this issue.

Order and scope. Paths and line numbers refer to the code after the pending GUI interaction and mobile pull request is merged into kiki.
Main files: apps/kimi-code/src/tui/utils/program-status.ts, apps/kimi-code/src/tui/daemon/daemon-tui.ts, apps/kimi-code/src/cli/run-shell.ts, packages/pi-tui/src/terminal.ts, packages/pi-tui/AGENTS.md, apps/kimi-code/test/**, docs/en/guides/interface.md, docs/zh/guides/interface.md.

Dominant language
TypeScript
Stars
54
Forks
5
Avg merge
2h 54m
Merged PRs (30d)
2

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from X-T-E-R/kiki

All issues in X-T-E-R/kiki

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.