comate-cli 0.3.0__tar.gz → 0.3.2__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.3.0 → comate_cli-0.3.2}/PKG-INFO +1 -1
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/app.py +9 -2
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/status_bar.py +29 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui.py +14 -3
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui_parts/render_panels.py +27 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/pyproject.toml +1 -1
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_app_mcp_preload.py +1 -1
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_completion_status_panel.py +4 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_input_history.py +9 -0
- comate_cli-0.3.2/tests/test_status_bar_transient.py +61 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_tui_mcp_init_gate.py +17 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/uv.lock +2 -2
- {comate_cli-0.3.0 → comate_cli-0.3.2}/.gitignore +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/README.md +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/__init__.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/__main__.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/main.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/mcp_cli.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/animations.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/error_display.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/event_renderer.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/history_printer.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/logging_adapter.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/logo.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/markdown_render.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/models.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/preflight.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/question_view.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/rewind_store.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/session_view.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/slash_commands.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tool_view.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/conftest.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_app_print_mode.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_app_shutdown.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_app_usage_line.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_context_command.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_custom_slash_commands.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_event_renderer.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_history_printer.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_history_sync.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_interrupt_exit_semantics.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_logging_adapter.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_logo.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_main_args.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_mcp_cli.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_mcp_slash_command.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_preflight.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_preflight_copilot.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_question_view.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_rewind_store.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_skills_slash_command.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_slash_completer.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_status_bar.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_task_panel_format.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_task_panel_rendering.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_task_poll.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_tool_view.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_tui_paste_placeholder.py +0 -0
- {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_tui_split_invariance.py +0 -0
|
@@ -242,7 +242,7 @@ async def _preload_mcp_in_tui(session: ChatSession) -> None:
|
|
|
242
242
|
return
|
|
243
243
|
await preload_task
|
|
244
244
|
except Exception as e:
|
|
245
|
-
logger.
|
|
245
|
+
logger.debug(f"MCP init failed: {e}", exc_info=True)
|
|
246
246
|
return
|
|
247
247
|
|
|
248
248
|
mgr = runtime._mcp_manager
|
|
@@ -250,7 +250,7 @@ async def _preload_mcp_in_tui(session: ChatSession) -> None:
|
|
|
250
250
|
return
|
|
251
251
|
|
|
252
252
|
for alias, reason in mgr.failed_servers:
|
|
253
|
-
logger.
|
|
253
|
+
logger.debug(f"MCP server '{alias}' skipped: {reason}")
|
|
254
254
|
|
|
255
255
|
loaded = mgr.tool_infos
|
|
256
256
|
if loaded:
|
|
@@ -360,6 +360,13 @@ async def run(
|
|
|
360
360
|
|
|
361
361
|
async def _mcp_loader() -> None:
|
|
362
362
|
await _preload_mcp_in_tui(session)
|
|
363
|
+
mgr = session.runtime._mcp_manager
|
|
364
|
+
if mgr and mgr.failed_servers:
|
|
365
|
+
aliases = [alias for alias, _ in mgr.failed_servers]
|
|
366
|
+
status_bar.show_transient(
|
|
367
|
+
f"⚠ MCP: {', '.join(aliases)} unavailable",
|
|
368
|
+
duration_s=5.0,
|
|
369
|
+
)
|
|
363
370
|
|
|
364
371
|
usage_line: str | None = None
|
|
365
372
|
active_session = session
|
|
@@ -29,6 +29,8 @@ class StatusBar:
|
|
|
29
29
|
self._context_left_pct: float = 100.0
|
|
30
30
|
self._git_diff_stats: GitDiffStats | None = None
|
|
31
31
|
self._git_diff_cache_time: float = 0.0
|
|
32
|
+
self._transient_message: str | None = None
|
|
33
|
+
self._transient_until: float | None = None
|
|
32
34
|
|
|
33
35
|
@staticmethod
|
|
34
36
|
def _resolve_model_name(session: ChatSession) -> str:
|
|
@@ -62,6 +64,7 @@ class StatusBar:
|
|
|
62
64
|
check=False,
|
|
63
65
|
capture_output=True,
|
|
64
66
|
text=True,
|
|
67
|
+
encoding="utf-8",
|
|
65
68
|
timeout=StatusBar._GIT_CMD_TIMEOUT_SECONDS,
|
|
66
69
|
)
|
|
67
70
|
except subprocess.TimeoutExpired:
|
|
@@ -82,6 +85,7 @@ class StatusBar:
|
|
|
82
85
|
check=False,
|
|
83
86
|
capture_output=True,
|
|
84
87
|
text=True,
|
|
88
|
+
encoding="utf-8",
|
|
85
89
|
timeout=StatusBar._GIT_CMD_TIMEOUT_SECONDS,
|
|
86
90
|
)
|
|
87
91
|
except subprocess.TimeoutExpired:
|
|
@@ -254,5 +258,30 @@ class StatusBar:
|
|
|
254
258
|
fragments.append(("", " "))
|
|
255
259
|
return fragments
|
|
256
260
|
|
|
261
|
+
def show_transient(self, message: str, duration_s: float = 5.0) -> None:
|
|
262
|
+
"""Set a transient message that auto-clears after *duration_s* seconds."""
|
|
263
|
+
self._transient_message = message
|
|
264
|
+
self._transient_until = time.monotonic() + duration_s
|
|
265
|
+
|
|
266
|
+
def clear_transient_if_expired(self) -> bool:
|
|
267
|
+
"""Check and clear expired transient message.
|
|
268
|
+
|
|
269
|
+
Returns True if the message was just cleared (state changed, needs repaint).
|
|
270
|
+
Returns False otherwise (no message, or message still active).
|
|
271
|
+
"""
|
|
272
|
+
if self._transient_until is not None and time.monotonic() >= self._transient_until:
|
|
273
|
+
self._transient_message = None
|
|
274
|
+
self._transient_until = None
|
|
275
|
+
return True
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def transient_message(self) -> str | None:
|
|
280
|
+
return self._transient_message
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def has_transient(self) -> bool:
|
|
284
|
+
return self._transient_message is not None
|
|
285
|
+
|
|
257
286
|
def helper_toolbar(self) -> list[tuple[str, str]]:
|
|
258
287
|
return []
|
|
@@ -83,8 +83,9 @@ _INPUT_HISTORY_MAX_ENTRIES = 200
|
|
|
83
83
|
def _get_input_history_path(cwd: str) -> Path:
|
|
84
84
|
"""Compute FileHistory path for a given cwd, ensuring parent dir exists."""
|
|
85
85
|
import hashlib
|
|
86
|
+
import re
|
|
86
87
|
|
|
87
|
-
slug =
|
|
88
|
+
slug = re.sub(r'[^a-zA-Z0-9_.\-]', '_', cwd).lstrip("_")
|
|
88
89
|
if len(slug) > 200:
|
|
89
90
|
hash_suffix = hashlib.sha1(cwd.encode()).hexdigest()[:8]
|
|
90
91
|
slug = f"{slug[:100]}_{hash_suffix}"
|
|
@@ -483,6 +484,7 @@ class TerminalAgentTUI(
|
|
|
483
484
|
"status.mode.act": "bg:default #60a5fa bold",
|
|
484
485
|
"status.mode.plan": "bg:default #7AC9CA bold",
|
|
485
486
|
"status.hint": "bg:default #6B7280",
|
|
487
|
+
"status.transient": "bg:default italic fg:ansiyellow",
|
|
486
488
|
"input.placeholder": "bg:default #9CA3AF",
|
|
487
489
|
"auto-suggestion": "bg:default #94a3b8",
|
|
488
490
|
"queue": "bg:#1d222a #d8dee9",
|
|
@@ -1141,6 +1143,12 @@ class TerminalAgentTUI(
|
|
|
1141
1143
|
else:
|
|
1142
1144
|
self._render_dirty = True
|
|
1143
1145
|
|
|
1146
|
+
# 瞬态消息过期检查
|
|
1147
|
+
if self._status_bar.clear_transient_if_expired():
|
|
1148
|
+
self._render_dirty = True
|
|
1149
|
+
elif self._status_bar.has_transient:
|
|
1150
|
+
self._render_dirty = True
|
|
1151
|
+
|
|
1144
1152
|
loading_line = self._renderer.loading_line().strip()
|
|
1145
1153
|
loading_changed = loading_line != self._last_loading_line
|
|
1146
1154
|
if loading_changed:
|
|
@@ -1276,10 +1284,13 @@ class TerminalAgentTUI(
|
|
|
1276
1284
|
timeout=self._mcp_init_gate_timeout_s,
|
|
1277
1285
|
)
|
|
1278
1286
|
except asyncio.TimeoutError:
|
|
1279
|
-
logger.
|
|
1287
|
+
logger.debug(
|
|
1280
1288
|
"MCP init timed out after "
|
|
1281
1289
|
f"{self._mcp_init_gate_timeout_s:.1f}s; degrade and continue",
|
|
1282
1290
|
)
|
|
1291
|
+
self._status_bar.show_transient(
|
|
1292
|
+
"⚠ MCP: init timed out", duration_s=5.0,
|
|
1293
|
+
)
|
|
1283
1294
|
await self._cancel_task_with_timeout(
|
|
1284
1295
|
init_task,
|
|
1285
1296
|
timeout_s=self._mcp_init_cancel_timeout_s,
|
|
@@ -1293,7 +1304,7 @@ class TerminalAgentTUI(
|
|
|
1293
1304
|
)
|
|
1294
1305
|
return
|
|
1295
1306
|
except Exception as exc:
|
|
1296
|
-
logger.
|
|
1307
|
+
logger.debug(f"MCP init failed in TUI bootstrap: {exc}", exc_info=True)
|
|
1297
1308
|
finally:
|
|
1298
1309
|
self._initializing = False
|
|
1299
1310
|
if self._queued_messages and not self._busy:
|
|
@@ -49,6 +49,33 @@ class RenderPanelsMixin:
|
|
|
49
49
|
def _status_text(self) -> list[tuple[str, str]]:
|
|
50
50
|
width = self._terminal_width()
|
|
51
51
|
|
|
52
|
+
# 瞬态消息优先于 mode 显示
|
|
53
|
+
transient = self._status_bar.transient_message
|
|
54
|
+
if transient:
|
|
55
|
+
# 右栏:model | ~branch / X% left (不变)
|
|
56
|
+
right_text = self._status_bar.info_status_text()
|
|
57
|
+
if self._busy or self._initializing:
|
|
58
|
+
git_frags = []
|
|
59
|
+
else:
|
|
60
|
+
git_frags = self._status_bar.git_diff_fragments()
|
|
61
|
+
|
|
62
|
+
right_w = sum(get_cwidth(c) for c in right_text)
|
|
63
|
+
if git_frags:
|
|
64
|
+
right_w += 2 + sum(get_cwidth(c) for _, t in git_frags for c in t)
|
|
65
|
+
|
|
66
|
+
left_w = sum(get_cwidth(c) for c in transient)
|
|
67
|
+
padding = max(1, width - left_w - right_w - 2)
|
|
68
|
+
|
|
69
|
+
frags: list[tuple[str, str]] = [
|
|
70
|
+
("class:status.transient", transient),
|
|
71
|
+
("class:status", " " * padding),
|
|
72
|
+
("class:status", right_text),
|
|
73
|
+
]
|
|
74
|
+
if git_frags:
|
|
75
|
+
frags.append(("class:status", " "))
|
|
76
|
+
frags.extend(git_frags)
|
|
77
|
+
return frags
|
|
78
|
+
|
|
52
79
|
# 左栏:mode 图标 + 提示文字
|
|
53
80
|
mode = self._status_bar.get_mode()
|
|
54
81
|
hint_text = "(shift+tab to cycle)"
|
|
@@ -9,7 +9,7 @@ from comate_cli.terminal_agent.app import _preload_mcp_in_tui
|
|
|
9
9
|
|
|
10
10
|
class _FakeRuntime:
|
|
11
11
|
def __init__(self) -> None:
|
|
12
|
-
self.
|
|
12
|
+
self.config = types.SimpleNamespace(mcp_enabled=True)
|
|
13
13
|
self._mcp_manager = types.SimpleNamespace(
|
|
14
14
|
failed_servers=[],
|
|
15
15
|
tool_infos=[types.SimpleNamespace(server_alias="ctx7")],
|
|
@@ -25,6 +25,15 @@ class TestGetInputHistoryPath(unittest.TestCase):
|
|
|
25
25
|
self.assertTrue(len(path.stem) <= 109) # 100 chars + _ + 8 hex
|
|
26
26
|
self.assertTrue(path.name.endswith(".txt"))
|
|
27
27
|
|
|
28
|
+
def test_slug_windows_path(self) -> None:
|
|
29
|
+
"""Windows paths with backslashes and drive letter produce a safe slug."""
|
|
30
|
+
from comate_cli.terminal_agent.tui import _get_input_history_path
|
|
31
|
+
with patch("comate_cli.terminal_agent.tui._INPUT_HISTORY_DIR", Path(tempfile.mkdtemp())):
|
|
32
|
+
path = _get_input_history_path(r"C:\Users\ligj\projects\agent-sdk")
|
|
33
|
+
# Colon, backslashes all replaced; no absolute-path hijacking
|
|
34
|
+
self.assertEqual(path.name, "C__Users_ligj_projects_agent-sdk.txt")
|
|
35
|
+
self.assertTrue(str(path).startswith(str(path.parent)))
|
|
36
|
+
|
|
28
37
|
def test_creates_parent_directory(self) -> None:
|
|
29
38
|
from comate_cli.terminal_agent.tui import _get_input_history_path
|
|
30
39
|
with tempfile.TemporaryDirectory() as tmp:
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import unittest
|
|
5
|
+
from unittest.mock import patch
|
|
6
|
+
|
|
7
|
+
from comate_cli.terminal_agent.status_bar import StatusBar
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _FakeSession:
|
|
11
|
+
"""Minimal stub so StatusBar.__init__ does not need a real ChatSession."""
|
|
12
|
+
class _agent:
|
|
13
|
+
class llm:
|
|
14
|
+
model = "test-model"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _make_status_bar() -> StatusBar:
|
|
18
|
+
with patch.object(StatusBar, "_resolve_git_branch", return_value="main"):
|
|
19
|
+
return StatusBar(_FakeSession()) # type: ignore[arg-type]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TestStatusBarTransient(unittest.TestCase):
|
|
23
|
+
def test_show_transient_sets_message(self) -> None:
|
|
24
|
+
sb = _make_status_bar()
|
|
25
|
+
assert sb.transient_message is None
|
|
26
|
+
assert sb.has_transient is False
|
|
27
|
+
|
|
28
|
+
sb.show_transient("hello", duration_s=5.0)
|
|
29
|
+
|
|
30
|
+
assert sb.transient_message == "hello"
|
|
31
|
+
assert sb.has_transient is True
|
|
32
|
+
|
|
33
|
+
def test_clear_transient_if_expired_returns_false_when_no_message(self) -> None:
|
|
34
|
+
sb = _make_status_bar()
|
|
35
|
+
assert sb.clear_transient_if_expired() is False
|
|
36
|
+
|
|
37
|
+
def test_clear_transient_if_expired_returns_false_while_active(self) -> None:
|
|
38
|
+
sb = _make_status_bar()
|
|
39
|
+
sb.show_transient("active", duration_s=10.0)
|
|
40
|
+
assert sb.clear_transient_if_expired() is False
|
|
41
|
+
assert sb.has_transient is True
|
|
42
|
+
|
|
43
|
+
def test_clear_transient_if_expired_returns_true_and_clears_when_expired(self) -> None:
|
|
44
|
+
sb = _make_status_bar()
|
|
45
|
+
sb.show_transient("expired", duration_s=0.0)
|
|
46
|
+
# duration_s=0.0 means it expires immediately at monotonic() + 0.0
|
|
47
|
+
# Give a tiny margin
|
|
48
|
+
time.sleep(0.01)
|
|
49
|
+
assert sb.clear_transient_if_expired() is True
|
|
50
|
+
assert sb.transient_message is None
|
|
51
|
+
assert sb.has_transient is False
|
|
52
|
+
|
|
53
|
+
def test_show_transient_overwrites_previous(self) -> None:
|
|
54
|
+
sb = _make_status_bar()
|
|
55
|
+
sb.show_transient("first", duration_s=10.0)
|
|
56
|
+
sb.show_transient("second", duration_s=10.0)
|
|
57
|
+
assert sb.transient_message == "second"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
unittest.main(verbosity=2)
|
|
@@ -51,6 +51,21 @@ class TestTUIMcpInitGate(unittest.IsolatedAsyncioTestCase):
|
|
|
51
51
|
tui._mcp_init_task = None
|
|
52
52
|
tui._ui_tick_task = None
|
|
53
53
|
|
|
54
|
+
# Provide a minimal status bar stub for show_transient()
|
|
55
|
+
class _FakeStatusBar:
|
|
56
|
+
def __init__(self) -> None:
|
|
57
|
+
self.transient_calls: list[tuple[str, float]] = []
|
|
58
|
+
def show_transient(self, msg: str, duration_s: float = 5.0) -> None:
|
|
59
|
+
self.transient_calls.append((msg, duration_s))
|
|
60
|
+
def clear_transient_if_expired(self) -> bool:
|
|
61
|
+
return False
|
|
62
|
+
@property
|
|
63
|
+
def has_transient(self) -> bool:
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
fake_status_bar = _FakeStatusBar()
|
|
67
|
+
tui._status_bar = fake_status_bar
|
|
68
|
+
|
|
54
69
|
submitted: list[str] = []
|
|
55
70
|
|
|
56
71
|
async def _fake_submit_user_message(
|
|
@@ -80,6 +95,8 @@ class TestTUIMcpInitGate(unittest.IsolatedAsyncioTestCase):
|
|
|
80
95
|
self.assertEqual(list(tui._queued_messages), [])
|
|
81
96
|
self.assertTrue(tui._renderer.closed)
|
|
82
97
|
self.assertEqual(tui._renderer.messages, [])
|
|
98
|
+
self.assertEqual(len(fake_status_bar.transient_calls), 1)
|
|
99
|
+
self.assertIn("timed out", fake_status_bar.transient_calls[0][0])
|
|
83
100
|
|
|
84
101
|
|
|
85
102
|
if __name__ == "__main__":
|
|
@@ -364,7 +364,7 @@ wheels = [
|
|
|
364
364
|
|
|
365
365
|
[[package]]
|
|
366
366
|
name = "comate-agent-sdk"
|
|
367
|
-
version = "0.
|
|
367
|
+
version = "0.3.1"
|
|
368
368
|
source = { editable = "../../" }
|
|
369
369
|
dependencies = [
|
|
370
370
|
{ name = "aiohttp" },
|
|
@@ -429,7 +429,7 @@ dev = [
|
|
|
429
429
|
|
|
430
430
|
[[package]]
|
|
431
431
|
name = "comate-cli"
|
|
432
|
-
version = "0.
|
|
432
|
+
version = "0.3.1"
|
|
433
433
|
source = { editable = "." }
|
|
434
434
|
dependencies = [
|
|
435
435
|
{ name = "comate-agent-sdk" },
|
|
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
|
{comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui_parts/slash_command_registry.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
|
|
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
|