mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
e02fd6e1d3d0ee7a47780ab3efc1c769cd373337
22 Commits
-
chore: clean up argument-comment lint and roll out all-target CI on macOS (#16054)
## Why `argument-comment-lint` was green in CI even though the repo still had many uncommented literal arguments. The main gap was target coverage: the repo wrapper did not force Cargo to inspect test-only call sites, so examples like the `latest_session_lookup_params(true, ...)` tests in `codex-rs/tui_app_server/src/lib.rs` never entered the blocking CI path. This change cleans up the existing backlog, makes the default repo lint path cover all Cargo targets, and starts rolling that stricter CI enforcement out on the platform where it is currently validated. ## What changed - mechanically fixed existing `argument-comment-lint` violations across the `codex-rs` workspace, including tests, examples, and benches - updated `tools/argument-comment-lint/run-prebuilt-linter.sh` and `tools/argument-comment-lint/run.sh` so non-`--fix` runs default to `--all-targets` unless the caller explicitly narrows the target set - fixed both wrappers so forwarded cargo arguments after `--` are preserved with a single separator - documented the new default behavior in `tools/argument-comment-lint/README.md` - updated `rust-ci` so the macOS lint lane keeps the plain wrapper invocation and therefore enforces `--all-targets`, while Linux and Windows temporarily pass `-- --lib --bins` That temporary CI split keeps the stricter all-targets check where it is already cleaned up, while leaving room to finish the remaining Linux- and Windows-specific target-gated cleanup before enabling `--all-targets` on those runners. The Linux and Windows failures on the intermediate revision were caused by the wrapper forwarding bug, not by additional lint findings in those lanes. ## Validation - `bash -n tools/argument-comment-lint/run.sh` - `bash -n tools/argument-comment-lint/run-prebuilt-linter.sh` - shell-level wrapper forwarding check for `-- --lib --bins` - shell-level wrapper forwarding check for `-- --tests` - `just argument-comment-lint` - `cargo test` in `tools/argument-comment-lint` - `cargo test -p codex-terminal-detection` ## Follow-up - Clean up remaining Linux-only target-gated callsites, then switch the Linux lint lane back to the plain wrapper invocation. - Clean up remaining Windows-only target-gated callsites, then switch the Windows lint lane back to the plain wrapper invocation.
Michael Bolin ·
2026-03-27 19:00:44 -07:00 -
Remove the legacy TUI split (#15922)
This is the part 1 of 2 PRs that will delete the `tui` / `tui_app_server` split. This part simply deletes the existing `tui` directory and marks the `tui_app_server` feature flag as removed. I left the `tui_app_server` feature flag in place for now so its presence doesn't result in an error. It is simply ignored. Part 2 will rename the `tui_app_server` directory `tui`. I did this as two parts to reduce visible code churn.
Eric Traut ·
2026-03-27 22:56:44 +00:00 -
chore: remove skill metadata from command approval payloads (#15906)
## Why This is effectively a follow-up to [#15812](https://github.com/openai/codex/pull/15812). That change removed the special skill-script exec path, but `skill_metadata` was still being threaded through command-approval payloads even though the approval flow no longer uses it to render prompts or resolve decisions. Keeping it around added extra protocol, schema, and client surface area without changing behavior. Removing it keeps the command-approval contract smaller and avoids carrying a dead field through app-server, TUI, and MCP boundaries. ## What changed - removed `ExecApprovalRequestSkillMetadata` and the corresponding `skillMetadata` field from core approval events and the v2 app-server protocol - removed the generated JSON and TypeScript schema output for that field - updated app-server, MCP server, TUI, and TUI app-server approval plumbing to stop forwarding the field - cleaned up tests that previously constructed or asserted `skillMetadata` ## Testing - `cargo test -p codex-app-server-protocol` - `cargo test -p codex-protocol` - `cargo test -p codex-app-server-test-client` - `cargo test -p codex-mcp-server` - `just argument-comment-lint`
Michael Bolin ·
2026-03-26 15:32:03 -07:00 -
Fix quoted command rendering in tui_app_server (#15825)
When `tui_app_server` is enabled, shell commands in the transcript render as fully quoted invocations like `/bin/zsh -lc "..."`. The non-app-server TUI correctly shows the parsed command body. Root cause: The app-server stores `ThreadItem::CommandExecution.command` as a shell-quoted string. When `tui_app_server` bridges that item back into the exec renderer, it was passing `vec![command]` unchanged instead of splitting the string back into argv. That prevented `strip_bash_lc_and_escape()` from recognizing the shell wrapper, so the renderer displayed the wrapper literally. Solution: Add a shared command-string splitter that round-trips shell-quoted commands back into argv when it is safe to do so, while preserving non-roundtrippable inputs as a single string. Use that helper everywhere `tui_app_server` reconstructs exec commands from app-server payloads, including live command-execution items, replayed thread items, and exec approval requests. This restores the same command display behavior as the direct TUI path without breaking Windows-style commands that cannot be safely round-tripped.
Eric Traut ·
2026-03-25 22:03:29 -06:00 -
fix(tui_app_server): fix remote subagent switching and agent names (#15513)
## TL;DR This PR changes the `tui_app_server` _path_ in the following ways: - add missing feature to show agent names (shows only UUIDs today) - add `Cmd/Alt+Arrows` navigation between agent conversations ## Problem When the TUI connects to a remote app server, collab agent tool-call items (spawn, wait, delegate, etc.) render thread UUIDs instead of human-readable agent names because the `ChatWidget` never receives nickname/role metadata for receiver threads. Separately, keyboard next/previous agent navigation silently does nothing when the local `AgentNavigationState` cache has not yet been populated with subagent threads that the remote server already knows about. Both issues share a root cause: in the remote (app-server) code path the TUI never proactively fetches thread metadata. In the local code path this metadata arrives naturally via spawn events the TUI itself orchestrates, but in the remote path those events were processed by a different client and the TUI only sees the resulting collab tool-call notifications. ## Mental model Collab agent tool-call notifications reference receiver threads by id, but carry no nickname or role. The TUI needs that metadata in two places: 1. **Rendering** -- `ChatWidget` converts `CollabAgentToolCall` items into history cells. Without metadata, agent status lines show raw UUIDs. 2. **Navigation** -- `AgentNavigationState` tracks known threads for the `/agent` picker and keyboard cycling. Without entries for remote subagents, next/previous has nowhere to go. This change closes the gap with two complementary strategies: - **Eager hydration**: when any notification carries `receiver_thread_ids`, the TUI fetches metadata (`thread/read`) for threads it has not yet cached before the notification is rendered. - **Backfill on thread switch**: when the user resumes, forks, or starts a new app-server thread, the TUI fetches the full `thread/loaded/list`, walks the parent-child spawn tree, and registers every descendant subagent in both the navigation cache and the `ChatWidget` metadata map. A new `collab_agent_metadata` side-table in `ChatWidget` stores nickname/role keyed by `ThreadId`, kept in sync by `App` whenever it calls `upsert_agent_picker_thread`. The `replace_chat_widget` helper re-seeds this map from `AgentNavigationState` so that thread switches (which reconstruct the widget) do not lose previously discovered metadata. ## Non-goals - This change does not alter the local (non-app-server) collab code path. That path already receives metadata via spawn events and is unaffected. - No new protocol messages are introduced. The change uses existing `thread/read` and `thread/loaded/list` RPCs. - No changes to how `AgentNavigationState` orders or cycles through threads. The traversal logic is unchanged; only the population of entries is extended. ## Tradeoffs - **Extra RPCs on notification path**: `hydrate_collab_agent_metadata_for_notification` issues a `thread/read` for each unknown receiver thread before the notification is forwarded to rendering. This adds latency on the notification path but only fires once per thread (the result is cached). The alternative -- rendering first and backfilling names later -- would cause visible flicker as UUIDs are replaced with names. - **Backfill fetches all loaded threads**: `backfill_loaded_subagent_threads` fetches the full loaded-thread list and walks the spawn tree even when the user may only care about one subagent. This is simple and correct but O(loaded_threads) per thread switch. For typical session sizes this is negligible; it could become a concern for sessions with hundreds of subagents. - **Metadata duplication**: agent nickname/role is now stored in both `AgentNavigationState` (for picker/label) and `ChatWidget::collab_agent_metadata` (for rendering). The two are kept in sync through `upsert_agent_picker_thread` and `replace_chat_widget`, but there is no compile-time enforcement of this coupling. ## Architecture ### New module: `app::loaded_threads` Pure function `find_loaded_subagent_threads_for_primary` that takes a flat list of `Thread` objects and a primary thread id, then walks the `SessionSource::SubAgent` parent-child edges to collect all transitive descendants. Returns a sorted vec of `LoadedSubagentThread` (thread_id + nickname + role). No async, no side effects -- designed for unit testing. ### New methods on `App` | Method | Purpose | |--------|---------| | `collab_receiver_thread_ids` | Extracts `receiver_thread_ids` from `ItemStarted` / `ItemCompleted` collab notifications | | `hydrate_collab_agent_metadata_for_notification` | Fetches and caches metadata for unknown receiver threads before a notification is rendered | | `backfill_loaded_subagent_threads` | Bulk-fetches all loaded threads and registers descendants of the primary thread | | `adjacent_thread_id_with_backfill` | Attempts navigation, falls back to backfill if the cache has no adjacent entry | | `replace_chat_widget` | Replaces the widget and re-seeds its metadata map from `AgentNavigationState` | ### New state in `ChatWidget` `collab_agent_metadata: HashMap<ThreadId, CollabAgentMetadata>` -- a lookup table that rendering functions consult to attach human-readable names to collab tool-call items. Populated externally by `App` via `set_collab_agent_metadata`. ### New method on `AppServerSession` `thread_loaded_list` -- thin wrapper around `ClientRequest::ThreadLoadedList`. ## Observability - `tracing::warn` on invalid thread ids during hydration and backfill. - `tracing::warn` on failed `thread/read` or `thread/loaded/list` RPCs (with thread id and error). - No new metrics or feature flags. ## Tests - **`loaded_threads::tests::finds_loaded_subagent_tree_for_primary_thread`** -- unit test for the spawn-tree walk: verifies child and grandchild are included, unrelated threads are excluded, and metadata is carried through. - **`app::tests::replace_chat_widget_reseeds_collab_agent_metadata_for_replay`** -- integration test that creates a `ChatWidget`, replaces it via `replace_chat_widget`, replays a collab wait notification, and asserts the rendered history cell contains the agent name rather than a UUID. - **Updated snapshot** `app_server_collab_wait_items_render_history` -- the existing collab wait rendering test now sets metadata before sending notifications, so the snapshot shows `Robie [explorer]` / `Ada [reviewer]` instead of raw thread ids. --------- Co-authored-by: Eric Traut <etraut@openai.com>
Felipe Coury ·
2026-03-25 12:50:42 -06:00 -
app-server: add filesystem watch support (#14533)
### Summary Add the v2 app-server filesystem watch RPCs and notifications, wire them through the message processor, and implement connection-scoped watches with notify-backed change delivery. This also updates the schema fixtures, app-server documentation, and the v2 integration coverage for watch and unwatch behavior. This allows clients to efficiently watch for filesystem updates, e.g. to react on branch changes. ### Testing - exercise watch lifecycles for directory changes, atomic file replacement, missing-file targets, and unwatch cleanup
Ruslan Nigmatullin ·
2026-03-24 15:52:13 -07:00 -
Finish moving codex exec to app-server (#15424)
This PR completes the conversion of non-interactive `codex exec` to use app server rather than directly using core events and methods. ### Summary - move `codex-exec` off exec-owned `AuthManager` and `ThreadManager` state - route exec bootstrap, resume, and auth refresh through existing app-server paths - replace legacy `codex/event/*` decoding in exec with typed app-server notification handling - update human and JSONL exec output adapters to translate existing app-server notifications only - clean up "app server client" layer by eliminating support for legacy notifications; this is no longer needed - remove exposure of `authManager` and `threadManager` from "app server client" layer ### Testing - `exec` has pretty extensive unit and integration tests already, and these all pass - In addition, I asked Codex to put together a comprehensive manual set of tests to cover all of the `codex exec` functionality (including command-line options), and it successfully generated and ran these tests
Eric Traut ·
2026-03-24 08:51:32 -06:00 -
Remove legacy auth and notification handling from tui_app_server (#15414)
## Summary - remove `tui_app_server` handling for legacy app-server notifications - drop the local ChatGPT auth refresh request path from `tui_app_server` - remove the now-unused refresh response helper from local auth loading Split out of #15106 so the `tui_app_server` cleanup can land separately from the larger `codex-exec` app-server migration.
Eric Traut ·
2026-03-21 15:06:10 -06:00 -
Remove legacy app-server notification handling from tui_app_server (#15390)
As part of moving the TUI onto the app server, we added some temporary handling of some legacy events. We've confirmed that these do not need to be supported, so this PR removes this support from the tui_app_server, allowing for additional simplifications in follow-on PRs. These events are needed only for very old rollouts. None of the other app server clients (IDE extension or app) support these either. ## Summary - stop translating legacy `codex/event/*` notifications inside `tui_app_server` - remove the TUI-side legacy warning and rollback buffering/replay paths that were only fed by those notifications - keep the lower-level app-server and app-server-client legacy event plumbing intact so PR #15106 can rebase on top and handle the remaining exec/lower-layer migration separately
Eric Traut ·
2026-03-21 12:29:33 -06:00 -
Add realtime transcript notification in v2 (#15344)
- emit a typed `thread/realtime/transcriptUpdated` notification from live realtime transcript deltas - expose that notification as flat `threadId`, `role`, and `text` fields instead of a nested transcript array - continue forwarding raw `handoff_request` items on `thread/realtime/itemAdded`, including the accumulated `active_transcript` - update app-server docs, tests, and generated protocol schema artifacts to match the delta-based payloads --------- Co-authored-by: Codex <noreply@openai.com>
Ahmed Ibrahim ·
2026-03-20 15:30:48 -07:00 -
Feat/restore image generation history (#15223)
Restore image generation items in resumed thread history
Won Park ·
2026-03-19 22:57:16 -07:00 -
feat(app-server): add mcpServer/startupStatus/updated notification (#15220)
Exposes the legacy `codex/event/mcp_startup_update` event as an API v2 notification. The legacy event has this shape: ``` #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)] pub struct McpStartupUpdateEvent { /// Server name being started. pub server: String, /// Current startup status. pub status: McpStartupStatus, } #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)] #[serde(rename_all = "snake_case", tag = "state")] #[ts(rename_all = "snake_case", tag = "state")] pub enum McpStartupStatus { Starting, Ready, Failed { error: String }, Cancelled, } ```Owen Lin ·
2026-03-19 15:09:59 -07:00 -
[hooks] use a user message > developer message for prompt continuation (#14867)
## Summary Persist Stop-hook continuation prompts as `user` messages instead of hidden `developer` messages + some requested integration tests This is a followup to @pakrym 's comment in https://github.com/openai/codex/pull/14532 to make sure stop-block continuation prompts match training for turn loops - Stop continuation now writes `<hook_prompt hook_run_id="...">stop hook's user prompt<hook_prompt>` - Introduces quick-xml dependency, though we already indirectly depended on it anyway via syntect - This PR only has about 500 lines of actual logic changes, the rest is tests/schema ## Testing Example run (with a sessionstart hook and 3 stop hooks) - this shows context added by session start, then two stop hooks sending their own additional prompts in a new turn. The model responds with a single message addressing both. Then when that turn ends, the hooks detect that they just ran using `stop_hook_active` and decide not to infinite loop test files for this (unzip, move codex -> .codex): [codex.zip](https://github.com/user-attachments/files/26075806/codex.zip) ``` › cats • Running SessionStart hook: lighting the observatory SessionStart hook (completed) warning: Hi, I'm a session start hook for wizard-tower (startup). hook context: A wimboltine stonpet is an exotic cuisine from hyperspace • Cats are tiny zen wizards, my friend: equal parts nap, mystery, and chaos. If you want, we can talk cat facts, cat breeds, cat names, or build something cat-themed in this repo. • Running Stop hook: checking the tower wards • Running Stop hook: sacking the guards • Running Stop hook: hiring the guards Stop hook (completed) warning: Wizard Tower Stop hook reviewed the completed reply (177 chars). Stop hook (blocked) warning: Wizard Tower Stop hook continuing conversation feedback: cook the stonpet Stop hook (blocked) warning: Wizard Tower Stop hook continuing conversation feedback: eat the cooked stonpet • Stonpet’s cooked, aloha style: flash-seared over a blue quasiflame, glazed with nebula salt, and rested until the hyperspace juices settle. Now we eat with gratitude, my friend. One mindful bite in, and the flavor is pure cosmic surf: smoky, bright, and totally out of this dimension. • Running Stop hook: checking the tower wards • Running Stop hook: sacking the guards • Running Stop hook: hiring the guards Stop hook (completed) warning: Wizard Tower Stop hook reviewed the completed reply (285 chars). Stop hook (completed) warning: Wizard Tower Stop hook saw a second pass and stayed calm to avoid a loop. Stop hook (completed) warning: Wizard Tower Stop hook saw a second pass and stayed calm to avoid a loop. ```
Andrei Eternal ·
2026-03-19 10:53:08 -07:00 -
Add thread/shellCommand to app server API surface (#14988)
This PR adds a new `thread/shellCommand` app server API so clients can implement `!` shell commands. These commands are executed within the sandbox, and the command text and output are visible to the model. The internal implementation mirrors the current TUI `!` behavior. - persist shell command execution as `CommandExecution` thread items, including source and formatted output metadata - bridge live and replayed app-server command execution events back into the existing `tui_app_server` exec rendering path This PR also wires `tui_app_server` to submit `!` commands through the new API.
Eric Traut ·
2026-03-18 23:42:40 -06:00 -
Eric Traut ·
2026-03-18 09:35:05 -06:00 -
feat: add memory citation to agent message (#14821)
Client side to come
jif-oai ·
2026-03-18 10:03:38 +00:00 -
Gate realtime audio interruption logic to v2 (#14984)
- thread the realtime version into conversation start and app-server notifications - keep playback-aware mic gating and playback interruption behavior on v2 only, leaving v1 on the legacy path
Ahmed Ibrahim ·
2026-03-17 15:24:37 -07:00 -
Add device-code onboarding and ChatGPT token refresh to app-server TUI (#14952)
## Summary - add device-code ChatGPT sign-in to `tui_app_server` onboarding and reuse the existing `chatgptAuthTokens` login path - fall back to browser login when device-code auth is unavailable on the server - treat `ChatgptAuthTokens` as an existing signed-in ChatGPT state during onboarding - add a local ChatGPT auth loader for handing local tokens to the app server and serving refresh requests - handle `account/chatgptAuthTokens/refresh` instead of marking it unsupported, including workspace/account mismatch checks - add focused coverage for onboarding success, existing auth handling, local auth loading, and refresh request behavior ## Testing - `cargo test -p codex-tui-app-server` - `just fix -p codex-tui-app-server`
Eric Traut ·
2026-03-17 14:12:12 -06:00 -
fix(tui): restore remote resume and fork history (#14930)
## Problem When the TUI connects to a **remote** app-server (via WebSocket), resume and fork operations lost all conversation history. `AppServerStartedThread` carried only the `SessionConfigured` event, not the full `Thread` snapshot. After resume or fork, the chat transcript was empty — prior turns were silently discarded. A secondary issue: `primary_session_configured` was not cleared on reset, causing stale session state after reconnection. ## Approach: TUI-side only, zero app-server changes The app-server **already returns** the full `Thread` object (with populated `turns: Vec<Turn>`) in its `ThreadStartResponse`, `ThreadResumeResponse`, and `ThreadForkResponse`. The data was always there — the TUI was simply throwing it away. The old `AppServerStartedThread` struct only kept the `SessionConfiguredEvent`, discarding the rich turn history that the server had already provided. This PR fixes the problem entirely within `tui_app_server` (3 files changed, 0 changes to `app-server`, `app-server-protocol`, or any other crate). Rather than modifying the server to send history in a different format or adding a new endpoint, the fix preserves the existing `Thread` snapshot and replays it through the TUI's standard event pipeline — making restored sessions indistinguishable from live ones. ## Solution Add a **thread snapshot replay** path. When the server hands back a `Thread` object (on start, resume, or fork), `restore_started_app_server_thread` converts its historical turns into the same core `Event` sequence the TUI already processes for live interactions, then replays them into the event store so the chat widget renders them. Key changes: - **`AppServerStartedThread` now carries the full `Thread`** — `started_thread_from_{start,resume,fork}_response` clone the thread into the struct alongside the existing `SessionConfiguredEvent`. - **`thread_snapshot_events()`** walks the thread's turns and items, producing `TurnStarted` → `ItemCompleted`* → `TurnComplete`/`TurnAborted` event sequences that the TUI already knows how to render. - **`restore_started_app_server_thread()`** pushes the session event + history events into the thread channel's store, activates the channel, and replays the snapshot — used for initial startup, resume, and fork. - **`primary_session_configured` cleared on reset** to prevent stale session state after reconnection. ## Tradeoffs - **`Thread` is cloned into `AppServerStartedThread`**: The full thread snapshot (including all historical turns) is cloned at startup. For long-lived threads this could be large, but it's a one-time cost and avoids lifetime gymnastics with the response. ## Tests - `restore_started_app_server_thread_replays_remote_history` — end-to-end: constructs a `Thread` with one completed turn, restores it, and asserts user/agent messages appear in the transcript. - `bridges_thread_snapshot_turns_for_resume_restore` — unit: verifies `thread_snapshot_events` produces the correct event sequence for completed and interrupted turns. ## Test plan - [ ] Verify `cargo check -p codex-tui-app-server` passes - [ ] Verify `cargo test -p codex-tui-app-server` passes - [ ] Manual: connect to a remote app-server, resume an existing thread, confirm history renders in the chat widget - [ ] Manual: fork a thread via remote, confirm prior turns appearFelipe Coury ·
2026-03-17 11:16:08 -06:00 -
Fix tui_app_server: ignore duplicate legacy stream events (#14892)
The in-process app-server currently emits both typed `ServerNotification`s and legacy `codex/event/*` notifications for the same live turn updates. `tui_app_server` was consuming both paths, so message deltas and completed items could be enqueued twice and rendered as duplicated output in the transcript. Ignore legacy notifications for event types that already have typed (app server) notification handling, while keeping legacy fallback behavior for events that still only arrive on the old path. This preserves compatibility without duplicating streamed commentary or final agent output. We will remove all of the legacy event handlers over time; they're here only during the short window where we're moving the tui to use the app server.
Eric Traut ·
2026-03-17 00:50:25 -06:00 -
Apply argument comment lint across codex-rs (#14652)
## Why Once the repo-local lint exists, `codex-rs` needs to follow the checked-in convention and CI needs to keep it from drifting. This commit applies the fallback `/*param*/` style consistently across existing positional literal call sites without changing those APIs. The longer-term preference is still to avoid APIs that require comments by choosing clearer parameter types and call shapes. This PR is intentionally the mechanical follow-through for the places where the existing signatures stay in place. After rebasing onto newer `main`, the rollout also had to cover newly introduced `tui_app_server` call sites. That made it clear the first cut of the CI job was too expensive for the common path: it was spending almost as much time installing `cargo-dylint` and re-testing the lint crate as a representative test job spends running product tests. The CI update keeps the full workspace enforcement but trims that extra overhead from ordinary `codex-rs` PRs. ## What changed - keep a dedicated `argument_comment_lint` job in `rust-ci` - mechanically annotate remaining opaque positional literals across `codex-rs` with exact `/*param*/` comments, including the rebased `tui_app_server` call sites that now fall under the lint - keep the checked-in style aligned with the lint policy by using `/*param*/` and leaving string and char literals uncommented - cache `cargo-dylint`, `dylint-link`, and the relevant Cargo registry/git metadata in the lint job - split changed-path detection so the lint crate's own `cargo test` step runs only when `tools/argument-comment-lint/*` or `rust-ci.yml` changes - continue to run the repo wrapper over the `codex-rs` workspace, so product-code enforcement is unchanged Most of the code changes in this commit are intentionally mechanical comment rewrites or insertions driven by the lint itself. ## Verification - `./tools/argument-comment-lint/run.sh --workspace` - `cargo test -p codex-tui-app-server -p codex-tui` - parsed `.github/workflows/rust-ci.yml` locally with PyYAML --- * -> #14652 * #14651
Michael Bolin ·
2026-03-16 16:48:15 -07:00 -
Move TUI on top of app server (parallel code) (#14717)
This PR replicates the `tui` code directory and creates a temporary parallel `tui_app_server` directory. It also implements a new feature flag `tui_app_server` to select between the two tui implementations. Once the new app-server-based TUI is stabilized, we'll delete the old `tui` directory and feature flag.
Eric Traut ·
2026-03-16 10:49:19 -06:00