Bug fixes: - #52: bundle run-sleep.sh in Claude Code plugin + 4-level fallback - #58: add skillopt-sleep console script entry point in pyproject.toml - #62: filter headless claude -p replay sessions from harvest Plugin sync (Claude Code / Codex / Copilot / OpenClaw): - Document all 22 CLI flags, 7 actions, 4 backends across all SKILL.md files - Document config keys (preferences, gate_mode, dream_rollouts, etc.) - Document memory consolidation (evolve_memory / evolve_skill) - Add schedule/unschedule to all plugins - Copilot MCP: expand schema from 3 → 16 params + schedule tools - OpenClaw: add schedule/unschedule subcommands via shared scheduler Tests: - Cross-plugin parity test (prevents future feature drift) - MCP schema completeness test Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -111,6 +111,56 @@ def _is_meta_prompt(text: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
# ── Issue #62: filter headless replay sessions ─────────────────────────
|
||||
|
||||
# Prompt markers generated by the engine's own headless `claude -p` calls
|
||||
# (judge, reflect, attempt). If the sole user prompt in a single-turn
|
||||
# session matches any of these, the session is engine-generated, not a
|
||||
# real user task.
|
||||
_REPLAY_PROMPT_MARKERS = (
|
||||
"## CURRENT SKILL",
|
||||
"## FAILED TASKS",
|
||||
"## SUCCESSFUL TASKS",
|
||||
"## OUTPUT FORMAT",
|
||||
"You are a strict grader",
|
||||
"Score the response 0.0-1.0",
|
||||
"You are SkillOpt-Sleep",
|
||||
"## TASK\n",
|
||||
"## SKILL\n",
|
||||
)
|
||||
|
||||
|
||||
def _is_headless_replay(digest: "SessionDigest") -> bool:
|
||||
"""Detect sessions created by the engine's own headless replay calls.
|
||||
|
||||
Heuristics (conservatively applied):
|
||||
1. Session has exactly 1 user turn AND
|
||||
2. The sole prompt matches engine-generated patterns (grader/reflect),
|
||||
OR the session lasted < 3 seconds (programmatic, not interactive).
|
||||
Multi-turn sessions are always kept (interactive by definition).
|
||||
"""
|
||||
if digest.n_user_turns > 1:
|
||||
return False
|
||||
if digest.n_user_turns == 0:
|
||||
return True
|
||||
prompt = digest.user_prompts[0] if digest.user_prompts else ""
|
||||
for marker in _REPLAY_PROMPT_MARKERS:
|
||||
if marker in prompt:
|
||||
return True
|
||||
# Sub-3-second single-turn sessions are almost certainly programmatic.
|
||||
if digest.started_at and digest.ended_at:
|
||||
try:
|
||||
from datetime import datetime
|
||||
fmt = "%Y-%m-%dT%H:%M:%S"
|
||||
start = datetime.strptime(digest.started_at[:19], fmt)
|
||||
end = datetime.strptime(digest.ended_at[:19], fmt)
|
||||
if (end - start).total_seconds() < 3:
|
||||
return True
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def digest_transcript(path: str) -> Optional[SessionDigest]:
|
||||
"""Build a SessionDigest from one ``<sessionId>.jsonl`` transcript."""
|
||||
session_id = os.path.splitext(os.path.basename(path))[0]
|
||||
@@ -236,6 +286,8 @@ def harvest(
|
||||
d = digest_transcript(p)
|
||||
if d is None:
|
||||
continue
|
||||
if _is_headless_replay(d):
|
||||
continue # Issue #62: skip engine's own headless replay sessions
|
||||
if not _project_matches(d.project or "", scope, invoked_project):
|
||||
continue
|
||||
if since_iso and d.ended_at and d.ended_at < since_iso:
|
||||
|
||||
Reference in New Issue
Block a user