1+ from types import SimpleNamespace
2+ from typing import cast
3+
14from kosong .message import Message
25from rich .text import Text
36
47import kimi_cli .ui .shell as shell_module
8+ from kimi_cli .soul import Soul
59from kimi_cli .ui .shell import Shell
610from kimi_cli .ui .shell .echo import render_user_echo
711from kimi_cli .ui .shell .prompt import PromptMode , UserInput
8- from kimi_cli .utils .slashcmd import SlashCommandCall
12+ from kimi_cli .utils .slashcmd import SlashCommand , SlashCommandCall
913from kimi_cli .wire .types import AudioURLPart , ImageURLPart , TextPart , VideoURLPart
1014
1115
@@ -18,6 +22,23 @@ def _make_user_input(command: str, *, mode: PromptMode = PromptMode.AGENT) -> Us
1822 )
1923
2024
25+ def _noop (app : object , args : str ) -> None :
26+ pass
27+
28+
29+ def _make_shell (* command_names : str ) -> Shell :
30+ commands = [
31+ SlashCommand (
32+ name = name ,
33+ description = f"{ name } command" ,
34+ func = _noop ,
35+ aliases = [],
36+ )
37+ for name in command_names
38+ ]
39+ return Shell (cast (Soul , SimpleNamespace (available_slash_commands = commands )))
40+
41+
2142def test_echo_agent_input_prints_stringified_user_message (monkeypatch ) -> None :
2243 printed : list [Text ] = []
2344 monkeypatch .setattr (shell_module .console , "print" , lambda text : printed .append (text ))
@@ -105,16 +126,48 @@ def test_render_user_echo_preserves_mixed_content_order() -> None:
105126
106127
107128def test_should_echo_agent_input_for_plain_agent_message () -> None :
108- assert Shell ._should_echo_agent_input (_make_user_input ("hi" )) is True
129+ shell = _make_shell ()
130+ assert shell ._should_echo_agent_input (_make_user_input ("hi" )) is True
131+
132+
133+ def test_should_not_echo_agent_input_for_exit_commands () -> None :
134+ shell = _make_shell ()
135+ assert shell ._should_echo_agent_input (_make_user_input ("exit" )) is False
136+ assert shell ._should_echo_agent_input (_make_user_input ("/exit" )) is False
137+ assert shell ._should_echo_agent_input (_make_user_input ("/quit" )) is False
138+
109139
140+ def test_should_echo_agent_input_for_skill_slash_commands () -> None :
141+ shell = _make_shell ("skill:demo" )
142+ assert shell ._should_echo_agent_input (_make_user_input ("/skill:demo" )) is True
143+ assert shell ._should_echo_agent_input (_make_user_input ("/skill:demo 修一下登录" )) is True
110144
111- def test_should_not_echo_agent_input_for_exit_or_slash_commands () -> None :
112- assert Shell ._should_echo_agent_input (_make_user_input ("exit" )) is False
113- assert Shell ._should_echo_agent_input (_make_user_input ("/exit" )) is False
114- assert Shell ._should_echo_agent_input (_make_user_input ("/help" )) is False
145+
146+ def test_should_echo_unregistered_skill_like_slash_commands () -> None :
147+ shell = _make_shell ("skill:demo" )
148+ assert shell ._should_echo_agent_input (_make_user_input ("/skill:not-found" )) is True
149+
150+
151+ def test_should_echo_flow_slash_commands () -> None :
152+ shell = _make_shell ("flow:demo" )
153+ assert shell ._should_echo_agent_input (_make_user_input ("/flow:demo" )) is True
154+ assert shell ._should_echo_agent_input (_make_user_input ("/flow:demo 执行一下" )) is True
155+
156+
157+ def test_should_echo_unregistered_flow_like_slash_commands () -> None :
158+ shell = _make_shell ("flow:demo" )
159+ assert shell ._should_echo_agent_input (_make_user_input ("/flow:not-found" )) is True
160+
161+
162+ def test_should_not_echo_agent_input_for_non_skill_slash_commands () -> None :
163+ shell = _make_shell ("skill:demo" )
164+ assert shell ._should_echo_agent_input (_make_user_input ("/help" )) is False
165+ assert shell ._should_echo_agent_input (_make_user_input ("/theme dark" )) is False
166+ assert shell ._should_echo_agent_input (_make_user_input ("/clear" )) is False
115167
116168
117169def test_hidden_slash_in_placeholder_is_not_treated_as_local_command () -> None :
170+ shell = _make_shell ()
118171 user_input = UserInput (
119172 mode = PromptMode .AGENT ,
120173 command = "[Pasted text #1 +3 lines]" ,
@@ -124,7 +177,7 @@ def test_hidden_slash_in_placeholder_is_not_treated_as_local_command() -> None:
124177
125178 assert Shell ._should_exit_input (user_input ) is False
126179 assert Shell ._agent_slash_command_call (user_input ) is None
127- assert Shell ._should_echo_agent_input (user_input ) is True
180+ assert shell ._should_echo_agent_input (user_input ) is True
128181
129182
130183def test_should_exit_input_is_mode_independent_for_visible_exit_commands () -> None :
@@ -135,6 +188,7 @@ def test_should_exit_input_is_mode_independent_for_visible_exit_commands() -> No
135188
136189
137190def test_visible_slash_command_keeps_expanded_placeholder_args () -> None :
191+ shell = _make_shell ()
138192 user_input = UserInput (
139193 mode = PromptMode .AGENT ,
140194 command = "/echo [Pasted text #1 +3 lines]" ,
@@ -147,8 +201,43 @@ def test_visible_slash_command_keeps_expanded_placeholder_args() -> None:
147201 args = "line1\n line2\n line3" ,
148202 raw_input = "/echo line1\n line2\n line3" ,
149203 )
150- assert Shell ._should_echo_agent_input (user_input ) is False
204+ assert shell ._should_echo_agent_input (user_input ) is False
205+
206+
207+ def test_visible_skill_slash_command_keeps_expanded_placeholder_args_and_still_echoes () -> None :
208+ shell = _make_shell ("skill:demo" )
209+ user_input = UserInput (
210+ mode = PromptMode .AGENT ,
211+ command = "/skill:demo [Pasted text #1 +3 lines]" ,
212+ resolved_command = "/skill:demo line1\n line2\n line3" ,
213+ content = [TextPart (text = "line1\n line2\n line3" )],
214+ )
215+
216+ assert Shell ._agent_slash_command_call (user_input ) == SlashCommandCall (
217+ name = "skill:demo" ,
218+ args = "line1\n line2\n line3" ,
219+ raw_input = "/skill:demo line1\n line2\n line3" ,
220+ )
221+ assert shell ._should_echo_agent_input (user_input ) is True
222+
223+
224+ def test_visible_flow_slash_command_keeps_expanded_placeholder_args_and_still_echoes () -> None :
225+ shell = _make_shell ("flow:demo" )
226+ user_input = UserInput (
227+ mode = PromptMode .AGENT ,
228+ command = "/flow:demo [Pasted text #1 +3 lines]" ,
229+ resolved_command = "/flow:demo line1\n line2\n line3" ,
230+ content = [TextPart (text = "line1\n line2\n line3" )],
231+ )
232+
233+ assert Shell ._agent_slash_command_call (user_input ) == SlashCommandCall (
234+ name = "flow:demo" ,
235+ args = "line1\n line2\n line3" ,
236+ raw_input = "/flow:demo line1\n line2\n line3" ,
237+ )
238+ assert shell ._should_echo_agent_input (user_input ) is True
151239
152240
153241def test_should_not_echo_non_agent_input () -> None :
154- assert Shell ._should_echo_agent_input (_make_user_input ("ls" , mode = PromptMode .SHELL )) is False
242+ shell = _make_shell ()
243+ assert shell ._should_echo_agent_input (_make_user_input ("ls" , mode = PromptMode .SHELL )) is False
0 commit comments