fix(code): support plain exit quit command (#4543)

Typing `exit` in dcode now quits the app without needing a slash
command.

Made by [Open
SWE](https://openswe.vercel.app/agents/211b4c5d-e31a-1941-4047-9412e6dc5319)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
This commit is contained in:
Johannes du Plessis
2026-07-12 23:15:51 -07:00
committed by GitHub
parent 8cf57aca9f
commit e6f10a1490
2 changed files with 111 additions and 3 deletions
+19 -3
View File
@@ -7588,6 +7588,16 @@ class DeepAgentsApp(App):
self.push_screen(dependency_screen)
return await result_future
@staticmethod
def _is_exit_keyword(value: str, mode: InputMode) -> bool:
"""Return whether `value` is the bare `exit` keyword in normal mode.
Matches case-insensitively and ignores surrounding whitespace. Only
`normal` mode qualifies, so `exit` typed in shell or command mode is
routed normally rather than quitting the app.
"""
return mode == "normal" and value.lower().strip() == "exit"
def _can_bypass_queue(self, value: str) -> bool:
"""Check if a slash command can skip the message queue.
@@ -7663,10 +7673,9 @@ class DeepAgentsApp(App):
# COMMANDS and so carry no bypass tier. Both must run even when the
# app is busy or wedged, so neither sits behind the queue.
always_bypass = ALWAYS_IMMEDIATE | HIDDEN_COMMANDS
normalized = value.lower().strip()
if force_bypass or (
mode == "command" and value.lower().strip() in always_bypass
):
if force_bypass or (mode == "command" and normalized in always_bypass):
await self._process_message(value, mode)
return
@@ -7717,6 +7726,13 @@ class DeepAgentsApp(App):
await dispatch_hook("user.prompt", {})
# A bare `exit` quits the app (REPL convention), mirroring `/quit`.
# Gated to this interactive path only, so external/scripted callers
# (on_external_input) can still send the literal "exit" to the agent.
if self._is_exit_keyword(value, mode):
self.exit()
return
await self._submit_input(value, mode)
async def _dismiss_startup_tip(self) -> None:
+92
View File
@@ -11884,6 +11884,76 @@ class TestSlashCommandBypass:
exit_mock.assert_called_once()
assert len(app._pending_messages) == 0
async def test_exit_keyword_exits_from_normal_mode(self) -> None:
"""Plain exit quits from normal mode, case-insensitive and whitespace-stripped.
The ` EXIT ` literal is the only coverage of the `.lower().strip()`
normalization; keep the padding and casing when editing this test.
"""
app = DeepAgentsApp()
async with app.run_test() as pilot:
await pilot.pause()
with patch.object(app, "exit") as exit_mock:
app.post_message(ChatInput.Submitted(" EXIT ", "normal"))
await pilot.pause()
exit_mock.assert_called_once()
assert len(app._pending_messages) == 0
async def test_exit_keyword_bypasses_queue_when_agent_running(self) -> None:
"""Plain exit should quit immediately even when the agent is running."""
app = DeepAgentsApp()
async with app.run_test() as pilot:
await pilot.pause()
app._agent_running = True
with patch.object(app, "exit") as exit_mock:
app.post_message(ChatInput.Submitted("exit", "normal"))
await pilot.pause()
exit_mock.assert_called_once()
assert len(app._pending_messages) == 0
async def test_exit_keyword_bypasses_thread_switching(self) -> None:
"""Plain exit should quit even during a thread switch."""
app = DeepAgentsApp()
async with app.run_test() as pilot:
await pilot.pause()
app._thread_switching = True
with patch.object(app, "exit") as exit_mock:
app.post_message(ChatInput.Submitted("exit", "normal"))
await pilot.pause()
exit_mock.assert_called_once()
assert len(app._pending_messages) == 0
async def test_exit_keyword_requires_exact_match(self) -> None:
"""Other messages containing exit should still go to the agent."""
app = DeepAgentsApp()
async with app.run_test() as pilot:
await pilot.pause()
with (
patch.object(app, "exit") as exit_mock,
patch.object(
app, "_handle_user_message", new_callable=AsyncMock
) as handler,
):
app.post_message(ChatInput.Submitted("exit now", "normal"))
await pilot.pause()
exit_mock.assert_not_called()
handler.assert_awaited_once_with("exit now")
def test_exit_keyword_only_matches_normal_mode(self) -> None:
"""`exit` quits only in normal mode; shell/command input is untouched."""
assert DeepAgentsApp._is_exit_keyword("exit", "normal") is True
assert DeepAgentsApp._is_exit_keyword("exit", "shell") is False
assert DeepAgentsApp._is_exit_keyword("exit", "shell_incognito") is False
assert DeepAgentsApp._is_exit_keyword("exit", "command") is False
async def test_force_clear_bypasses_queue_when_agent_running(self) -> None:
"""/force-clear should process immediately when agent is running."""
app = DeepAgentsApp()
@@ -11957,6 +12027,28 @@ class TestSlashCommandBypass:
QueuedMessage(text="next task", mode="normal")
]
async def test_external_prompt_exit_is_forwarded(self) -> None:
"""An external `exit` prompt should be sent to the agent, not quit."""
app = DeepAgentsApp()
async with app.run_test() as pilot:
await pilot.pause()
with (
patch.object(app, "exit") as exit_mock,
patch.object(
app, "_handle_user_message", new_callable=AsyncMock
) as handler,
):
app.post_message(
ExternalInput(
ExternalEvent(kind="prompt", payload="exit", source="test")
)
)
await pilot.pause()
exit_mock.assert_not_called()
handler.assert_awaited_once_with("exit")
async def test_version_executes_during_connecting(self) -> None:
"""/version should process immediately when only connecting."""
app = DeepAgentsApp()