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.
Files changed (100) hide show
  1. {comate_cli-0.3.0 → comate_cli-0.3.2}/PKG-INFO +1 -1
  2. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/app.py +9 -2
  3. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/status_bar.py +29 -0
  4. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui.py +14 -3
  5. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui_parts/render_panels.py +27 -0
  6. {comate_cli-0.3.0 → comate_cli-0.3.2}/pyproject.toml +1 -1
  7. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_app_mcp_preload.py +1 -1
  8. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_completion_status_panel.py +4 -0
  9. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_input_history.py +9 -0
  10. comate_cli-0.3.2/tests/test_status_bar_transient.py +61 -0
  11. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_tui_mcp_init_gate.py +17 -0
  12. {comate_cli-0.3.0 → comate_cli-0.3.2}/uv.lock +2 -2
  13. {comate_cli-0.3.0 → comate_cli-0.3.2}/.gitignore +0 -0
  14. {comate_cli-0.3.0 → comate_cli-0.3.2}/README.md +0 -0
  15. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/__init__.py +0 -0
  16. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/__main__.py +0 -0
  17. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/main.py +0 -0
  18. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/mcp_cli.py +0 -0
  19. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/__init__.py +0 -0
  20. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/animations.py +0 -0
  21. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/assistant_render.py +0 -0
  22. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
  23. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/env_utils.py +0 -0
  24. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/error_display.py +0 -0
  25. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/event_renderer.py +0 -0
  26. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/fragment_utils.py +0 -0
  27. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/history_printer.py +0 -0
  28. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/input_geometry.py +0 -0
  29. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
  30. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/logging_adapter.py +0 -0
  31. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/logo.py +0 -0
  32. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/markdown_render.py +0 -0
  33. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/mention_completer.py +0 -0
  34. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/message_style.py +0 -0
  35. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/models.py +0 -0
  36. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/preflight.py +0 -0
  37. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/question_view.py +0 -0
  38. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/resume_selector.py +0 -0
  39. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/rewind_store.py +0 -0
  40. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
  41. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
  42. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/selection_menu.py +0 -0
  43. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/session_view.py +0 -0
  44. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/slash_commands.py +0 -0
  45. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/startup.py +0 -0
  46. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/text_effects.py +0 -0
  47. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tips.py +0 -0
  48. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tool_view.py +0 -0
  49. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
  50. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
  51. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui_parts/history_sync.py +0 -0
  52. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
  53. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
  54. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
  55. {comate_cli-0.3.0 → comate_cli-0.3.2}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
  56. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/conftest.py +0 -0
  57. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_app_preflight_gate.py +0 -0
  58. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_app_print_mode.py +0 -0
  59. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_app_shutdown.py +0 -0
  60. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_app_usage_line.py +0 -0
  61. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_cli_project_root.py +0 -0
  62. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_compact_command_semantics.py +0 -0
  63. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_completion_context_activation.py +0 -0
  64. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_context_command.py +0 -0
  65. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_custom_slash_commands.py +0 -0
  66. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_event_renderer.py +0 -0
  67. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_history_printer.py +0 -0
  68. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_history_sync.py +0 -0
  69. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_input_behavior.py +0 -0
  70. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_interrupt_exit_semantics.py +0 -0
  71. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_layout_coordinator.py +0 -0
  72. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_logging_adapter.py +0 -0
  73. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_logo.py +0 -0
  74. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_main_args.py +0 -0
  75. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_mcp_cli.py +0 -0
  76. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_mcp_slash_command.py +0 -0
  77. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_mention_completer.py +0 -0
  78. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_preflight.py +0 -0
  79. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_preflight_copilot.py +0 -0
  80. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_question_key_bindings.py +0 -0
  81. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_question_view.py +0 -0
  82. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_resume_selector.py +0 -0
  83. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_rewind_command_semantics.py +0 -0
  84. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_rewind_store.py +0 -0
  85. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_rpc_protocol.py +0 -0
  86. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_rpc_stdio_bridge.py +0 -0
  87. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_selection_menu.py +0 -0
  88. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_skills_slash_command.py +0 -0
  89. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_slash_argument_hint.py +0 -0
  90. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_slash_completer.py +0 -0
  91. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_slash_registry.py +0 -0
  92. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_status_bar.py +0 -0
  93. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_task_panel_format.py +0 -0
  94. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_task_panel_key_bindings.py +0 -0
  95. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_task_panel_rendering.py +0 -0
  96. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_task_poll.py +0 -0
  97. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_tool_view.py +0 -0
  98. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_tui_elapsed_status.py +0 -0
  99. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_tui_paste_placeholder.py +0 -0
  100. {comate_cli-0.3.0 → comate_cli-0.3.2}/tests/test_tui_split_invariance.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comate-cli
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Comate terminal CLI built on comate-agent-sdk
5
5
  Project-URL: Homepage, https://github.com/AndyLee1024/agent-sdk
6
6
  Project-URL: Repository, https://github.com/AndyLee1024/agent-sdk
@@ -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.warning(f"MCP init failed: {e}", exc_info=True)
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.warning(f"MCP server '{alias}' skipped: {reason}")
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 = cwd.replace("/", "_").lstrip("_")
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.warning(
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.warning(f"MCP init failed in TUI bootstrap: {exc}", exc_info=True)
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)"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "comate-cli"
7
- version = "0.3.0"
7
+ version = "0.3.2"
8
8
  description = "Comate terminal CLI built on comate-agent-sdk"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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.options = types.SimpleNamespace(mcp_enabled=True)
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")],
@@ -39,6 +39,10 @@ class _FakeStatusBar:
39
39
  self.git_diff_calls += 1
40
40
  return []
41
41
 
42
+ @property
43
+ def transient_message(self) -> str | None:
44
+ return None
45
+
42
46
 
43
47
  class _FakeLoadingState:
44
48
  def __init__(self, text: str = "") -> None:
@@ -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.2.9"
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.2.9"
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