CLI Refactor to Textual - #686
Merged
Merged
Conversation
…specific ui for diffs, todos, subagents
Eugene Yurtsev (eyurtsev)
approved these changes
Jan 8, 2026
Marcelo5444
pushed a commit
to Marcelo5444/deepagents
that referenced
this pull request
Jul 30, 2026
### Summary Full Textual-based terminal UI replacing the Rich console implementation, with persistent session support via SQLite. ### Features Multi-line input - Enter to send, Ctrl+J for newlines Fuzzy file completion - Type @ to autocomplete files Slash commands - /help, /clear, /quit with autocomplete Bash mode - Type !ls to run shell commands directly Tool approval UI - y/n/a quick keys, Escape to cancel Auto-approve mode - Shift+Tab to toggle Session persistence - Resume conversations with -r ### Usage deepagents # New session deepagents -r # Resume most recent deepagents -r abc123 # Resume specific thread deepagents threads list # List sessions deepagents threads delete ID # Delete session ### Changes Added app.py, textual_adapter.py, and widgets/ for Textual UI Added sessions.py for SQLite-backed thread persistence Removed commands.py, execution.py (legacy Rich CLI) Trimmed ui.py (~1,200 lines of dead code removed) ### Tests 139 tests passing
Mason Daugherty (mdrxy)
added a commit
that referenced
this pull request
Aug 4, 2026
Interrupting a turn, or exiting while a background thread-cache refresh is in flight, no longer strands an unawaited coroutine or an open SQLite handle. --- The unit suite emitted three families of warnings, all at teardown. Two are ours and are fixed here; the third is upstream and is filtered. ### An unawaited turn coroutine `_send_to_agent` handed `run_worker` an already-built `_run_agent_task` coroutine. Textual never runs the work of a worker cancelled before its first event-loop step, so that coroutine was finalized unawaited — a `RuntimeWarning`, and (once interpreter teardown had gone far enough to break the import machinery its cleanup relies on) an unraisable `KeyError: '__import__'`. Passing a callable means the coroutine only exists if the worker actually runs, so there is nothing to strand. Several tests were closing that coroutine by hand purely to silence the warning; those workarounds are gone, and a new test pins the callable contract. **Where it started:** #5196. Handing `run_worker` a coroutine dates all the way back to the original Textual REPL (#686), but it was harmless until something cancelled a worker before its first step. #5196 added the recovery path for exactly that situation along with the tests that exercise it. Its parent commit runs the app test module with zero `never awaited` warnings; #5196 itself produces three. ### An unclosed SQLite handle `aiosqlite` opens the database on its worker thread and hands the raw `sqlite3.Connection` back through a future, recording it on the connection only when the awaiting coroutine resumes. A cancel landing anywhere in that window left the handle unreachable from the cleanup that follows, so the garbage collector reported `ResourceWarning: unclosed database`. There are two halves to the window, and both are now covered: - Cancelled while the worker is still opening, the library has no handle recorded yet, so the cleanup it queues closes nothing. The session module now records the handle from the worker thread the moment the connector returns. - Cancelled after the handle is delivered but before the coroutine resumes, the library clears its own reference before that queued cleanup can run — so it again closes nothing. The guard now also queues an explicit close ahead of the library's cleanup, while the handle is still reachable. Both closes run on the thread that opened the handle, and closing twice is a no-op, so neither disturbs a normal shutdown. `get_checkpointer` builds its connection through the same helper rather than `AsyncSqliteSaver.from_conn_string`, so it gets the same guard. **Where it started:** #5174. The prewarm that reads the session database has existed since #1481, but it ran once at startup, so it had normally finished before anything cancelled it. #5174 re-fires it after every turn, which reliably leaves a session-DB read in flight when a test app exits. Counting handles that `aiosqlite` opened and never closed across the goal-command tests: zero on the parent commit, fourteen on #5174. ### A `typing` deprecation from `google-genai` `google.genai.types` builds a union alias out of `typing._UnionGenericAlias`, which CPython 3.14 deprecates, and it fires at import before any of this package's code runs. This one is not a regression from any change here — `deepagents-code` has been tested on the 3.14 leg since the package was created in #3027, and the warning appears wherever a test imports the Google integration. It is tracked upstream as [googleapis/python-genai#1640](googleapis/python-genai#1640) and still unfixed as of `google-genai` 2.13.0, so it is filtered narrowly (message, category, and module) rather than worked around. Made by [Open SWE](https://openswe.vercel.app/agents/f42590ef-0fe4-0b9f-6880-1c48b28446d5) --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
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
Full Textual-based terminal UI replacing the Rich console implementation, with persistent session support via SQLite.
Features
Multi-line input - Enter to send, Ctrl+J for newlines
Fuzzy file completion - Type @ to autocomplete files
Slash commands - /help, /clear, /quit with autocomplete
Bash mode - Type !ls to run shell commands directly
Tool approval UI - y/n/a quick keys, Escape to cancel
Auto-approve mode - Shift+Tab to toggle
Session persistence - Resume conversations with -r
Usage
deepagents # New session
deepagents -r # Resume most recent
deepagents -r abc123 # Resume specific thread
deepagents threads list # List sessions
deepagents threads delete ID # Delete session
Changes
Added app.py, textual_adapter.py, and widgets/ for Textual UI
Added sessions.py for SQLite-backed thread persistence
Removed commands.py, execution.py (legacy Rich CLI)
Trimmed ui.py (~1,200 lines of dead code removed)
Tests
139 tests passing