mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-15 03:12:21 +02:00
fix(skills): inject published skills when Auto-approve is off (#6643)
With Auto-approve OFF the agent loop asked get_relevant_skills for min_confidence=2.0, meant as 'published only'. The gate applies the confidence check to published skills too, and confidence is 0-1, so no user skill was ever injected, published or not. The explicit-activation path already treats OFF as status == published. Add a published_only flag to get_relevant_skills and pass it from both callers that used the sentinel (relevance injection and the skill preflight). Auto-approve ON is unchanged: a skill still needs a passing audit and the confidence threshold. Fixes #6634
This commit is contained in:
@@ -745,6 +745,7 @@ class SkillsManager:
|
||||
min_confidence: float = 0.0,
|
||||
available_toolsets: Optional[Iterable[str]] = None,
|
||||
platform: Optional[str] = None,
|
||||
published_only: bool = False,
|
||||
) -> List[Dict]:
|
||||
if skills is None:
|
||||
skills = self.load_all()
|
||||
@@ -775,9 +776,15 @@ class SkillsManager:
|
||||
if not skill.get("platforms") or platform in skill.get("platforms", [])
|
||||
]
|
||||
# Prompt injection is fail-closed for user skills. Built-ins are
|
||||
# shipped procedures; every other skill needs a passing audit and a
|
||||
# confidence score at the user's current threshold.
|
||||
if min_confidence > 0:
|
||||
# shipped procedures. With Auto-approve OFF (published_only), the
|
||||
# user's publish is the approval; otherwise a skill needs a passing
|
||||
# audit and a confidence score at the user's current threshold.
|
||||
if published_only:
|
||||
skills = [
|
||||
s for s in skills
|
||||
if s.get("source") == "builtin" or s.get("status") == "published"
|
||||
]
|
||||
elif min_confidence > 0:
|
||||
def _passes(s):
|
||||
if s.get("source") == "builtin":
|
||||
return True
|
||||
|
||||
+8
-4
@@ -10325,9 +10325,10 @@ def _has_matching_skill_for_turn(
|
||||
))))
|
||||
if max_items == 0:
|
||||
return False
|
||||
published_only = not prefs.get("auto_approve_skills", True)
|
||||
min_confidence = (
|
||||
2.0
|
||||
if not prefs.get("auto_approve_skills", True)
|
||||
0.0
|
||||
if published_only
|
||||
else float(prefs.get(
|
||||
"skill_min_confidence",
|
||||
get_setting("skill_autosave_min_confidence", 0.85),
|
||||
@@ -10344,6 +10345,7 @@ def _has_matching_skill_for_turn(
|
||||
threshold=0.25,
|
||||
max_items=max_items,
|
||||
min_confidence=min_confidence,
|
||||
published_only=published_only,
|
||||
))
|
||||
except Exception as exc:
|
||||
logger.debug("skill preflight failed (non-fatal): %s", exc)
|
||||
@@ -14952,8 +14954,9 @@ def _build_system_prompt(
|
||||
# confidence threshold. Approve OFF → published-only (no draft
|
||||
# passes). Approve ON → drafts at/above the chosen confidence
|
||||
# (0 = "All"). Falls back to the global default setting.
|
||||
if not _prefs.get("auto_approve_skills", True):
|
||||
_skill_min_conf = 2.0 # nothing draft clears it → published only
|
||||
_skill_published_only = not _prefs.get("auto_approve_skills", True)
|
||||
if _skill_published_only:
|
||||
_skill_min_conf = 0.0 # published_only decides eligibility
|
||||
else:
|
||||
try:
|
||||
_skill_min_conf = float(_prefs.get(
|
||||
@@ -14976,6 +14979,7 @@ def _build_system_prompt(
|
||||
max_items=_skill_max_injected,
|
||||
min_confidence=_skill_min_conf,
|
||||
available_toolsets=relevant_tools,
|
||||
published_only=_skill_published_only,
|
||||
) if _skill_max_injected > 0 else []
|
||||
else:
|
||||
# Explicit client activation chooses which eligible skill
|
||||
|
||||
@@ -133,3 +133,20 @@ def test_unaudited_or_below_threshold_published_skill_is_not_injected(tmp_path):
|
||||
assert not sm.get_relevant_skills(
|
||||
"premature skill", min_confidence=0.80,
|
||||
)
|
||||
|
||||
|
||||
def test_auto_approve_off_injects_published_skills_without_an_audit(tmp_path):
|
||||
# #6634: OFF used min_confidence=2.0, which no 0-1 confidence clears, so
|
||||
# every user skill was rejected. Publishing is the approval when OFF.
|
||||
(tmp_path / "skills").mkdir()
|
||||
for name, status in (("premature-skill", "published"), ("draft-skill", "draft")):
|
||||
path = _write_skill_md(tmp_path / "skills", name)
|
||||
text = path.read_text(encoding="utf-8").replace("audit_verdict: pass\n", "")
|
||||
path.write_text(text.replace("status: published", f"status: {status}"), encoding="utf-8")
|
||||
sm = SkillsManager(str(tmp_path))
|
||||
|
||||
query = "premature skill draft skill"
|
||||
# threshold=0 isolates the eligibility gate from relevance scoring.
|
||||
assert _names(sm.get_relevant_skills(query, threshold=0.0, published_only=True)) == {"premature-skill"}
|
||||
# Auto-approve ON still needs a passing audit, as before.
|
||||
assert not sm.get_relevant_skills(query, threshold=0.0, min_confidence=0.85)
|
||||
|
||||
Reference in New Issue
Block a user