comate-cli 0.2.2__tar.gz → 0.2.4__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 (94) hide show
  1. {comate_cli-0.2.2 → comate_cli-0.2.4}/.gitignore +1 -1
  2. {comate_cli-0.2.2 → comate_cli-0.2.4}/PKG-INFO +1 -1
  3. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/app.py +1 -1
  4. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/event_renderer.py +91 -0
  5. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui.py +39 -1
  6. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui_parts/commands.py +1 -1
  7. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui_parts/history_sync.py +59 -0
  8. {comate_cli-0.2.2 → comate_cli-0.2.4}/pyproject.toml +1 -1
  9. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_event_renderer.py +69 -0
  10. {comate_cli-0.2.2 → comate_cli-0.2.4}/uv.lock +2 -2
  11. comate_cli-0.2.2/test_memory.md +0 -10
  12. {comate_cli-0.2.2 → comate_cli-0.2.4}/README.md +0 -0
  13. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/__init__.py +0 -0
  14. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/__main__.py +0 -0
  15. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/main.py +0 -0
  16. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/mcp_cli.py +0 -0
  17. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/__init__.py +0 -0
  18. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/animations.py +0 -0
  19. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/assistant_render.py +0 -0
  20. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
  21. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/env_utils.py +0 -0
  22. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/error_display.py +0 -0
  23. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/fragment_utils.py +0 -0
  24. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/history_printer.py +0 -0
  25. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/input_geometry.py +0 -0
  26. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
  27. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/logging_adapter.py +0 -0
  28. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/logo.py +0 -0
  29. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/markdown_render.py +0 -0
  30. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/mention_completer.py +0 -0
  31. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/message_style.py +0 -0
  32. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/models.py +0 -0
  33. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/preflight.py +0 -0
  34. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/question_view.py +0 -0
  35. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/resume_selector.py +0 -0
  36. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/rewind_store.py +0 -0
  37. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
  38. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
  39. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/selection_menu.py +0 -0
  40. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/session_view.py +0 -0
  41. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/slash_commands.py +0 -0
  42. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/startup.py +0 -0
  43. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/status_bar.py +0 -0
  44. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/text_effects.py +0 -0
  45. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tips.py +0 -0
  46. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tool_view.py +0 -0
  47. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
  48. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
  49. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
  50. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
  51. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
  52. {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
  53. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/conftest.py +0 -0
  54. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_app_mcp_preload.py +0 -0
  55. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_app_preflight_gate.py +0 -0
  56. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_app_shutdown.py +0 -0
  57. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_app_usage_line.py +0 -0
  58. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_cli_project_root.py +0 -0
  59. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_compact_command_semantics.py +0 -0
  60. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_completion_context_activation.py +0 -0
  61. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_completion_status_panel.py +0 -0
  62. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_context_command.py +0 -0
  63. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_custom_slash_commands.py +0 -0
  64. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_history_sync.py +0 -0
  65. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_input_behavior.py +0 -0
  66. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_interrupt_exit_semantics.py +0 -0
  67. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_layout_coordinator.py +0 -0
  68. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_logging_adapter.py +0 -0
  69. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_logo.py +0 -0
  70. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_main_args.py +0 -0
  71. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_mcp_cli.py +0 -0
  72. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_mcp_slash_command.py +0 -0
  73. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_mention_completer.py +0 -0
  74. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_preflight.py +0 -0
  75. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_preflight_copilot.py +0 -0
  76. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_question_key_bindings.py +0 -0
  77. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_question_view.py +0 -0
  78. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_resume_selector.py +0 -0
  79. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_rewind_command_semantics.py +0 -0
  80. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_rewind_store.py +0 -0
  81. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_rpc_protocol.py +0 -0
  82. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_rpc_stdio_bridge.py +0 -0
  83. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_selection_menu.py +0 -0
  84. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_skills_slash_command.py +0 -0
  85. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_slash_argument_hint.py +0 -0
  86. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_slash_completer.py +0 -0
  87. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_slash_registry.py +0 -0
  88. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_status_bar.py +0 -0
  89. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_task_panel_key_bindings.py +0 -0
  90. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_tool_view.py +0 -0
  91. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_tui_elapsed_status.py +0 -0
  92. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_tui_mcp_init_gate.py +0 -0
  93. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_tui_paste_placeholder.py +0 -0
  94. {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_tui_split_invariance.py +0 -0
@@ -222,4 +222,4 @@ __marimo__/
222
222
  ./tmp/
223
223
 
224
224
  # OS generated files
225
- .DS_Store
225
+ .DS_Store.worktrees/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comate-cli
3
- Version: 0.2.2
3
+ Version: 0.2.4
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
@@ -230,7 +230,7 @@ def _format_resume_hint(session_id: str | None) -> str | None:
230
230
  async def _preload_mcp_in_tui(session: ChatSession) -> None:
231
231
  """在 TUI 内异步加载 MCP,初始化阶段不输出 scrollback 文案。"""
232
232
  runtime = session.runtime
233
- if not bool(runtime.options.mcp_enabled):
233
+ if not bool(runtime.config.mcp_enabled):
234
234
  return
235
235
 
236
236
  try:
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import logging
4
4
  import time
5
+ from collections import deque
5
6
  from dataclasses import dataclass, field
6
7
  from pathlib import Path
7
8
  from typing import Any, Literal
@@ -17,6 +18,7 @@ from comate_agent_sdk.agent.events import (
17
18
  SubagentStopEvent,
18
19
  SubagentToolCallEvent,
19
20
  SubagentToolResultEvent,
21
+ TeamMessageEvent,
20
22
  TextEvent,
21
23
  ThinkingEvent,
22
24
  TaskUpdatedEvent,
@@ -38,6 +40,7 @@ logger = logging.getLogger(__name__)
38
40
  _DEFAULT_TOOL_ERROR_SUMMARY_MAX_LEN = 160
39
41
  _DEFAULT_TOOL_PANEL_MAX_LINES = 4
40
42
  _DEFAULT_TASK_PANEL_MAX_LINES = 6
43
+ _RECENT_TEAM_EVENT_CACHE_SIZE = 128
41
44
 
42
45
 
43
46
  def _truncate(content: str, max_len: int = 120) -> str:
@@ -183,6 +186,9 @@ class EventRenderer:
183
186
  50,
184
187
  )
185
188
  self._latest_diff_lines: list[str] | None = None
189
+ self._recent_team_event_keys: deque[tuple[str, str, str, str, str, str]] = deque(
190
+ maxlen=_RECENT_TEAM_EVENT_CACHE_SIZE
191
+ )
186
192
 
187
193
  def start_turn(self) -> None:
188
194
  self._flush_assistant_segment()
@@ -229,6 +235,7 @@ class EventRenderer:
229
235
  def reset_history_view(self) -> None:
230
236
  """重置 history 视图状态(用于会话切换后的重新加载)。"""
231
237
  self._history = []
238
+ self._recent_team_event_keys.clear()
232
239
  self._running_tools.clear()
233
240
  self._thinking_content = ""
234
241
  self._assistant_buffer = ""
@@ -294,6 +301,74 @@ class EventRenderer:
294
301
  HistoryEntry(entry_type="system", text=normalized, severity=severity)
295
302
  )
296
303
 
304
+ @staticmethod
305
+ def _team_event_key(
306
+ *,
307
+ agent_name: str,
308
+ from_agent: str,
309
+ to_agent: str | None,
310
+ message_type: str,
311
+ timestamp: str,
312
+ content_preview: str,
313
+ ) -> tuple[str, str, str, str, str, str]:
314
+ return (
315
+ str(agent_name or "").strip(),
316
+ str(from_agent or "").strip(),
317
+ str(to_agent or "").strip(),
318
+ str(message_type or "").strip(),
319
+ str(timestamp or "").strip(),
320
+ str(content_preview or "").strip(),
321
+ )
322
+
323
+ def append_team_message_event(
324
+ self,
325
+ *,
326
+ agent_name: str,
327
+ from_agent: str,
328
+ to_agent: str | None,
329
+ message_type: str,
330
+ content_preview: str,
331
+ timestamp: str,
332
+ ) -> bool:
333
+ event_key = self._team_event_key(
334
+ agent_name=agent_name,
335
+ from_agent=from_agent,
336
+ to_agent=to_agent,
337
+ message_type=message_type,
338
+ timestamp=timestamp,
339
+ content_preview=content_preview,
340
+ )
341
+ if event_key in self._recent_team_event_keys:
342
+ logger.debug(
343
+ "[team-diag] renderer_dedupe "
344
+ "agent=%r from=%r to=%r type=%r timestamp=%r preview=%r",
345
+ agent_name,
346
+ from_agent,
347
+ to_agent,
348
+ message_type,
349
+ timestamp,
350
+ content_preview,
351
+ )
352
+ return False
353
+
354
+ self._recent_team_event_keys.append(event_key)
355
+ target = to_agent or "[broadcast]"
356
+ preview_str = f' "{content_preview}"' if content_preview else ""
357
+ text = f"[team] {from_agent} → {target}: ({message_type}){preview_str}"
358
+ logger.debug(
359
+ "[team-diag] renderer_append "
360
+ "agent=%r from=%r to=%r type=%r timestamp=%r preview=%r history_len_before=%d",
361
+ agent_name,
362
+ from_agent,
363
+ to_agent,
364
+ message_type,
365
+ timestamp,
366
+ content_preview,
367
+ len(self._history),
368
+ )
369
+ self.append_system_message(text)
370
+ return True
371
+
297
372
  def append_elapsed_message(self, content: str) -> None:
298
373
  """追加一条灰色无前缀的计时统计行到 history scrollback."""
299
374
  normalized = content.strip()
@@ -941,6 +1016,22 @@ class EventRenderer:
941
1016
  self._history.append(
942
1017
  HistoryEntry(entry_type="system", text=text)
943
1018
  )
1019
+ case TeamMessageEvent(
1020
+ agent_name=agent_name,
1021
+ from_agent=from_agent,
1022
+ to_agent=to_agent,
1023
+ message_type=message_type,
1024
+ content_preview=content_preview,
1025
+ timestamp=timestamp,
1026
+ ):
1027
+ self.append_team_message_event(
1028
+ agent_name=agent_name,
1029
+ from_agent=from_agent,
1030
+ to_agent=to_agent,
1031
+ message_type=message_type,
1032
+ content_preview=content_preview,
1033
+ timestamp=timestamp,
1034
+ )
944
1035
  case _:
945
1036
  logger.debug("Unhandled event type: %s", type(event).__name__)
946
1037
 
@@ -15,7 +15,7 @@ from contextlib import suppress
15
15
  from pathlib import Path
16
16
  from typing import Any
17
17
 
18
- from prompt_toolkit.application import Application
18
+ from prompt_toolkit.application import Application, run_in_terminal
19
19
  from prompt_toolkit.completion import (
20
20
  ThreadedCompleter,
21
21
  merge_completers,
@@ -36,6 +36,7 @@ from comate_agent_sdk.agent.events import (
36
36
  PlanApprovalRequiredEvent,
37
37
  SessionInitEvent,
38
38
  StopEvent,
39
+ TeamMessageEvent,
39
40
  )
40
41
 
41
42
  from comate_cli.terminal_agent.animations import (
@@ -97,6 +98,43 @@ class TerminalAgentTUI(
97
98
  self._renderer = renderer
98
99
  self._rewind_store = RewindStore(session=self._session, project_root=Path.cwd())
99
100
 
101
+ # Team inbox 消息直连 scrollback(绕开 session_event_queue,实现实时显示)
102
+ _renderer = self._renderer
103
+ def _on_team_message(event: TeamMessageEvent) -> None:
104
+ logger.debug(
105
+ "[team-diag] tui_append "
106
+ "session_id=%r recv_agent=%r from=%r to=%r type=%r timestamp=%r "
107
+ "preview=%r history_len_before=%d",
108
+ self._session.session_id,
109
+ event.agent_name,
110
+ event.from_agent,
111
+ event.to_agent,
112
+ event.message_type,
113
+ event.timestamp,
114
+ event.content_preview,
115
+ len(self._renderer.history_entries()),
116
+ )
117
+ def _write() -> None:
118
+ _renderer.append_team_message_event(
119
+ agent_name=str(event.agent_name or ""),
120
+ from_agent=str(event.from_agent or ""),
121
+ to_agent=event.to_agent,
122
+ message_type=str(event.message_type or ""),
123
+ content_preview=str(event.content_preview or ""),
124
+ timestamp=str(event.timestamp or ""),
125
+ )
126
+ try:
127
+ from prompt_toolkit.application import get_app
128
+ from prompt_toolkit.application.dummy import DummyApplication
129
+ app = get_app()
130
+ if not isinstance(app, DummyApplication):
131
+ app.create_background_task(run_in_terminal(_write, in_executor=False))
132
+ return
133
+ except Exception:
134
+ pass
135
+ _write()
136
+ self._session._agent._team.team_message_event_callback = _on_team_message
137
+
100
138
  self._tool_panel_max_lines = read_env_int(
101
139
  "AGENT_SDK_TUI_TOOL_PANEL_MAX_LINES",
102
140
  4,
@@ -875,7 +875,7 @@ class CommandsMixin:
875
875
  agent_level = self._session._agent.level
876
876
  if agent_level:
877
877
  current_level = agent_level
878
- llm_levels = self._session._agent.options.llm_levels
878
+ llm_levels = self._session._agent._runtime_state.llm_levels
879
879
  except Exception:
880
880
  pass
881
881
 
@@ -220,6 +220,25 @@ class HistorySyncMixin:
220
220
  pending = self._pending_history_entries()
221
221
  if not pending:
222
222
  return
223
+ team_pending = [
224
+ entry
225
+ for entry in pending
226
+ if entry.entry_type == "system" and isinstance(entry.text, str) and entry.text.startswith("[team] ")
227
+ ]
228
+ if team_pending:
229
+ logger.debug(
230
+ "[team-diag] history_async_drain "
231
+ "session_id=%r pending_count=%d team_pending_count=%d printed_index=%d",
232
+ getattr(self._session, "session_id", ""),
233
+ len(pending),
234
+ len(team_pending),
235
+ self._printed_history_index,
236
+ )
237
+ for entry in team_pending:
238
+ logger.debug(
239
+ "[team-diag] history_async_entry text=%r",
240
+ entry.text,
241
+ )
223
242
  group = render_history_group(
224
243
  console,
225
244
  pending,
@@ -245,6 +264,25 @@ class HistorySyncMixin:
245
264
  pending = self._pending_history_entries()
246
265
  if not pending:
247
266
  return
267
+ team_pending = [
268
+ entry
269
+ for entry in pending
270
+ if entry.entry_type == "system" and isinstance(entry.text, str) and entry.text.startswith("[team] ")
271
+ ]
272
+ if team_pending:
273
+ logger.debug(
274
+ "[team-diag] history_sync_drain "
275
+ "session_id=%r pending_count=%d team_pending_count=%d printed_index=%d",
276
+ getattr(self._session, "session_id", ""),
277
+ len(pending),
278
+ len(team_pending),
279
+ self._printed_history_index,
280
+ )
281
+ for entry in team_pending:
282
+ logger.debug(
283
+ "[team-diag] history_sync_entry text=%r",
284
+ entry.text,
285
+ )
248
286
  group = render_history_group(
249
287
  console,
250
288
  pending,
@@ -259,9 +297,30 @@ class HistorySyncMixin:
259
297
  entries = self._renderer.history_entries()
260
298
  if self._printed_history_index >= len(entries):
261
299
  return []
300
+ previous_index = self._printed_history_index
262
301
  pending = entries[self._printed_history_index :]
263
302
  self._printed_history_index = len(entries)
264
303
  # 根据 _show_thinking 开关过滤 thinking 条目
265
304
  if not getattr(self, "_show_thinking", False):
266
305
  pending = [e for e in pending if e.entry_type != "thinking"]
306
+ team_pending = [
307
+ entry
308
+ for entry in pending
309
+ if entry.entry_type == "system" and isinstance(entry.text, str) and entry.text.startswith("[team] ")
310
+ ]
311
+ if team_pending:
312
+ logger.debug(
313
+ "[team-diag] history_pending "
314
+ "session_id=%r previous_index=%d new_index=%d total_entries=%d team_pending_count=%d",
315
+ getattr(self._session, "session_id", ""),
316
+ previous_index,
317
+ self._printed_history_index,
318
+ len(entries),
319
+ len(team_pending),
320
+ )
321
+ for entry in team_pending:
322
+ logger.debug(
323
+ "[team-diag] history_pending_entry text=%r",
324
+ entry.text,
325
+ )
267
326
  return pending
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "comate-cli"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  description = "Comate terminal CLI built on comate-agent-sdk"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -10,6 +10,7 @@ from comate_agent_sdk.agent.events import (
10
10
  StepCompleteEvent,
11
11
  StopEvent,
12
12
  SubagentProgressEvent,
13
+ TeamMessageEvent,
13
14
  TextEvent,
14
15
  TaskUpdatedEvent,
15
16
  ToolCallEvent,
@@ -34,6 +35,74 @@ def _task_payload(size: int) -> list[dict[str, str]]:
34
35
 
35
36
 
36
37
  class TestEventRenderer(unittest.TestCase):
38
+ def test_team_message_event_deduplicates_identical_event(self) -> None:
39
+ renderer = EventRenderer()
40
+ event = TeamMessageEvent(
41
+ agent_name="girlfriend",
42
+ from_agent="boyfriend",
43
+ to_agent="girlfriend",
44
+ message_type="message",
45
+ content_preview="where are you",
46
+ timestamp="2026-03-19T16:00:00+00:00",
47
+ )
48
+
49
+ renderer.handle_event(event)
50
+ renderer.handle_event(event)
51
+
52
+ system_entries = [e for e in renderer.history_entries() if e.entry_type == "system"]
53
+ self.assertEqual(len(system_entries), 1)
54
+ self.assertIn("[team] boyfriend → girlfriend: (message)", str(system_entries[0].text))
55
+
56
+ def test_team_message_event_keeps_distinct_timestamps(self) -> None:
57
+ renderer = EventRenderer()
58
+ renderer.handle_event(
59
+ TeamMessageEvent(
60
+ agent_name="girlfriend",
61
+ from_agent="boyfriend",
62
+ to_agent="girlfriend",
63
+ message_type="message",
64
+ content_preview="where are you",
65
+ timestamp="2026-03-19T16:00:00+00:00",
66
+ )
67
+ )
68
+ renderer.handle_event(
69
+ TeamMessageEvent(
70
+ agent_name="girlfriend",
71
+ from_agent="boyfriend",
72
+ to_agent="girlfriend",
73
+ message_type="message",
74
+ content_preview="where are you",
75
+ timestamp="2026-03-19T16:00:01+00:00",
76
+ )
77
+ )
78
+
79
+ system_entries = [e for e in renderer.history_entries() if e.entry_type == "system"]
80
+ self.assertEqual(len(system_entries), 2)
81
+
82
+ def test_direct_append_and_event_handle_share_team_dedupe(self) -> None:
83
+ renderer = EventRenderer()
84
+ renderer.append_team_message_event(
85
+ agent_name="girlfriend",
86
+ from_agent="boyfriend",
87
+ to_agent="girlfriend",
88
+ message_type="message",
89
+ content_preview="where are you",
90
+ timestamp="2026-03-19T16:00:00+00:00",
91
+ )
92
+ renderer.handle_event(
93
+ TeamMessageEvent(
94
+ agent_name="girlfriend",
95
+ from_agent="boyfriend",
96
+ to_agent="girlfriend",
97
+ message_type="message",
98
+ content_preview="where are you",
99
+ timestamp="2026-03-19T16:00:00+00:00",
100
+ )
101
+ )
102
+
103
+ system_entries = [e for e in renderer.history_entries() if e.entry_type == "system"]
104
+ self.assertEqual(len(system_entries), 1)
105
+
37
106
  def test_compaction_result_event_renders_skip_message(self) -> None:
38
107
  renderer = EventRenderer()
39
108
  renderer.start_turn()
@@ -364,7 +364,7 @@ wheels = [
364
364
 
365
365
  [[package]]
366
366
  name = "comate-agent-sdk"
367
- version = "0.2.1"
367
+ version = "0.2.3"
368
368
  source = { editable = "../../" }
369
369
  dependencies = [
370
370
  { name = "aiohttp" },
@@ -427,7 +427,7 @@ dev = [
427
427
 
428
428
  [[package]]
429
429
  name = "comate-cli"
430
- version = "0.2.1"
430
+ version = "0.2.3"
431
431
  source = { editable = "." }
432
432
  dependencies = [
433
433
  { name = "comate-agent-sdk" },
@@ -1,10 +0,0 @@
1
- # 测试记忆
2
-
3
- 这是一个测试用的 memory 文件。
4
-
5
- ## 测试内容
6
- - 条目 1:测试条目
7
- - 条目 2:另一个测试
8
-
9
- ## 记录时间
10
- 2026-03-01
File without changes
File without changes