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.
- {comate_cli-0.2.2 → comate_cli-0.2.4}/.gitignore +1 -1
- {comate_cli-0.2.2 → comate_cli-0.2.4}/PKG-INFO +1 -1
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/app.py +1 -1
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/event_renderer.py +91 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui.py +39 -1
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui_parts/commands.py +1 -1
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui_parts/history_sync.py +59 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/pyproject.toml +1 -1
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_event_renderer.py +69 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/uv.lock +2 -2
- comate_cli-0.2.2/test_memory.md +0 -10
- {comate_cli-0.2.2 → comate_cli-0.2.4}/README.md +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/__init__.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/__main__.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/main.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/mcp_cli.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/__init__.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/animations.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/assistant_render.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/custom_slash_commands.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/env_utils.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/error_display.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/fragment_utils.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/history_printer.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/input_geometry.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/logging_adapter.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/logo.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/markdown_render.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/mention_completer.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/message_style.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/models.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/preflight.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/question_view.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/resume_selector.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/rewind_store.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/rpc_protocol.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/rpc_stdio.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/selection_menu.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/session_view.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/slash_commands.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/startup.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/status_bar.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/text_effects.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tips.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tool_view.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui_parts/key_bindings.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui_parts/render_panels.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/conftest.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_app_mcp_preload.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_app_preflight_gate.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_app_shutdown.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_app_usage_line.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_cli_project_root.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_compact_command_semantics.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_completion_context_activation.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_completion_status_panel.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_context_command.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_custom_slash_commands.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_history_sync.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_input_behavior.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_interrupt_exit_semantics.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_layout_coordinator.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_logging_adapter.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_logo.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_main_args.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_mcp_cli.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_mcp_slash_command.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_mention_completer.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_preflight.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_preflight_copilot.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_question_key_bindings.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_question_view.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_resume_selector.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_rewind_command_semantics.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_rewind_store.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_rpc_protocol.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_rpc_stdio_bridge.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_selection_menu.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_skills_slash_command.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_slash_argument_hint.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_slash_completer.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_slash_registry.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_status_bar.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_task_panel_key_bindings.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_tool_view.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_tui_elapsed_status.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_tui_mcp_init_gate.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_tui_paste_placeholder.py +0 -0
- {comate_cli-0.2.2 → comate_cli-0.2.4}/tests/test_tui_split_invariance.py +0 -0
|
@@ -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.
|
|
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.
|
|
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
|
|
@@ -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.
|
|
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.
|
|
430
|
+
version = "0.2.3"
|
|
431
431
|
source = { editable = "." }
|
|
432
432
|
dependencies = [
|
|
433
433
|
{ name = "comate-agent-sdk" },
|
comate_cli-0.2.2/test_memory.md
DELETED
|
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.2.2 → comate_cli-0.2.4}/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
|