feat: agent status (OSC 9001) + terminal in-band protocol features#156
Open
matej21 wants to merge 12 commits into
Open
feat: agent status (OSC 9001) + terminal in-band protocol features#156matej21 wants to merge 12 commits into
matej21 wants to merge 12 commits into
Conversation
iTerm2 shell integration emits OSC 1337 ; CurrentDir=<path> instead of OSC 7. Feed it into the same reported_cwd storage so the sidebar, "new tab here", and cwd tracking work regardless of which sequence the shell speaks. Other OSC 1337 subcommands are deliberately ignored. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbKbY5iGGgXSnpHz2V5aSr
Add per-terminal progress state for the ConEmu/Windows Terminal protocol OSC 9 ; 4 ; st ; pr (also used by WezTerm, Ghostty, Kitty). Programs like npm/cargo emit it to report progress. Previously these sequences were swallowed as plain-text notifications; now they're parsed into a TerminalProgress state exposed via Terminal::progress(). UI consumption comes in a follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbKbY5iGGgXSnpHz2V5aSr
Surface OSC 9;4 progress as a thin 2px bar pinned to the bottom of each terminal tab. Determinate states fill value%; indeterminate renders a dim full-width busy bar. Colored by state: normal=border_active, error=error, paused=warning. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbKbY5iGGgXSnpHz2V5aSr
Add JumpToPrevious/NextFailedCommand actions that walk only the prompts whose command finished with a non-zero exit code, mirroring the existing prompt-jump navigation. Bound to cmd-shift-up / cmd-shift-down in the TerminalPane context. The viewport scroll step is factored into a shared helper reused by both jump-to-prompt and jump-to-failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbKbY5iGGgXSnpHz2V5aSr
Handle OSC 52 read requests (OSC 52 ; c ; ?) where a terminal app asks for the clipboard contents. Gated behind a new allow_clipboard_read setting that defaults to OFF, since clipboard read lets a program exfiltrate copied data. Requests are queued by the event listener and drained in the PTY event loop, where the setting and the system clipboard are reachable; when disabled they're silently dropped. OSC 52 write is unaffected and always allowed. Adds a settings-panel toggle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbKbY5iGGgXSnpHz2V5aSr
Answer the XTWINOPS 'report text area size in pixels' query (CSI 14 t). alacritty emits a TextAreaSizeRequest with a formatter; the listener now supplies the current geometry (cols/rows x cell pixel dims) and writes the reply back to the PTY. CSI 18 t (size in cells) already worked via PtyWrite. The listener's shared query state (palette + size) is grouped into a CurrentState struct to keep the constructor compact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbKbY5iGGgXSnpHz2V5aSr
Honor the kitty keyboard protocol 'disambiguate escape codes' flag (TermMode::DISAMBIGUATE_ESC_CODES), which alacritty already tracks but okena ignored — it always sent legacy bytes. When an app enables disambiguate mode, ambiguous keys now emit CSI u: Esc (always), ctrl/alt+key (Ctrl+i vs Tab), and modified Enter/Tab/Backspace (Shift+Tab). Plain text, arrows, and Home/End fall through to the existing legacy logic, so there is zero change when the flag is off. Higher levels (report-all-keys-as-esc, associated text, event types) need input-dispatch changes and are tracked as a follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbKbY5iGGgXSnpHz2V5aSr
alacritty gates ALL kitty keyboard-mode handling (push/pop/set/report of the CSI u mode sequences) behind config.kitty_keyboard, which defaults to false. With it off, an app enabling the protocol was silently ignored and term.mode() never reflected DISAMBIGUATE_ESC_CODES, so the level-1 encoder added in 71e69e81 never fired and CSI ? u queries went unanswered. Enable the flag so the protocol actually works. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbKbY5iGGgXSnpHz2V5aSr
Determinate OSC 9;4 progress now shows the percentage in the tab label (e.g. "build 60%") so it reads even when the bar is a thin sliver, and bump the bar to 3px. Indeterminate progress stays bar-only (no value). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbKbY5iGGgXSnpHz2V5aSr
The input::tests unit tests drive key_to_bytes with hand-built flags, bypassing the config->mode->flag chain that the missing kitty_keyboard config flag broke. Add tests that push CSI > 1 u through process_output and assert kitty_keyboard_flags() flips and the encoder emits CSI u, so the wiring (and TermConfig.kitty_keyboard) can't silently regress. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbKbY5iGGgXSnpHz2V5aSr
Esc, Tab and Shift+Tab reach the terminal pane via dedicated GPUI actions (SendEscape/SendTab/SendBacktab) so they participate in context routing — but their handlers sent hard-coded legacy bytes, bypassing key_to_bytes and the kitty encoder. So even with disambiguate mode on, Esc stayed bare \x1b and Shift+Tab stayed \x1b[Z. Add kitty-aware Terminal::send_escape/send_tab/send_backtab that route through key_to_bytes, so Esc → CSI 27 u and Shift+Tab → CSI 9;2 u in disambiguate mode (plain Tab stays \t). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbKbY5iGGgXSnpHz2V5aSr
… & resume Report what an AI coding agent (Claude Code, Codex, …) is doing in each pane via a push-based, in-band OSC 9001 protocol, and remember/resume its session across restarts. The model is open: a small fixed lifecycle drives colour/sort/ notifications while a free-form message + labels carry whatever the agent wants. Display & protocol: - OSC 9001 (st/msg/lbl) parsed in the terminal sidecar into a canonical AgentStatus; surfaced as a per-tab indicator, a cross-project sidebar AGENTS section (click to jump), a per-project field in the remote API, and a desktop notification on blocked/done. Runtime-only, never persisted. - reveal_terminal navigates to a hidden pane (retargets zoom/fullscreen) behind agent-row / notification / remote-focus clicks. - Bundled Claude Code plugin (integrations/claude-code) maps lifecycle hooks to states; OKENA_TTY exports the pane's slave pts so hooks without a controlling terminal can emit the OSC. Session capture, persistence & resume (harness-extensible): - Reserved lbl keys agent/session_id/transcript_path are captured into a sticky per-pane AgentSession (UUID-validated, survives st=clear), persisted per terminal in workspace.json, and resumed on restore behind the opt-in auto_resume_agent_sessions setting (e.g. `claude --resume <id>`). - Per-harness resume/transcript logic lives behind a gpui-free okena_core::agent_harness registry keyed by agent id (works desktop + headless); Claude Code + Codex impls in the okena-ext-* crates. Adding a harness is additive. - Auto-resume requires a session backend (the terminal_id the session is keyed by only survives a restart with tmux/dtach/screen). Hardening: - Bound OSC 9001 custom/labels to mirror the OSC 99 caps (a hostile pane could otherwise pin unbounded in-band memory, amplified to every remote client); decode off-lock; empty msg falls back to the default notification body. - Headless PTY loop now drains the remote_dirty edge so agent status pushes to remote/mobile clients (was desktop-only). - reveal_terminal no longer clears individual-zoom on a same-project click (regressed sidebar/cursor clicks); corrected the OKENA_TTY tmux-reattach docs. Docs: docs/agent-status.md, crates/okena-terminal/CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbKbY5iGGgXSnpHz2V5aSr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A batch of in-band terminal-protocol features on this branch, headlined by
Agent Status (OSC 9001) — the focus of this session (final commit
fc9d724e).Agent Status (OSC 9001) — new this session
Report what an AI coding agent (Claude Code, Codex, …) is doing in each pane via
a push-based, in-band escape sequence, and remember/resume its session across
restarts.
Display & protocol
to jump), a per-project
terminal_agent_statusfield in the remote API, and adesktop notification on
blocked/done. Runtime-only.integrations/claude-code/) maps lifecyclehooks → states;
OKENA_TTYlets hooks without a controlling terminal emit the OSC.Session capture, persistence & resume (harness-extensible)
lblkeysagent/session_id/transcript_path→ a sticky per-paneAgentSession(UUID-validated, survivesst=clear), persisted per terminal inworkspace.json, resumed on restore behind the opt-inauto_resume_agent_sessionssetting (claude --resume <id>).okena_core::agent_harnessregistry keyed byagentid (desktop + headless);Claude Code + Codex impls in the
okena-ext-*crates — adding a harness is additive.terminal_idthe session is keyedby only survives a restart with tmux/dtach/screen).
Hardening (internal review pass)
custom/labels(mirror the OSC 99 caps) against unbounded in-band inputamplified to every remote client; base64 decode off-lock; empty
msg→ default body.remote_dirtyedge so agent status pushes toremote/mobile clients (was desktop-only).
reveal_terminalno longer clears individual-zoom on a same-project click;corrected the
OKENA_TTYtmux-reattach docs.Docs:
docs/agent-status.md,crates/okena-terminal/CLAUDE.md.Other terminal-protocol commits already on the branch
OSC 1337 cwd · OSC 9;4 progress + tab progress (+ percent) · OSC 133 jump-to-failed ·
OSC 52 clipboard read (opt-in) · CSI 14t pixel size · kitty keyboard protocol
(level 1) + Esc/Tab routing.
Testing
cargo build(workspace) ✅; tests green for the touched crates (okena-core,okena-terminal, okena-workspace, okena-state, okena-ext-claude, okena-views-terminal).
remote_dirtyedges,reveal_terminalregressions, UUID validation, the Claudeharness command,
parse_labels_json.claude) isbuild-/unit-tested only, not exercised live.
Follow-ups (not in this PR)
codexresume invocation (harness stub returnsNone)okena-ext-codexalready parses its JSONL.🤖 Generated with Claude Code
https://claude.ai/code/session_01KbKbY5iGGgXSnpHz2V5aSr