comate-cli 0.5.8__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {comate_cli-0.5.8 → comate_cli-0.6.0}/PKG-INFO +1 -1
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/history_printer.py +13 -2
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/tui.py +1 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/tui_parts/history_sync.py +12 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/pyproject.toml +1 -1
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_history_printer.py +85 -4
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_skills_slash_command.py +47 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/uv.lock +2 -2
- {comate_cli-0.5.8 → comate_cli-0.6.0}/.gitignore +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/README.md +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/__init__.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/__main__.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/main.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/mcp_cli.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/animations.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/app.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/codenames.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/error_display.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/event_renderer.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/figures.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/logging_adapter.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/logo.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/markdown_render.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/models.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/path_context_hint.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/__init__.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/components/__init__.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/components/detail_view.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/components/plugin_list.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/components/search_box.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/components/tab_bar.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/marketplace_install_view.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/plugin_picker.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/tabs/__init__.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/tabs/errors_tab.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/preflight.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/question_view.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/rewind_store.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/session_view.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/slash_commands.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/status_bar.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/tool_view.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/docs/superpowers/plans/2026-04-03-phrase-shuffle.md +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/docs/superpowers/specs/2026-04-01-conditional-diff-subtitle-design.md +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/docs/superpowers/specs/2026-04-03-phrase-shuffle-design.md +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/conftest.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_animator_shuffle.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_app_print_mode.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_app_shutdown.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_app_usage_line.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_completion_status_panel.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_context_command.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_custom_slash_commands.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_discover_tab.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_errors_tab.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_event_renderer.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_event_renderer_boundary.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_event_renderer_e2e.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_event_renderer_streaming.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_format_error.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_handle_error.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_history_sync.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_input_history.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_installed_tab.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_interrupt_exit_semantics.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_logging_adapter.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_logo.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_main_args.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_markdown_render.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_marketplaces_tab.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_mcp_cli.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_mcp_slash_command.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_path_context_hint.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_plugin_slash_commands.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_plugin_tui_components.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_preflight.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_preflight_copilot.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_question_view.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_rewind_store.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_slash_completer.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_status_bar.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_status_bar_transient.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_task_panel_rendering.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_task_poll.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_tool_view.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_tui_esc_queue.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_tui_paste_placeholder.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_tui_queue_preview.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_tui_queue_sdk_source.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_tui_split_invariance.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_tui_team_messages.py +0 -0
- {comate_cli-0.5.8 → comate_cli-0.6.0}/tests/test_update_check.py +0 -0
|
@@ -87,6 +87,7 @@ def render_history_group(
|
|
|
87
87
|
terminal_width: int,
|
|
88
88
|
render_markdown_to_plain: Callable[..., str],
|
|
89
89
|
prev_was_assistant: bool = False,
|
|
90
|
+
prev_was_thinking: bool = False,
|
|
90
91
|
assume_run_continues_at_tail: bool = False,
|
|
91
92
|
) -> Group | None:
|
|
92
93
|
if not entries:
|
|
@@ -98,6 +99,7 @@ def render_history_group(
|
|
|
98
99
|
# printer 这一层被识别为"连续 assistant entry 的 run"——只在 run 起始处加 `●`,
|
|
99
100
|
# 后续行用 2 空格缩进保持视觉对齐。`prev_was_assistant` 把 run 状态跨 drain 传过来。
|
|
100
101
|
renderables: list[Any] = []
|
|
102
|
+
needs_leading_gap_after_thinking = prev_was_thinking
|
|
101
103
|
for idx, entry in enumerate(entries):
|
|
102
104
|
is_assistant = entry.entry_type == "assistant"
|
|
103
105
|
in_run_continuation = is_assistant and prev_was_assistant
|
|
@@ -116,10 +118,15 @@ def render_history_group(
|
|
|
116
118
|
):
|
|
117
119
|
continue
|
|
118
120
|
|
|
119
|
-
# Thinking entries:
|
|
121
|
+
# Thinking entries: 灰色显示,无前缀。thinking 流式文本里的空白行
|
|
122
|
+
# 只属于模型输出内容,不应把同一个 thinking block 视觉上切散。
|
|
120
123
|
if entry.entry_type == "thinking":
|
|
121
124
|
content = str(entry.text)
|
|
122
|
-
content_lines = content.splitlines()
|
|
125
|
+
content_lines = [line for line in content.splitlines() if line.strip()]
|
|
126
|
+
if not content_lines:
|
|
127
|
+
prev_was_assistant = False
|
|
128
|
+
continue
|
|
129
|
+
needs_leading_gap_after_thinking = False
|
|
123
130
|
line_text = Text()
|
|
124
131
|
line_text.append(" ", style="dim")
|
|
125
132
|
line_text.append(content_lines[0], style="dim")
|
|
@@ -138,6 +145,10 @@ def render_history_group(
|
|
|
138
145
|
prev_was_assistant = False
|
|
139
146
|
continue
|
|
140
147
|
|
|
148
|
+
if needs_leading_gap_after_thinking:
|
|
149
|
+
renderables.append(Text(""))
|
|
150
|
+
needs_leading_gap_after_thinking = False
|
|
151
|
+
|
|
141
152
|
# Elapsed entries: 灰色显示,无前缀
|
|
142
153
|
if entry.entry_type == "elapsed":
|
|
143
154
|
line_text = Text()
|
|
@@ -270,6 +270,7 @@ class TerminalAgentTUI(
|
|
|
270
270
|
# 拆成 N 条 entry,跨 drain 时需要用这个 seed 让 history_printer 把 run 续上
|
|
271
271
|
# (否则每次 drain 都重新加一个 `●`)。
|
|
272
272
|
self._last_drained_was_assistant = False
|
|
273
|
+
self._last_drained_thinking_tail_needs_gap = False
|
|
273
274
|
self._render_dirty = True
|
|
274
275
|
self._diff_panel_visible = False
|
|
275
276
|
self._diff_panel_scroll = 0
|
|
@@ -30,12 +30,14 @@ class HistorySyncMixin:
|
|
|
30
30
|
# message 拆成 N 条 entry,需要 seed 让 history_printer 把 run 续上)。class-level
|
|
31
31
|
# default 让所有 host(含测试 dummy)无需显式初始化也能用。
|
|
32
32
|
_last_drained_was_assistant: bool = False
|
|
33
|
+
_last_drained_thinking_tail_needs_gap: bool = False
|
|
33
34
|
|
|
34
35
|
async def _replay_scrollback_after_rewind(self) -> None:
|
|
35
36
|
"""清屏并重建 scrollback:Logo + 当前会话历史。"""
|
|
36
37
|
self._renderer.reset_history_view()
|
|
37
38
|
self._printed_history_index = 0
|
|
38
39
|
self._last_drained_was_assistant = False
|
|
40
|
+
self._last_drained_thinking_tail_needs_gap = False
|
|
39
41
|
|
|
40
42
|
def _clear_and_print_logo() -> None:
|
|
41
43
|
out = sys.__stdout__ or sys.stdout
|
|
@@ -289,11 +291,16 @@ class HistorySyncMixin:
|
|
|
289
291
|
terminal_width=self._terminal_width(),
|
|
290
292
|
render_markdown_to_plain=render_markdown_to_plain,
|
|
291
293
|
prev_was_assistant=self._last_drained_was_assistant,
|
|
294
|
+
prev_was_thinking=self._last_drained_thinking_tail_needs_gap,
|
|
292
295
|
assume_run_continues_at_tail=getattr(self, "_busy", False),
|
|
293
296
|
)
|
|
294
297
|
if group is None:
|
|
295
298
|
return
|
|
299
|
+
busy = getattr(self, "_busy", False)
|
|
296
300
|
self._last_drained_was_assistant = pending[-1].entry_type == "assistant"
|
|
301
|
+
self._last_drained_thinking_tail_needs_gap = (
|
|
302
|
+
pending[-1].entry_type == "thinking" and busy
|
|
303
|
+
)
|
|
297
304
|
|
|
298
305
|
def _print() -> None:
|
|
299
306
|
width = self._terminal_width()
|
|
@@ -336,11 +343,16 @@ class HistorySyncMixin:
|
|
|
336
343
|
terminal_width=self._terminal_width(),
|
|
337
344
|
render_markdown_to_plain=render_markdown_to_plain,
|
|
338
345
|
prev_was_assistant=self._last_drained_was_assistant,
|
|
346
|
+
prev_was_thinking=self._last_drained_thinking_tail_needs_gap,
|
|
339
347
|
assume_run_continues_at_tail=getattr(self, "_busy", False),
|
|
340
348
|
)
|
|
341
349
|
if group is None:
|
|
342
350
|
return
|
|
351
|
+
busy = getattr(self, "_busy", False)
|
|
343
352
|
self._last_drained_was_assistant = pending[-1].entry_type == "assistant"
|
|
353
|
+
self._last_drained_thinking_tail_needs_gap = (
|
|
354
|
+
pending[-1].entry_type == "thinking" and busy
|
|
355
|
+
)
|
|
344
356
|
print_history_group_sync(console, group)
|
|
345
357
|
|
|
346
358
|
def _pending_history_entries(self) -> list[HistoryEntry]:
|
|
@@ -299,11 +299,12 @@ class TestAssumeRunContinuesAtTail(unittest.TestCase):
|
|
|
299
299
|
assume_run_continues_at_tail=True 让 history_printer 把 batch 末尾的 assistant
|
|
300
300
|
entry 当作 "run 仍在续" 处理,不加 trailing 空行。"""
|
|
301
301
|
|
|
302
|
-
def
|
|
302
|
+
def _render_str(
|
|
303
303
|
self,
|
|
304
304
|
entries: list[HistoryEntry],
|
|
305
305
|
*,
|
|
306
306
|
prev_was_assistant: bool = False,
|
|
307
|
+
prev_was_thinking: bool = False,
|
|
307
308
|
assume_run_continues_at_tail: bool = False,
|
|
308
309
|
) -> list[str]:
|
|
309
310
|
console = Console(file=None, force_terminal=True, width=120)
|
|
@@ -311,13 +312,32 @@ class TestAssumeRunContinuesAtTail(unittest.TestCase):
|
|
|
311
312
|
console, entries, terminal_width=120,
|
|
312
313
|
render_markdown_to_plain=_identity_md,
|
|
313
314
|
prev_was_assistant=prev_was_assistant,
|
|
315
|
+
prev_was_thinking=prev_was_thinking,
|
|
314
316
|
assume_run_continues_at_tail=assume_run_continues_at_tail,
|
|
315
317
|
)
|
|
316
318
|
if group is None:
|
|
317
|
-
return
|
|
319
|
+
return ""
|
|
318
320
|
with console.capture() as capture:
|
|
319
321
|
console.print(group, soft_wrap=True)
|
|
320
|
-
return _strip_ansi(capture.get())
|
|
322
|
+
return _strip_ansi(capture.get())
|
|
323
|
+
|
|
324
|
+
def _render_lines(
|
|
325
|
+
self,
|
|
326
|
+
entries: list[HistoryEntry],
|
|
327
|
+
*,
|
|
328
|
+
prev_was_assistant: bool = False,
|
|
329
|
+
prev_was_thinking: bool = False,
|
|
330
|
+
assume_run_continues_at_tail: bool = False,
|
|
331
|
+
) -> list[str]:
|
|
332
|
+
output = self._render_str(
|
|
333
|
+
entries,
|
|
334
|
+
prev_was_assistant=prev_was_assistant,
|
|
335
|
+
prev_was_thinking=prev_was_thinking,
|
|
336
|
+
assume_run_continues_at_tail=assume_run_continues_at_tail,
|
|
337
|
+
)
|
|
338
|
+
if output == "":
|
|
339
|
+
return []
|
|
340
|
+
return output.split("\n")
|
|
321
341
|
|
|
322
342
|
def test_default_off_preserves_trailing_blank_line(self) -> None:
|
|
323
343
|
"""默认 False:保持原行为,末尾 assistant entry 后加 trailing 空行。"""
|
|
@@ -394,12 +414,73 @@ class TestAssumeRunContinuesAtTail(unittest.TestCase):
|
|
|
394
414
|
first_idx = lines.index(" line one")
|
|
395
415
|
self.assertEqual(lines[first_idx + 1], " line two", msg=lines)
|
|
396
416
|
|
|
417
|
+
def test_thinking_internal_blank_lines_are_collapsed(self) -> None:
|
|
418
|
+
entries = [
|
|
419
|
+
HistoryEntry(
|
|
420
|
+
entry_type="thinking",
|
|
421
|
+
text="line one\n\nline two\n \nline three\n",
|
|
422
|
+
),
|
|
423
|
+
]
|
|
424
|
+
lines = self._render_lines(entries)
|
|
425
|
+
first_idx = lines.index(" line one")
|
|
426
|
+
self.assertEqual(
|
|
427
|
+
lines[first_idx : first_idx + 3],
|
|
428
|
+
[" line one", " line two", " line three"],
|
|
429
|
+
msg=lines,
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
def test_thinking_keeps_boundary_blank_lines(self) -> None:
|
|
433
|
+
entries = [
|
|
434
|
+
HistoryEntry(entry_type="user", text="hello"),
|
|
435
|
+
HistoryEntry(entry_type="thinking", text="line one\n\nline two\n"),
|
|
436
|
+
HistoryEntry(entry_type="assistant", text="hi\n"),
|
|
437
|
+
]
|
|
438
|
+
lines = self._render_lines(entries)
|
|
439
|
+
user_idx = lines.index("> hello")
|
|
440
|
+
thinking_idx = lines.index(" line one")
|
|
441
|
+
assistant_idx = lines.index("⏺ hi")
|
|
442
|
+
|
|
443
|
+
self.assertEqual(lines[user_idx + 1], "", msg=lines)
|
|
444
|
+
self.assertEqual(thinking_idx, user_idx + 2, msg=lines)
|
|
445
|
+
self.assertEqual(lines[thinking_idx + 1], " line two", msg=lines)
|
|
446
|
+
self.assertEqual(lines[thinking_idx + 2], "", msg=lines)
|
|
447
|
+
self.assertEqual(assistant_idx, thinking_idx + 3, msg=lines)
|
|
448
|
+
|
|
397
449
|
def test_assume_tail_suppresses_trailing_blank_for_thinking_tail(self) -> None:
|
|
398
450
|
entry = HistoryEntry(entry_type="thinking", text="streamed thought\n")
|
|
399
451
|
lines = self._render_lines([entry], assume_run_continues_at_tail=True)
|
|
400
452
|
non_empty = [ln for ln in lines if ln.strip() != ""]
|
|
401
453
|
self.assertEqual(non_empty, [" streamed thought"], msg=lines)
|
|
402
454
|
|
|
455
|
+
def test_cross_batch_thinking_to_assistant_keeps_boundary_blank_line(self) -> None:
|
|
456
|
+
first = self._render_str(
|
|
457
|
+
[HistoryEntry(entry_type="thinking", text="thought\n")],
|
|
458
|
+
assume_run_continues_at_tail=True,
|
|
459
|
+
)
|
|
460
|
+
second = self._render_str(
|
|
461
|
+
[HistoryEntry(entry_type="assistant", text="answer\n")],
|
|
462
|
+
prev_was_thinking=True,
|
|
463
|
+
)
|
|
464
|
+
merged = (first + second).split("\n")
|
|
465
|
+
thinking_idx = merged.index(" thought")
|
|
466
|
+
assistant_idx = merged.index("⏺ answer")
|
|
467
|
+
|
|
468
|
+
self.assertEqual(merged[thinking_idx + 1], "", msg=merged)
|
|
469
|
+
self.assertEqual(assistant_idx, thinking_idx + 2, msg=merged)
|
|
470
|
+
|
|
471
|
+
def test_cross_batch_thinking_to_thinking_does_not_insert_blank_line(self) -> None:
|
|
472
|
+
first = self._render_str(
|
|
473
|
+
[HistoryEntry(entry_type="thinking", text="line one\n")],
|
|
474
|
+
assume_run_continues_at_tail=True,
|
|
475
|
+
)
|
|
476
|
+
second = self._render_str(
|
|
477
|
+
[HistoryEntry(entry_type="thinking", text="line two\n")],
|
|
478
|
+
prev_was_thinking=True,
|
|
479
|
+
)
|
|
480
|
+
merged = (first + second).split("\n")
|
|
481
|
+
first_idx = merged.index(" line one")
|
|
482
|
+
self.assertEqual(merged[first_idx + 1], " line two", msg=merged)
|
|
483
|
+
|
|
403
484
|
def test_split_thinking_batch_visual_matches_single_batch(self) -> None:
|
|
404
485
|
entries = [
|
|
405
486
|
HistoryEntry(entry_type="thinking", text="line one\n"),
|
|
@@ -410,7 +491,7 @@ class TestAssumeRunContinuesAtTail(unittest.TestCase):
|
|
|
410
491
|
first = self._render_lines(
|
|
411
492
|
entries[:2], assume_run_continues_at_tail=True,
|
|
412
493
|
)
|
|
413
|
-
second = self._render_lines(entries[2:])
|
|
494
|
+
second = self._render_lines(entries[2:], prev_was_thinking=True)
|
|
414
495
|
merged = first + second
|
|
415
496
|
|
|
416
497
|
single_content = [ln for ln in single_lines if ln.strip() != ""]
|
|
@@ -5,6 +5,7 @@ from types import SimpleNamespace
|
|
|
5
5
|
|
|
6
6
|
from comate_agent_sdk.context import ContextIR, TokenCounter
|
|
7
7
|
from comate_agent_sdk.context.items import ItemType
|
|
8
|
+
from comate_agent_sdk.skill.bundled import get_builtin_skills
|
|
8
9
|
from comate_agent_sdk.skill.models import SkillDefinition
|
|
9
10
|
from comate_cli.terminal_agent.slash_commands import SlashCommandSpec
|
|
10
11
|
from comate_cli.terminal_agent.tui import TerminalAgentTUI
|
|
@@ -381,6 +382,52 @@ class TestSkillSlashRegistration(unittest.TestCase):
|
|
|
381
382
|
self.assertEqual(app._skill_slash_command_names, {"demo"})
|
|
382
383
|
self.assertIn("demo", {spec.name for spec in completer.updated_specs})
|
|
383
384
|
|
|
385
|
+
def test_sync_registers_builtin_skillify_with_argument_hint(self) -> None:
|
|
386
|
+
registry = SlashCommandRegistry()
|
|
387
|
+
completer = _FakeSlashCompleter()
|
|
388
|
+
app = TerminalAgentTUI.__new__(TerminalAgentTUI)
|
|
389
|
+
app._session = _build_session(list(get_builtin_skills()))
|
|
390
|
+
app._slash_registry = registry
|
|
391
|
+
app._skill_slash_command_names = set()
|
|
392
|
+
app._slash_completer = completer
|
|
393
|
+
|
|
394
|
+
app._sync_skill_slash_commands()
|
|
395
|
+
|
|
396
|
+
skillify = registry.resolve("skillify")
|
|
397
|
+
self.assertIsNotNone(skillify)
|
|
398
|
+
assert skillify is not None
|
|
399
|
+
self.assertEqual(skillify.source, "skill")
|
|
400
|
+
self.assertEqual(skillify.spec.execution_kind, "hybrid")
|
|
401
|
+
self.assertIsNotNone(skillify.spec.argument_hint)
|
|
402
|
+
self.assertIn("description", skillify.spec.argument_hint or "")
|
|
403
|
+
self.assertEqual(app._skill_slash_command_names, {"skillify"})
|
|
404
|
+
self.assertIn("skillify", {spec.name for spec in completer.updated_specs})
|
|
405
|
+
|
|
406
|
+
def test_custom_slash_skillify_takes_precedence_over_builtin_skill(self) -> None:
|
|
407
|
+
registry = SlashCommandRegistry()
|
|
408
|
+
registry.register(
|
|
409
|
+
spec=SlashCommandSpec(
|
|
410
|
+
name="skillify",
|
|
411
|
+
description="custom skillify",
|
|
412
|
+
execution_kind="local",
|
|
413
|
+
),
|
|
414
|
+
handler=_noop_handler,
|
|
415
|
+
)
|
|
416
|
+
completer = _FakeSlashCompleter()
|
|
417
|
+
app = TerminalAgentTUI.__new__(TerminalAgentTUI)
|
|
418
|
+
app._session = _build_session(list(get_builtin_skills()))
|
|
419
|
+
app._slash_registry = registry
|
|
420
|
+
app._skill_slash_command_names = set()
|
|
421
|
+
app._slash_completer = completer
|
|
422
|
+
|
|
423
|
+
app._sync_skill_slash_commands()
|
|
424
|
+
|
|
425
|
+
skillify = registry.resolve("skillify")
|
|
426
|
+
self.assertIsNotNone(skillify)
|
|
427
|
+
assert skillify is not None
|
|
428
|
+
self.assertEqual(skillify.source, "builtin")
|
|
429
|
+
self.assertEqual(app._skill_slash_command_names, set())
|
|
430
|
+
|
|
384
431
|
|
|
385
432
|
if __name__ == "__main__":
|
|
386
433
|
unittest.main()
|
|
@@ -389,7 +389,7 @@ wheels = [
|
|
|
389
389
|
|
|
390
390
|
[[package]]
|
|
391
391
|
name = "comate-agent-sdk"
|
|
392
|
-
version = "0.6.
|
|
392
|
+
version = "0.6.9"
|
|
393
393
|
source = { editable = "../../" }
|
|
394
394
|
dependencies = [
|
|
395
395
|
{ name = "aiohttp" },
|
|
@@ -461,7 +461,7 @@ dev = [
|
|
|
461
461
|
|
|
462
462
|
[[package]]
|
|
463
463
|
name = "comate-cli"
|
|
464
|
-
version = "0.5.
|
|
464
|
+
version = "0.5.8"
|
|
465
465
|
source = { editable = "." }
|
|
466
466
|
dependencies = [
|
|
467
467
|
{ name = "charset-normalizer" },
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/components/__init__.py
RENAMED
|
File without changes
|
{comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/components/detail_view.py
RENAMED
|
File without changes
|
{comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/components/plugin_list.py
RENAMED
|
File without changes
|
{comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/components/search_box.py
RENAMED
|
File without changes
|
{comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/components/tab_bar.py
RENAMED
|
File without changes
|
{comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/marketplace_install_view.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/tabs/discover_tab.py
RENAMED
|
File without changes
|
|
File without changes
|
{comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/tabs/installed_tab.py
RENAMED
|
File without changes
|
{comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/plugins/tabs/marketplaces_tab.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/tui_parts/mcp_connecting_view.py
RENAMED
|
File without changes
|
|
File without changes
|
{comate_cli-0.5.8 → comate_cli-0.6.0}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{comate_cli-0.5.8 → comate_cli-0.6.0}/docs/superpowers/specs/2026-04-03-phrase-shuffle-design.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|