feat(tui): start wiring app-server backend behind --app-server#14546
feat(tui): start wiring app-server backend behind --app-server#14546fcoury wants to merge 8 commits intoopenai:etraut/tui-app-server-startupfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR begins integrating the embedded app-server into the TUI by introducing a new runtime event layer (RuntimeEvent / AppServerEvent) so the outer App loop can own app-server work (starting with skills listing) and react to app-server notifications, while updating TUI tests/helpers to unwrap the new event envelope.
Changes:
- Add
RuntimeEvent+AppServerEvent, and updateAppEventSenderto sendRuntimeEvent. - Route
Op::ListSkillsvia an app-server request handled in theAppruntime loop. - Add
--app-serverCLI flag and update tests to receiveRuntimeEvent::App(...).
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| codex-rs/tui/src/status_indicator_widget.rs | Update tests to use RuntimeEvent channels. |
| codex-rs/tui/src/public_widgets/composer_input.rs | Switch internal channel types to RuntimeEvent. |
| codex-rs/tui/src/lib.rs | Thread new app_server CLI flag through to App::run. |
| codex-rs/tui/src/cli.rs | Add --app-server boolean flag. |
| codex-rs/tui/src/chatwidget/tests.rs | Introduce a RuntimeEvent-unwrapping receiver helper for tests and update initialization. |
| codex-rs/tui/src/chatwidget.rs | Add app-server event usage and reroute skills listing into runtime lane; expose use_app_server() and widen refresh_plugin_mentions visibility. |
| codex-rs/tui/src/bottom_pane/status_line_setup.rs | Update tests to use RuntimeEvent channels. |
| codex-rs/tui/src/bottom_pane/skills_toggle_view.rs | Update tests to use RuntimeEvent channels. |
| codex-rs/tui/src/bottom_pane/request_user_input/mod.rs | Update tests to unwrap RuntimeEvent::App(...) via a local helper. |
| codex-rs/tui/src/bottom_pane/mod.rs | Update tests to use RuntimeEvent channels and unwrap RuntimeEvent::App(...). |
| codex-rs/tui/src/bottom_pane/mcp_server_elicitation.rs | Update tests to unwrap RuntimeEvent::App(...) via a local helper. |
| codex-rs/tui/src/bottom_pane/list_selection_view.rs | Update tests to use RuntimeEvent channels and unwrap RuntimeEvent::App(...). |
| codex-rs/tui/src/bottom_pane/feedback_view.rs | Update tests to use RuntimeEvent channels. |
| codex-rs/tui/src/bottom_pane/chat_composer_history.rs | Update tests to use RuntimeEvent channels and unwrap RuntimeEvent::App(...). |
| codex-rs/tui/src/bottom_pane/chat_composer.rs | Update tests to use RuntimeEvent channels and adjust pattern matches accordingly. |
| codex-rs/tui/src/bottom_pane/approval_overlay.rs | Update tests to use RuntimeEvent channels and unwrap RuntimeEvent::App(...). |
| codex-rs/tui/src/bottom_pane/app_link_view.rs | Update tests to use RuntimeEvent channels and unwrap RuntimeEvent::App(...). |
| codex-rs/tui/src/app_event_sender.rs | Change sender to emit RuntimeEvent and add send_app_server. |
| codex-rs/tui/src/app_event.rs | Introduce RuntimeEvent and AppServerEvent enums. |
| codex-rs/tui/src/app.rs | Handle RuntimeEvent in main loop; issue app-server skills/list; react to app/list/updated notifications. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
63081a2 to
fe09b3d
Compare
Add a TUI `--app-server` flag and use it to route `ListSkills`
through the embedded app-server while leaving the existing core
path unchanged when the flag is off.
Keep the change surface small by preserving the base `AppEvent`
model, intercepting only `AppEvent::CodexOp(Op::ListSkills { .. })`
in the app loop, and normalizing the app-server response back into
the existing skills UI update path.
Clarify the intent behind the --app-server flag, the type conversion helpers, and the dual-channel routing in request_skills_list.
Route app-server `skills/list` refreshes through a detached request handle so the main TUI event loop stays responsive while scans run. The completed result is fed back through an app event, preserving the existing UI update flow. Expand empty skills cwd requests against the active chat session cwd instead of the embedded app-server startup cwd. This keeps skill discovery aligned with the visible thread when users switch to sessions rooted in other directories.
Restore the CLI `app_server` flag flow after the rebase by passing the embedded app-server client and the boolean flag as distinct values into `App::run`. Drop stale imports from `tui/src/app.rs` now that app-server event handling lives in the adapter module. This keeps the rebased branch building cleanly.
1a20ad1 to
b8dbe64
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b8dbe649a4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Generate a fresh app-server request ID for each detached `skills/list` RPC issued by the TUI when `--app-server` is enabled. This avoids duplicate in-flight JSON-RPC request IDs during overlapping skills refreshes, which could otherwise cause the embedded app-server to reject a request and surface an avoidable TUI error.
Carry the requested cwd set through `SkillsListLoaded` and only apply a `skills/list` response when it still matches the active chat cwd. This prevents a delayed app-server response for an old session cwd from clearing or replacing the visible skills after the user switches threads. Add a regression test covering the stale-response case.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cc27416c00
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if !requested_cwds.contains(¤t_cwd) { | ||
| return Ok(()); | ||
| } |
There was a problem hiding this comment.
Drop stale skills responses for the same cwd
skills/list calls are launched on detached tasks, so multiple requests for the same cwd can complete out of order; however this handler only rejects responses when the cwd changed, and otherwise applies whatever arrives last. In --app-server mode that allows an older response to overwrite a newer one (for example after rapid refreshes or enable/disable flows), leaving the UI with stale skill metadata until another reload. Consider tracking request recency (e.g., request_id/counter) and ignoring older completions even when cwd matches.
Useful? React with 👍 / 👎.
etraut-openai
left a comment
There was a problem hiding this comment.
I started to review. This was a good experiment because the ListSkills doesn't involve any events, so it's relatively straightforward. However, this highlights how challenging it's going to be to retain both legacy and app server code paths — even in the short term. It makes me question whether a feature flag is the right approach here.
I'm going to do an experiment and create a separate parallel PR that explores the approach where we don't make it conditional and just cut over to the new app server path so we can compare.
codex-rs/tui/src/app.rs
Outdated
| /// below; these are intentionally field-by-field rather than derived to | ||
| /// keep the two protocol crates decoupled and make field divergence a | ||
| /// compile error. | ||
| fn into_core_skills_list_entry( |
There was a problem hiding this comment.
It looks like it takes a lot of code to convert from app server to core data structures in this case. We'll eventually want the TUI to consume app server data structures directly, so maybe it's better to convert in the other direction? Or do you think we should do that as a separate (later) step?
There was a problem hiding this comment.
I have another branch where I do that. We can discuss the implementation. In this particular PR I wanted to do as little as possible to get this one action migrated.
Track a per-cwd `skills/list` generation in the TUI and carry that context through `SkillsListLoaded` completions from the embedded app-server path. This prevents delayed same-cwd or old-cwd responses from overwriting newer skill state after overlapping refreshes, and adds regressions for both stale-response cases.
@etraut-openai I share your concern. I think the clean cut is the right approach, but even that is not without its problems. If you check the last version of my PR, there are certain features that just can't be migrated (or not unless we implement them on AppServer). The one that always comes to mind is the Another case is the shutdown. If you execute your branch today and hit This is because we are running the local shutdown, which is fast but also shutdown the embedded app-server runtime. And this process drains the background tasks, waiting up to 10 minutes. I think we need to explore moving all the remaining commands, even This is a list of items we had migrated vs decided not to migrate, mostly to not change app-server on PR #14018. Actions kept local in TUIHandled locally (no app-server RPC needed):
Forwarded directly to
Deferred (emit local warning, not functional):
Notifications staying on the legacy event bridgeThese notifications are not on the typed app-server path:
Three server notifications use the typed app-server path today:
Everything else flows through the legacy Other non-migrated pieces
|
Move the temporary app-server `skills/list` request and response adaptation helpers out of `tui/src/app.rs` into `tui/src/app/skills.rs`. This keeps the main app loop focused on orchestration, makes the hybrid migration code easier to isolate, and removes a stale `ListSkills`-specific comment from the `use_app_server` flag docs.
Problem
The TUI talks directly to codex-core, duplicating business logic that the app-server already provides to other surfaces like the VSCode extension. This means behavior can diverge between surfaces, and any fix or enhancement must be replicated in both the TUI and the app-server independently. The goal is to migrate the TUI onto the app-server so all clients share a single implementation. Skills loading (
ListSkills) is the first operation to move — it's self-contained, has clear request/response semantics, and exercises the full conversion pipeline — making it a good proving ground for the pattern the remaining operations will follow.Mental model
There are now two skill-loading channels, selected by the
--app-serverCLI flag:ChatWidgetsendsOp::ListSkillsover the codex-core op channel. Core handles the request end-to-end and firesEventMsg::ListSkillsResponseback.ChatWidgetwrapsOp::ListSkillsinsideAppEvent::CodexOp(…)and sends it up to theAppevent loop.App::runintercepts it, expands emptycwdsagainst the active chat session cwd, kicks off a detached typedskills/listRPC against the embedded app-server, converts the response from app-server protocol types into core protocol types, and pushes the result into the sameset_skills_from_response/refresh_plugin_mentionspath the legacy flow uses.The decision point is
ChatWidget::request_skills_list, which inspectsuse_app_serverand routes to the appropriate channel. TheAppevent loop'sselect!arm pattern-matches onAppEvent::CodexOp(Op::ListSkills { .. })only whenuse_app_serveris true; all other events fall through to the existinghandle_eventhandler.Non-goals
ListSkillsthrough the app-server. This is the first gated operation; others will follow the same pattern.Tradeoffs
into_core_*boilerplate (8 functions): These could be derived with a macro or eliminated by unifying the protocol types, but field-by-field mapping keeps the two protocol crates decoupled and makes divergence between them compile-time visible.RequestId::Integer(1): The detachedskills/listpath still uses a static request ID. That is acceptable for the current narrowly scoped migration, but it is an explicit limitation and should become a counter before this pattern is reused more broadly.per_cwd_extra_user_roots: None: The TUI does not yet support per-CWD extra roots. This is deferred, not forgotten.Architecture
App-server adapter (
app_server_adapter.rs)A transitional adapter module on
Appthat drains the app-server event stream during the hybrid migration period. It handlesLagged,ServerNotification, andLegacyNotificationevents, and rejects anyServerRequestwith a JSON-RPC -32000 error since the TUI does not yet handle server-initiated requests. This module should shrink and disappear as more flows move onto the app-server surface.Files changed outside
tui/The only non-TUI code touched by this branch is:
app-server-client/src/lib.rs— ExtendedInProcessAppServerClientwith typed request/response plumbing and anInProcessAppServerRequesterhandle for making RPC calls from spawned tasks.Observability
skills/listRPC failures surface ascolor_eyreerrors that propagate up and terminate the app — no silent swallowing.emit_skill_load_warningson the app-server path identically to the legacy path.Tests
list_skills_without_app_server_uses_core_op_channel— verifiesSkillsUpdateAvailableroutes through the op channel whenuse_app_server=false, and nothing appears on the app-event channel.list_skills_with_app_server_uses_app_event_channel— verifies the inverse: op channel stays empty, app-event channel receives the wrapped op.effective_skills_list_cwds_uses_active_chat_widget_cwd_when_request_empty— verifies the app-server path uses the active session cwd rather than the original launch cwd when the request does not specifycwds.effective_skills_list_cwds_preserves_explicit_cwds— verifies explicitly provided cwd values are forwarded unchanged.parses_app_server_flag— confirms clap parses--app-server.use_app_server: falsetoChatWidgetInitconstructors, confirming no behavioral change in the default path.