diff --git a/.claude/hooks/.diagnostics_state.json b/.claude/hooks/.diagnostics_state.json deleted file mode 100644 index 16a76768..00000000 --- a/.claude/hooks/.diagnostics_state.json +++ /dev/null @@ -1 +0,0 @@ -{"file": "/home/patrick/Projects/AIPass/src/aipass/drone/apps/modules/git_module.py", "errors": [{"line": 671, "message": "E501: Line too long (193 > 120)"}]} \ No newline at end of file diff --git a/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py b/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py index 807d0ed7..6e91faeb 100644 --- a/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py +++ b/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py @@ -535,11 +535,15 @@ class MonitoringFileHandler(FileSystemEventHandler): # CLAUDE CODE AGENT ACTIVITY PARSING (JSONL sessions) # ========================================================================= + _MAX_AGENT_EVENTS_PER_BATCH = 3 + def _parse_agent_activity(self, file_path, branch): """Parse Claude Code session JSONL to show agent actions. Returns True if an event was emitted (or deduped), False on failure. - Iterates forward through new lines, emitting all distinct actions. + Iterates forward through all new lines (for model extraction), but only + emits the last few distinct actions to prevent doom-scrolling when large + batches accumulate between polls or after compaction resets. """ try: path_key = str(file_path) @@ -560,6 +564,9 @@ class MonitoringFileHandler(FileSystemEventHandler): if not lines: return True + pending_actions = [] + last_seen = self._last_agent_action.get(path_key) + for line in lines: try: entry = _json.loads(line) @@ -575,10 +582,12 @@ class MonitoringFileHandler(FileSystemEventHandler): if not action_text: continue - if self._last_agent_action.get(path_key) == action_text: + if action_text == last_seen: continue - self._last_agent_action[path_key] = action_text + last_seen = action_text + pending_actions.append(action_text) + for action_text in pending_actions[-self._MAX_AGENT_EVENTS_PER_BATCH :]: tagged_branch = self._tag_branch_with_model(path_key, branch) evt = MonitoringEvent( priority=1, @@ -591,6 +600,9 @@ class MonitoringFileHandler(FileSystemEventHandler): if self._event_queue: self._event_queue.enqueue(evt) + if pending_actions: + self._last_agent_action[path_key] = pending_actions[-1] + return True except Exception as e: diff --git a/src/aipass/prax/tests/test_filesystem_handler.py b/src/aipass/prax/tests/test_filesystem_handler.py index 0268aef4..32060233 100644 --- a/src/aipass/prax/tests/test_filesystem_handler.py +++ b/src/aipass/prax/tests/test_filesystem_handler.py @@ -1763,6 +1763,56 @@ class TestParseAgentActivity: result = handler._parse_agent_activity(mock_path, "PRAX") assert result is True + def test_batch_cap_limits_emitted_events(self): + """Should only emit last N events when batch exceeds cap.""" + mod, _, _, _, queue = _import_filesystem_handler() + handler = _make_handler(mod, queue=queue) + mock_path = MagicMock() + mock_path.__str__ = MagicMock(return_value="/fake/claude.jsonl") + mock_path.name = "claude.jsonl" + entries = [] + tools = ["Read", "Edit", "Write", "Grep", "Glob", "Bash"] + for i, tool in enumerate(tools): + entries.append( + { + "type": "assistant", + "message": {"content": [{"type": "tool_use", "name": tool, "input": {"file_path": f"f{i}.py"}}]}, + } + ) + data = "\n".join(json.dumps(e) for e in entries) + "\n" + mock_path.stat.return_value.st_size = len(data) + with patch("builtins.open", _mopen(read_data=data)): + result = handler._parse_agent_activity(mock_path, "PRAX") + assert result is True + cap = mod.MonitoringFileHandler._MAX_AGENT_EVENTS_PER_BATCH + assert queue.enqueue.call_count == cap + + def test_batch_cap_emits_tail_not_head(self): + """Should emit the last actions in a large batch, not the first.""" + mod, mock_eq, _, _, queue = _import_filesystem_handler() + handler = _make_handler(mod, queue=queue) + mock_path = MagicMock() + mock_path.__str__ = MagicMock(return_value="/fake/claude.jsonl") + mock_path.name = "claude.jsonl" + entries = [] + for i in range(6): + entries.append( + { + "type": "assistant", + "message": {"content": [{"type": "tool_use", "name": f"Tool{i}", "input": {}}]}, + } + ) + data = "\n".join(json.dumps(e) for e in entries) + "\n" + mock_path.stat.return_value.st_size = len(data) + with patch("builtins.open", _mopen(read_data=data)): + handler._parse_agent_activity(mock_path, "PRAX") + cap = mod.MonitoringFileHandler._MAX_AGENT_EVENTS_PER_BATCH + assert queue.enqueue.call_count == cap + last_call_kwargs = mock_eq.MonitoringEvent.call_args_list[-1][1] + assert "Tool5" in last_call_kwargs["message"] + first_call_kwargs = mock_eq.MonitoringEvent.call_args_list[-cap][1] + assert "Tool3" in first_call_kwargs["message"] + # ============================================= # CHECK COMMAND INDICATOR TESTS