Squash Odysseus development history

This commit is contained in:
pewdiepie-archdaemon
2026-09-11 06:04:19 +00:00
parent e5c99a5eee
commit 6ee6502010
2050 changed files with 538359 additions and 57745 deletions
+110 -20
View File
@@ -28,6 +28,10 @@ SKILL_EXTRACT_PROMPT = (
"(personal errands, a specific person/place/date, casual conversation).\n"
"- A pure question/answer or explanation with no transferable method.\n"
"- The agent failed, gave up, or the approach is not worth repeating.\n\n"
"- Routine use of an existing tool, or a generic checklist with no new discovery.\n"
"Prefer a specific successful workaround, an unexpected pitfall, or a verified "
"sequence that would save rediscovery. Preserve exact useful commands and "
"verification steps, but replace private identifiers and credentials with placeholders.\n\n"
"When (and only when) a genuine reusable procedure exists, return a JSON "
"object with:\n"
'- "title": short name (under 10 words)\n'
@@ -48,6 +52,77 @@ MIN_CONFIDENCE = 0.6
CONTEXT_WINDOW = 12
def _skill_dicts(skills):
for skill in skills or []:
if isinstance(skill, dict):
yield skill
def _has_duplicate_title(skills, title: str) -> bool:
wanted = title.lower()
for skill in _skill_dicts(skills):
existing = skill.get("title", "")
if isinstance(existing, str) and existing.lower() == wanted:
return True
return False
def _extract_json_object(text: str) -> Optional[dict]:
"""Best-effort extraction of a JSON object from an LLM response.
The response may be wrapped in code fences or surrounded by prose. Uses
json.JSONDecoder().raw_decode() to locate the boundaries of complete JSON
objects starting at each '{' position. Nested objects are filtered out to
keep only top-level candidates. If multiple non-overlapping valid JSON
objects are found, it is treated as ambiguous and returns None. Otherwise,
returns the single valid candidate dictionary.
"""
if not text:
return None
s = text.strip()
if s.startswith("```"):
s = s.split("\n", 1)[-1].rsplit("```", 1)[0].strip()
decoder = json.JSONDecoder()
candidates = []
start = s.find("{")
while start != -1:
try:
obj, idx = decoder.raw_decode(s[start:])
end_pos = start + idx
if isinstance(obj, dict):
candidates.append((start, end_pos, obj))
except (json.JSONDecodeError, ValueError):
pass
start = s.find("{", start + 1)
# Filter out nested candidates to identify top-level dictionaries
top_level = []
for c in candidates:
is_nested = False
for other in candidates:
if other == c:
continue
if other[0] <= c[0] and c[1] <= other[1]:
is_nested = True
break
if not is_nested:
top_level.append(c)
if not top_level:
return None
if len(top_level) > 1:
logger.debug(
"[skill-extract] Found multiple non-overlapping JSON objects: %s",
[item[2].get("title") for item in top_level]
)
return None
return top_level[0][2]
async def maybe_extract_skill(
session,
skills_manager,
@@ -59,6 +134,10 @@ async def maybe_extract_skill(
owner: Optional[str] = None,
):
"""Extract a skill if the agent run was complex enough."""
if not model:
logger.debug("[skill-extract] No model provided, skipping")
return None
# Quiet by default; flip to DEBUG when chasing extractor issues.
logger.debug(
"[skill-extract] start: rounds=%d tools=%d model=%s owner=%s",
@@ -78,9 +157,23 @@ async def maybe_extract_skill(
logger.debug("[skill-extract] no recent messages, skipping")
return None
# Strip media (images/audio) from messages
stripped_recent = []
for msg in recent:
content = msg.get("content", "")
if isinstance(content, list):
text_only = [b for b in content if isinstance(b, dict) and b.get("type") == "text"]
if not text_only and content:
continue
content = text_only
stripped_recent.append({"role": msg.get("role"), "content": content})
if not stripped_recent:
return None
# Build conversation summary for extraction
conv_lines = []
for msg in recent:
for msg in stripped_recent:
role = msg.get("role", "?")
content = msg.get("content", "")
if isinstance(content, list):
@@ -136,21 +229,14 @@ async def maybe_extract_skill(
except Exception:
pass
# Parse JSON
text = response.strip()
if text.startswith("```"):
text = text.split("\n", 1)[-1].rsplit("```", 1)[0].strip()
# After strip_think, the JSON may still be embedded inside surrounding
# commentary — slice from the first '{' to the matching last '}'.
if text and text[0] != "{":
_start = text.find("{")
_end = text.rfind("}")
if 0 <= _start < _end:
text = text[_start : _end + 1]
data = json.loads(text)
if not data or not isinstance(data, dict):
logger.debug("[skill-extract] parsed JSON not a dict, dropping")
# Parse JSON. The object may be wrapped in code fences or surrounded by
# commentary (and may contain a stray/invalid brace fragment before
# the real object — including one that makes the response itself look
# like it starts with '{'), so use a tolerant extractor that tries the
# whole string first and then each '{' candidate left-to-right.
data = _extract_json_object(response)
if not data:
logger.debug("[skill-extract] no JSON object found in response, dropping")
return None
title = data.get("title", "").strip()
@@ -173,10 +259,13 @@ async def maybe_extract_skill(
# Check for duplicate skills
existing = skills_manager.load(owner=owner)
for sk in existing:
if sk.get("title", "").lower() == title.lower():
logger.debug("[skill-extract] '%s' already exists — dropped as duplicate", title)
return None
if _has_duplicate_title(existing, title):
logger.debug("[skill-extract] '%s' already exists — dropped as duplicate", title)
return None
# Automatic approval happens only after the audit has passed. A new
# extraction begins as a draft so it cannot enter chat context early.
_initial_status = "draft"
entry = skills_manager.add_skill(
title=title,
@@ -188,6 +277,7 @@ async def maybe_extract_skill(
confidence=data.get("confidence", 0.7),
session_id=getattr(session, "session_id", None),
owner=owner,
status=_initial_status,
)
try:
from src.event_bus import fire_event