2 Commits

6 changed files with 121 additions and 5 deletions

View File

@@ -184,6 +184,9 @@ def start_bot(**kwargs):
active_inference = ActiveInferenceEngine(username)
# Core Autonomous Engines
from GramAddict.core.goap import GoalExecutor
GoalExecutor.get_instance(device, username)
zero_engine = ZeroLatencyEngine(device)
nav_graph = QNavGraph(device)
growth_brain = GrowthBrain(username, persona_interests=persona_interests)

View File

@@ -178,6 +178,8 @@ class ScreenIdentity:
return ScreenType.FOLLOW_LIST
if "profile_header_container" in ids:
if selected_tab == "profile_tab":
return ScreenType.OWN_PROFILE
return ScreenType.OTHER_PROFILE
# Reels structural markers — present even when Instagram hides the tab bar

View File

@@ -205,11 +205,13 @@ class QdrantBase:
point_id = self.generate_uuid(seed_string)
try:
self.client.delete(collection_name=self.collection_name, points_selector=[point_id])
logger.info(
f"🗑️ [Qdrant] Purged poisoned memory vector from {self.collection_name} (UUID: {point_id[:8]}...)",
extra={"color": "\x1b[31m"},
)
res = self.client.retrieve(collection_name=self.collection_name, ids=[point_id])
if res:
self.client.delete(collection_name=self.collection_name, points_selector=[point_id])
logger.info(
f"🗑️ [Qdrant] Purged poisoned memory vector from {self.collection_name} (UUID: {point_id[:8]}...)",
extra={"color": "\x1b[31m"},
)
return True
except Exception as e:
self._handle_error(e, f"Failed to delete point {point_id}")

View File

@@ -0,0 +1,28 @@
from unittest.mock import MagicMock, patch
import pytest
def test_bot_flow_goal_executor_init_order():
# We want to prove that without explicit get_instance(device, username),
# QNavGraph(device) initializes GoalExecutor with an empty username.
# We have to reset GoalExecutor singleton first
from GramAddict.core.goap import GoalExecutor
from GramAddict.core.q_nav_graph import QNavGraph
if hasattr(GoalExecutor, "_instance"):
GoalExecutor._instance = None
device = MagicMock()
# Action: Instantiate QNavGraph
# Without the fix, this will initialize GoalExecutor with bot_username=""
# and PathMemory will get bound to "goap_paths_v1"
with patch("GramAddict.core.qdrant_memory.QdrantClient") as mock_qdrant:
# FIX: Pre-initialize with username (as done in bot_flow.py)
executor_pre = GoalExecutor.get_instance(device, "marisaundmarc")
nav_graph = QNavGraph(device)
executor = GoalExecutor.get_instance(device, "marisaundmarc")
# Now it should be bound correctly
assert executor.path_memory._db.collection_name == "goap_paths_v1_marisaundmarc", "PathMemory collection name does not contain the username suffix! Initialization leak occurred."

View File

@@ -0,0 +1,42 @@
from unittest.mock import MagicMock, patch, PropertyMock
from GramAddict.core.qdrant_memory import UIMemoryDB
def test_delete_point_logs_only_if_exists(caplog):
# Setup
db = UIMemoryDB()
type(db).is_connected = PropertyMock(return_value=True)
db.client = MagicMock()
db.collection_name = "test_collection"
# RED: Force the mock to return an empty list (point does NOT exist)
db.client.retrieve.return_value = []
# Action
result = db.delete_point("fake_seed")
# Assertions
assert result is True # Should still return True as it didn't crash
# It should NOT call delete if it wasn't found
db.client.delete.assert_not_called()
# It should NOT log the "Purged poisoned memory" line
assert "Purged poisoned memory vector" not in caplog.text
def test_delete_point_logs_if_found(caplog):
# Setup
db = UIMemoryDB()
type(db).is_connected = PropertyMock(return_value=True)
db.client = MagicMock()
db.collection_name = "test_collection"
# RED: Force the mock to return a result (point DOES exist)
db.client.retrieve.return_value = [{"id": "some_uuid"}]
# Action
with patch("GramAddict.core.qdrant_memory.logger") as mock_logger:
result = db.delete_point("fake_seed")
# Assertions
assert result is True
db.client.delete.assert_called_once()
mock_logger.info.assert_called_once()
assert "Purged poisoned memory vector" in mock_logger.info.call_args[0][0]

View File

@@ -0,0 +1,39 @@
import pytest
from GramAddict.core.perception.screen_identity import ScreenIdentity, ScreenType
def test_screen_identity_own_profile_vs_other_profile():
identity = ScreenIdentity("marisaundmarc")
# When we are on our OWN profile, 'profile_tab' is selected,
# but 'profile_header_container' is ALSO present.
# The bug is that 'profile_header_container' shadows 'profile_tab' selected=True.
# Let's create an XML dump that mimics this scenario:
own_profile_xml = """<?xml version="1.0" encoding="utf-8"?>
<hierarchy>
<node package="com.instagram.android" resource-id="com.instagram.android:id/profile_header_container" text="" content-desc="" clickable="false" bounds="[0,0][100,100]" />
<node package="com.instagram.android" resource-id="com.instagram.android:id/profile_tab" selected="true" text="" content-desc="Profile" clickable="true" bounds="[0,0][100,100]" />
</hierarchy>
"""
result = identity.identify(own_profile_xml)
assert result["screen_type"] == ScreenType.OWN_PROFILE, "Failed! own profile was classified as OTHER_PROFILE because profile_header_container shadowed it."
def test_screen_identity_other_profile_vs_own_profile():
identity = ScreenIdentity("marisaundmarc")
# When we are on someone ELSE's profile, 'profile_tab' is NOT selected
# (or maybe 'feed_tab' or 'search_tab' is selected, or none).
# And 'profile_header_container' is present.
other_profile_xml = """<?xml version="1.0" encoding="utf-8"?>
<hierarchy>
<node package="com.instagram.android" resource-id="com.instagram.android:id/profile_header_container" text="" content-desc="" clickable="false" bounds="[0,0][100,100]" />
<node package="com.instagram.android" resource-id="com.instagram.android:id/feed_tab" selected="true" text="" content-desc="Home" clickable="true" bounds="[0,0][100,100]" />
</hierarchy>
"""
result = identity.identify(other_profile_xml)
assert result["screen_type"] == ScreenType.OTHER_PROFILE, "Failed! other profile was not classified as OTHER_PROFILE."