Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
"""Regression: a bot profile's system prompt must reflect ITS OWN skills/home,
|
||||
never the launch (default) profile's — even when the agent build runs on a
|
||||
thread that did not bind the HERMES_HOME ContextVar.
|
||||
|
||||
Root cause this guards (confirmed empirically): ContextVars do not propagate
|
||||
into ``threading.Thread``. ``build_skills_system_prompt`` and the
|
||||
active-profile line resolved the home via the ambient ``get_hermes_home()``,
|
||||
so an unbound build thread fell back to ``~/.hermes`` (default) and leaked
|
||||
default's full skills index + "Active Hermes profile: default" into a bot's
|
||||
prompt, while the live ``skills_list()`` (re-bound per turn) correctly
|
||||
showed the bot's real, empty set. The agent now resolves its own home from
|
||||
its ``_session_db.db_path`` and passes it explicitly.
|
||||
"""
|
||||
|
||||
import re
|
||||
import threading
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def _skills_body(prompt: str) -> str:
|
||||
m = re.search(r"<available_skills>(.*?)</available_skills>", prompt, re.DOTALL)
|
||||
return (m.group(1).strip() if m else "")
|
||||
|
||||
|
||||
def test_skills_prompt_scoped_to_override_not_ambient_home(tmp_path, monkeypatch):
|
||||
"""An explicit skills_dir_override wins over ambient HERMES_HOME, on a
|
||||
bare thread with no override bound."""
|
||||
from agent import prompt_builder
|
||||
|
||||
# A "default" home WITH skills (the thing that must NOT leak).
|
||||
default_home = tmp_path / "default"
|
||||
default_skills = default_home / "skills" / "general" / "leaky-skill"
|
||||
default_skills.mkdir(parents=True)
|
||||
(default_skills / "SKILL.md").write_text(
|
||||
"---\nname: leaky-skill\ndescription: should never appear in a bot prompt\n---\nbody\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# An empty bot profile (no skills dir at all).
|
||||
bot_skills = tmp_path / "profiles" / "emptybot" / "skills"
|
||||
|
||||
# Bind ambient home to default (mimics a build thread that lost the
|
||||
# bot's override and fell back to launch).
|
||||
monkeypatch.setenv("HERMES_HOME", str(default_home))
|
||||
prompt_builder.clear_skills_system_prompt_cache(clear_snapshot=False)
|
||||
|
||||
result = {}
|
||||
|
||||
def build():
|
||||
# No set_hermes_home_override on THIS thread — ambient resolves to
|
||||
# default. The override arg must still scope to the empty bot.
|
||||
result["bot"] = _skills_body(
|
||||
prompt_builder.build_skills_system_prompt(skills_dir_override=bot_skills)
|
||||
)
|
||||
|
||||
t = threading.Thread(target=build)
|
||||
t.start()
|
||||
t.join()
|
||||
|
||||
assert result["bot"] == "", (
|
||||
"empty bot profile leaked skills from the ambient (default) home: "
|
||||
+ result["bot"][:200]
|
||||
)
|
||||
|
||||
|
||||
def test_agent_home_resolves_from_session_db_path(tmp_path):
|
||||
"""The agent's own home is read from its session_db, independent of any
|
||||
ContextVar."""
|
||||
from agent import system_prompt
|
||||
|
||||
bot_home = tmp_path / "profiles" / "mybot"
|
||||
bot_home.mkdir(parents=True)
|
||||
|
||||
class _DB:
|
||||
db_path = bot_home / "state.db"
|
||||
|
||||
class _Agent:
|
||||
_session_db = _DB()
|
||||
|
||||
assert system_prompt._agent_home(_Agent()) == bot_home
|
||||
assert system_prompt._agent_skills_dir(_Agent()) == bot_home / "skills"
|
||||
|
||||
|
||||
def test_agent_home_none_without_session_db():
|
||||
from agent import system_prompt
|
||||
|
||||
class _Agent:
|
||||
_session_db = None
|
||||
|
||||
assert system_prompt._agent_home(_Agent()) is None
|
||||
assert system_prompt._agent_skills_dir(_Agent()) is None
|
||||
|
||||
|
||||
def test_profile_name_correct_on_bound_profile_session(tmp_path, monkeypatch):
|
||||
"""Regression for the fix-of-the-fix: on a CORRECTLY bound profile session
|
||||
the ambient home IS the profile dir, so deriving the profile name with
|
||||
``get_hermes_home()/profiles`` as the root would never match and every
|
||||
profile would misreport as \"default\". The name must derive from the
|
||||
hermes ROOT (get_default_hermes_root)."""
|
||||
from agent import system_prompt
|
||||
|
||||
bot_home = tmp_path / "profiles" / "mybot"
|
||||
bot_home.mkdir(parents=True)
|
||||
|
||||
# Bound session: HERMES_HOME env points at the profile dir itself.
|
||||
monkeypatch.setenv("HERMES_HOME", str(bot_home))
|
||||
|
||||
assert system_prompt._profile_name_for_home(bot_home) == "mybot"
|
||||
|
||||
|
||||
def test_profile_name_default_when_home_is_root(tmp_path, monkeypatch):
|
||||
from agent import system_prompt
|
||||
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
||||
assert system_prompt._profile_name_for_home(tmp_path) == "default"
|
||||
|
||||
|
||||
def test_profile_name_correct_when_ambient_is_another_profile(tmp_path, monkeypatch):
|
||||
"""CodeRabbit case: agent belongs to profile A while the ambient home is
|
||||
bound to profile B. The root must derive independently of the ambient
|
||||
home, so A still resolves as 'mybot' (not 'default', and never 'other')."""
|
||||
from agent import system_prompt
|
||||
|
||||
bot_home = tmp_path / "profiles" / "mybot"
|
||||
bot_home.mkdir(parents=True)
|
||||
other_home = tmp_path / "profiles" / "other"
|
||||
other_home.mkdir(parents=True)
|
||||
|
||||
monkeypatch.setenv("HERMES_HOME", str(other_home))
|
||||
|
||||
assert system_prompt._profile_name_for_home(bot_home) == "mybot"
|
||||
Reference in New Issue
Block a user