diff --git a/.gitignore b/.gitignore
index a5f491f..cdbfc4f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,3 +37,4 @@ traceback.log
htmlcov/
.coverage
coverage.xml
+.hypothesis/
diff --git a/.hypothesis/unicode_data/14.0.0/codec-utf-8.json.gz b/.hypothesis/unicode_data/14.0.0/codec-utf-8.json.gz
index 20bc621..47e7f93 100644
Binary files a/.hypothesis/unicode_data/14.0.0/codec-utf-8.json.gz and b/.hypothesis/unicode_data/14.0.0/codec-utf-8.json.gz differ
diff --git a/GramAddict/core/behaviors/obstacle_guard.py b/GramAddict/core/behaviors/obstacle_guard.py
index 6549b1e..422fb8c 100644
--- a/GramAddict/core/behaviors/obstacle_guard.py
+++ b/GramAddict/core/behaviors/obstacle_guard.py
@@ -46,7 +46,7 @@ class ObstacleGuardPlugin(BehaviorPlugin):
if situation == SituationType.OBSTACLE_MODAL:
if misses >= 2:
logger.error("🛑 [ObstacleGuard] Failed to recover from OBSTACLE_MODAL after multiple attempts.")
- sae.unlearn_current_state()
+ sae.unlearn_current_state(xml)
dump_ui_state(ctx.device, f"fatal_obstacle_{ctx.session_state.job_target}")
return BehaviorResult(executed=True, should_skip=True, metadata={"return_code": "CONTEXT_LOST"})
diff --git a/GramAddict/core/behaviors/resonance_evaluator.py b/GramAddict/core/behaviors/resonance_evaluator.py
index cb8f8be..2677876 100644
--- a/GramAddict/core/behaviors/resonance_evaluator.py
+++ b/GramAddict/core/behaviors/resonance_evaluator.py
@@ -49,7 +49,8 @@ class ResonanceEvaluatorPlugin(BehaviorPlugin):
tele = ctx.cognitive_stack.get("telepathic")
if tele:
logger.info("✨ [Resonance] Performing visual vibe check...")
- vibe = tele.evaluate_post_vibe()
+ persona_interests = getattr(ctx.configs.args, "persona_interests", [])
+ vibe = tele.evaluate_post_vibe(ctx.device, persona_interests)
vibe_score = vibe.get("quality_score", 5) / 10.0
if vibe.get("matches_niche"):
vibe_score = min(1.0, vibe_score + 0.2)
diff --git a/GramAddict/core/stealth_typing.py b/GramAddict/core/stealth_typing.py
index 49ed81a..dc541d5 100644
--- a/GramAddict/core/stealth_typing.py
+++ b/GramAddict/core/stealth_typing.py
@@ -5,7 +5,7 @@ from time import sleep
logger = logging.getLogger(__name__)
-def ghost_type(device, text: str):
+def ghost_type(device, text: str, speed: str = "normal"):
"""
Tesla Stealth Ghost Keyboard.
Bypasses UIAutomator virtual IME completely and sends raw Native InputEvents.
@@ -48,6 +48,10 @@ def ghost_type(device, text: str):
else:
_adb_inject_text(device, chunk)
+ if speed == "fast":
+ sleep(random.uniform(0.01, 0.05))
+ continue
+
# Realistic pause between semantic bursts (humans think while typing)
if chunk.endswith((" ", ".", ",", "!", "?")):
sleep(random.uniform(0.2, 0.5))
diff --git a/test_config.yml b/test_config.yml
index 6d01cd9..e87b005 100644
--- a/test_config.yml
+++ b/test_config.yml
@@ -93,7 +93,7 @@ limits:
speed_multiplier: 1.0
# ── Infrastructure & System ──
-device: 192.168.1.206:40505
+device: 192.168.1.206:36369
app-id: com.instagram.android
debug: true
blank_start: true
diff --git a/tests/integration/test_dm_loop.py b/tests/integration/test_dm_loop.py
index f10d7f4..ae56d7c 100644
--- a/tests/integration/test_dm_loop.py
+++ b/tests/integration/test_dm_loop.py
@@ -58,7 +58,7 @@ def test_dm_engine_basic_loop(dm_mock_dependencies):
with (
patch("GramAddict.core.bot_flow.sleep"),
patch("GramAddict.core.bot_flow._humanized_click") as mock_click,
- patch("GramAddict.core.stealth_typing.ghost_type") as mock_ghost_type,
+ patch("GramAddict.core.stealth_typing.ghost_type", autospec=True) as mock_ghost_type,
patch("GramAddict.core.llm_provider.query_llm", return_value={"response": "I am good, thanks!"}),
):
res = _run_zero_latency_dm_loop(
diff --git a/tests/unit/behaviors/test_obstacle_guard.py b/tests/unit/behaviors/test_obstacle_guard.py
index ff282e4..894a7f3 100644
--- a/tests/unit/behaviors/test_obstacle_guard.py
+++ b/tests/unit/behaviors/test_obstacle_guard.py
@@ -1,4 +1,4 @@
-from unittest.mock import MagicMock, patch
+from unittest.mock import MagicMock, create_autospec, patch
import pytest
@@ -102,11 +102,14 @@ class TestObstacleGuardPlugin:
@patch("GramAddict.core.behaviors.obstacle_guard.dump_ui_state")
@patch("GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine")
def test_execute_obstacle_miss_3_abort(self, mock_sae, mock_dump, obstacle_guard, mock_context):
- mock_instance = MagicMock()
+ from GramAddict.core.situational_awareness import SituationalAwarenessEngine
+
+ mock_instance = create_autospec(SituationalAwarenessEngine, instance=True)
mock_instance.perceive.return_value = SituationType.OBSTACLE_MODAL
mock_sae.get_instance.return_value = mock_instance
- mock_context.device.dump_hierarchy.return_value = "dummy"
+ xml_content = "dummy"
+ mock_context.device.dump_hierarchy.return_value = xml_content
mock_context.device.get_info.return_value = {"displayWidth": 1080, "displayHeight": 2400}
mock_context.shared_state["consecutive_marker_misses"] = 2
mock_context.session_state.job_target = "Feed"
@@ -115,5 +118,5 @@ class TestObstacleGuardPlugin:
assert result.executed is True
assert result.metadata.get("return_code") == "CONTEXT_LOST"
- mock_instance.unlearn_current_state.assert_called_once()
+ mock_instance.unlearn_current_state.assert_called_once_with(xml_content)
mock_dump.assert_called_once()
diff --git a/tests/unit/behaviors/test_resonance_evaluator.py b/tests/unit/behaviors/test_resonance_evaluator.py
index 8594475..3daaeff 100644
--- a/tests/unit/behaviors/test_resonance_evaluator.py
+++ b/tests/unit/behaviors/test_resonance_evaluator.py
@@ -1,4 +1,5 @@
-from unittest.mock import MagicMock, patch
+from unittest import mock
+from unittest.mock import MagicMock, create_autospec, patch
import pytest
@@ -63,10 +64,12 @@ class TestResonanceEvaluatorPlugin:
@patch("GramAddict.core.behaviors.resonance_evaluator.humanized_scroll")
@patch("GramAddict.core.behaviors.resonance_evaluator.sleep")
def test_execute_visual_vibe_check(self, mock_sleep, mock_scroll, resonance_evaluator, mock_context):
+ from GramAddict.core.telepathic_engine import TelepathicEngine
+
mock_context.configs.args.visual_vibe_check_percentage = 100
mock_context.cognitive_stack["resonance"].calculate_resonance.return_value = 0.5
- mock_tele = MagicMock()
+ mock_tele = create_autospec(TelepathicEngine, instance=True)
mock_tele.evaluate_post_vibe.return_value = {"quality_score": 10, "matches_niche": True}
mock_context.cognitive_stack["telepathic"] = mock_tele
@@ -77,4 +80,4 @@ class TestResonanceEvaluatorPlugin:
assert result.should_skip is False
# res_score = 0.5 * 0.3 + 1.0 * 0.7 = 0.15 + 0.70 = 0.85
assert round(mock_context.shared_state["res_score"], 2) == 0.85
- mock_tele.evaluate_post_vibe.assert_called_once()
+ mock_tele.evaluate_post_vibe.assert_called_once_with(mock_context.device, mock.ANY)