Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
742725c
加入高危命令黑名单 Add high-risk command blacklist
May 9, 2026
af56da0
GenericAgent Agent System Design Overview
May 10, 2026
65a4227
Merge remote-tracking branch 'upstream/main'
May 11, 2026
d8fa52c
Subagent 集群监控,Subagents Monitor
May 11, 2026
b585296
remove desing overview html
May 11, 2026
0ed6692
Merge remote-tracking branch 'upstream/main'
May 13, 2026
94e2f73
merged from upstream 0513
May 13, 2026
99c9692
skill_learn_from_cases — 案例驱动技能学习 CLI 工具
May 13, 2026
f8a0efa
skill_learn_from_cases — 案例驱动技能学习 CLI 工具 v001
May 13, 2026
1c321db
skill_learn_from_cases — 案例驱动技能学习 CLI 工具 v002
May 13, 2026
bc3c9ea
skill_learn_from_cases — 案例驱动技能学习 CLI 工具 v003
May 14, 2026
3448be6
skill_learn_from_cases — 案例驱动技能学习 CLI 工具 v005
May 14, 2026
c04f9ab
skill_learn_from_cases 案例驱动技能学习 CLI 工具 v006
May 14, 2026
0043612
skill_learn_from_cases 案例驱动技能学习 CLI 工具 v007
May 14, 2026
9a0dfe6
skill\_learn\_from\_cases 案例驱动技能学习 CLI 工具 v008
May 14, 2026
22c30ba
skill_learn_from_cases 案例驱动技能学习 CLI 工具 v009
May 14, 2026
9cc3b22
skill_learn_from_cases v010
May 14, 2026
47d4875
skill_learn_from_cases v 011
May 14, 2026
405145d
earn_skill_from_cases v000
May 15, 2026
41a2c9f
Merge remote-tracking branch 'upstream/main'
May 15, 2026
1de2ae2
fix:[开始空闲自主行动]按钮点击无反应
May 15, 2026
e123390
修复点击[开始空闲自主行动]按钮,无反应问题
May 15, 2026
ec7ae8d
clean fork 0515
May 15, 2026
7e51ab7
clean051502
May 15, 2026
1cad02b
自主行动相关按钮修复
May 15, 2026
1baa23b
Merge remote-tracking branch 'upstream/main'
May 16, 2026
c056de8
learn_skill_from_cases v000
May 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions frontends/stapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,17 @@ def _pet_hook(ctx):
st.divider()
if st.button("开始空闲自主行动"):
st.session_state.last_reply_time = int(time.time()) - 1800
st.toast("已将上次回复时间设为1800秒前"); st.rerun()
st.session_state.autonomous_enabled = True
st.toast("已将上次回复时间设为1800秒前,自主行动已激活"); st.rerun(scope="app")
if st.session_state.autonomous_enabled:
if st.button("⏸️ 禁止自主行动"):
st.session_state.autonomous_enabled = False
st.toast("⏸️ 已禁止自主行动"); st.rerun()
st.toast("⏸️ 已禁止自主行动"); st.rerun(scope="app")
st.caption("🟢 自主行动运行中,会在你离开它30分钟后自动进行")
else:
if st.button("▶️ 允许自主行动", type="primary"):
st.session_state.autonomous_enabled = True
st.toast("✅ 已允许自主行动"); st.rerun()
st.toast("✅ 已允许自主行动"); st.rerun(scope="app")
st.caption("🔴 自主行动已停止")
with st.sidebar: render_sidebar()

Expand Down
1 change: 1 addition & 0 deletions tools/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# tools package - utility modules
61 changes: 61 additions & 0 deletions tools/learn_skill_from_cases/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# learn_skill_from_cases — English-only Skill Learning CLI

A streamlined skill learning tool. **English input only** — provide skill names in pure English.

## Usage

```bash
# Learn a skill
python -m tools.learn_skill_from_cases "docker_compose_production"

# List learned skills
python -m tools.learn_skill_from_cases --list

# Show skill details
python -m tools.learn_skill_from_cases --show docker_compose_production

# Dry run (preview without creating files)
python -m tools.learn_skill_from_cases "python_async" --dry-run

# Force refresh (skip inheriting previous patterns)
python -m tools.learn_skill_from_cases "neo4j_modeling" --force

# Show version
python -m tools.learn_skill_from_cases --version
```

## Environment Variables

| Variable | Default | Description |
|---|---|---|
| `SKILL_LLM_ENABLE` | `0` | Set to `1` to enable LLM enhancement |
| `LLM_API_BASE` | `http://localhost:11434/v1` | OpenAI-compatible API endpoint |
| `LLM_API_KEY` | — | API key if required |
| `LLM_MODEL` | `qwen2.5:7b` | Model name |
| `LLM_TIMEOUT` | `30` | HTTP timeout in seconds |

## Output Structure

```
GA_ROOT/skills_learning/
└── {skill_name}/
├── rev{N}/
│ ├── meta.json
│ ├── cases/all_cases.json
│ ├── patterns/knowledge_patterns.json
│ ├── tools/assess.py
│ ├── reports/learning_report.md
│ ├── reports/skill_definition.json
│ └── practice/
└── ...
```

## Phase Flow

The tool runs a 5-phase pipeline:

1. **Bootstrap** — create version directory
2. **Define** — fetch skill definition
3. **Search** — collect web cases
4. **Extract** — derive knowledge patterns
5. **Validate** — run assessment and score
1 change: 1 addition & 0 deletions tools/learn_skill_from_cases/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""learn_skill_from_cases — English-only skill learning from cases (simplified version)"""
117 changes: 117 additions & 0 deletions tools/learn_skill_from_cases/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""
__main__.py — learn_skill_from_cases CLI entry point

Usage:
python -m tools.learn_skill_from_cases "docker_compose_production"
python -m tools.learn_skill_from_cases --list
python -m tools.learn_skill_from_cases "python_async" --dry-run
python -m tools.learn_skill_from_cases "neo4j_modeling" --force
python -m tools.learn_skill_from_cases --version
python -m tools.learn_skill_from_cases --show docker_compose_production
"""
import sys, argparse, re, json
from pathlib import Path

GA_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(GA_ROOT))

from tools.learn_skill_from_cases import dir_manager


def validate_english_only(name: str):
"""Reject skill names containing CJK characters. English only."""
if re.search(r'[\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]', name):
print("Error: Skill name must be in English only.")
print(" Chinese characters, Japanese characters, and mixed-language inputs are not supported.")
print(" Please provide a pure English skill name (e.g., 'docker_compose_production').")
sys.exit(1)


def cmd_list():
"""List all learned skills with version info."""
skills = dir_manager.get_all_skills()
if not skills:
print("No skills learned yet. Use:")
print(' python -m tools.learn_skill_from_cases "your_skill_name"')
return
print(f"\nLearned skills ({len(skills)} total):")
print("-" * 55)
for skill in skills:
versions = dir_manager.get_versions(skill)
print(f" {skill:30s} rev{versions[-1] if versions else '--'}")


def cmd_show(skill_name: str):
"""Show details of a specific skill (version list + patterns)."""
skill_dir = dir_manager.get_skill_dir(skill_name)
if not skill_dir.exists():
print(f"Skill '{skill_name}' not found.")
return
versions = dir_manager.get_versions(skill_name)
if not versions:
print(f"Skill '{skill_name}' has no versions.")
return
print(f"\nSkill: {skill_name}")
print("=" * 55)
for v in versions:
print(f" rev{v}")
patterns_file = skill_dir / f"rev{v}" / "patterns" / "knowledge_patterns.json"
if patterns_file.exists():
try:
patterns = json.loads(patterns_file.read_text(encoding="utf-8"))
for p in patterns:
print(f" [{p.get('level','?')}] {p.get('principle','?')[:70]}")
except Exception:
pass


def main():
parser = argparse.ArgumentParser(
description="learn_skill_from_cases — English-only skill learning from cases (simplified)",
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument("skill_name", nargs="?", help="English skill name to learn (e.g., docker_compose_production)")
parser.add_argument("--list", action="store_true", help="List all learned skills")
parser.add_argument("--show", metavar="NAME", help="Show details of a learned skill")
parser.add_argument("--dry-run", action="store_true", help="Preview without creating files")
parser.add_argument("--force", action="store_true", help="Skip inherited patterns, start fresh")
parser.add_argument("--version", action="store_true", help="Show version")

args = parser.parse_args()

# Handle special commands
if args.version:
print("learn_skill_from_cases v1.0.0 (simplified English-only version)")
return

if args.list:
cmd_list()
return

if args.show:
cmd_show(args.show)
return

# Must have a skill name
if not args.skill_name:
parser.print_help()
print("\nError: Please provide a skill name or use --list.")
sys.exit(1)

# Validate: English only
validate_english_only(args.skill_name)

# Run the learning pipeline
from tools.learn_skill_from_cases.engine import run
ctx = run(args.skill_name, dry_run=args.dry_run, force=args.force)

if ctx.get("score", 0) >= 60:
print(f"\n Learning score: {ctx['score']:.1f}/100 — Good result!")
elif ctx.get("score", 0) > 0:
print(f"\n Learning score: {ctx['score']:.1f}/100 — Consider adding more cases.")
else:
print(f"\n Score not available. Review the output above.")


if __name__ == "__main__":
main()
130 changes: 130 additions & 0 deletions tools/learn_skill_from_cases/dir_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""
dir_manager.py — Skill version directory management (simplified, English-only)

Responsibilities: detect existing versions, create revN directories, inherit previous patterns.
"""
import os, json, shutil, re
from pathlib import Path

GA_ROOT = Path(__file__).resolve().parents[2]
SKILL_LEARN_ROOT = GA_ROOT / "skills_learning"


def _sanitize_skill_name(skill_name: str) -> str:
"""Sanitize skill name: only allow alphanumeric, underscore, hyphen. No path traversal."""
sanitized = re.sub(r'[^\w\-]', '_', skill_name)
sanitized = sanitized.strip('_')
return sanitized or "unnamed_skill"


def _list_dirs(parent: Path) -> list[Path]:
if not parent.exists():
return []
return [d for d in parent.iterdir() if d.is_dir()]


def get_versions(skill_name: str) -> list[int]:
"""Get existing version numbers for a skill, e.g. [1, 2, 3]"""
skill_dir = SKILL_LEARN_ROOT / _sanitize_skill_name(skill_name)
versions = []
for d in _list_dirs(skill_dir):
if d.name.startswith("rev"):
try:
versions.append(int(d.name[3:]))
except ValueError:
pass
return sorted(versions)


def next_version(skill_name: str) -> int:
"""Return the next version number."""
versions = get_versions(skill_name)
return (max(versions) + 1) if versions else 1


def ensure_root_exists():
"""Ensure skills_learning/ root directory exists."""
if not SKILL_LEARN_ROOT.exists():
SKILL_LEARN_ROOT.mkdir(parents=True, exist_ok=True)
print(" [OK] skills_learning/ root directory created")


def get_skill_dir(skill_name: str) -> Path:
"""Return skill directory (path injection protected)."""
return SKILL_LEARN_ROOT / _sanitize_skill_name(skill_name)


def get_latest_revision_dir(skill_name: str) -> Path | None:
"""Return the latest rev directory that has knowledge patterns."""
safe_name = _sanitize_skill_name(skill_name)
versions = get_versions(safe_name)
if not versions:
return None
skill_dir = SKILL_LEARN_ROOT / safe_name
for v in reversed(versions):
patterns_file = skill_dir / f"rev{v}" / "patterns" / "knowledge_patterns.json"
if patterns_file.exists():
return skill_dir / f"rev{v}"
return skill_dir / f"rev{versions[-1]}"


def get_latest_patterns(skill_name: str) -> list[dict]:
"""Inherit knowledge patterns from the latest revision."""
latest = get_latest_revision_dir(skill_name)
if latest is None:
return []
patterns_file = latest / "patterns" / "knowledge_patterns.json"
if patterns_file.exists():
with open(patterns_file, encoding="utf-8") as f:
return json.load(f)
return []


def get_latest_cases(skill_name: str) -> list[dict]:
"""Inherit cases from the latest revision."""
latest = get_latest_revision_dir(skill_name)
if not latest:
return []
cases_file = latest / "cases" / "all_cases.json"
if cases_file.exists():
try:
with open(cases_file, encoding="utf-8") as f:
data = json.load(f)
return data if isinstance(data, list) else [data]
except (json.JSONDecodeError, OSError):
pass
return []


def create_revision_dir(skill_name: str, version: int) -> Path:
"""
Create revN directory structure:
revN/
├── meta.json
├── cases/
├── patterns/
├── tools/
├── reports/
└── practice/
"""
rev_dir = SKILL_LEARN_ROOT / _sanitize_skill_name(skill_name) / f"rev{version}"
subdirs = ["cases", "patterns", "tools", "practice", "reports"]
for s in subdirs:
(rev_dir / s).mkdir(parents=True, exist_ok=True)

meta = {
"skill": skill_name,
"version": version,
"created_at": "2026-05-15",
"status": "in_progress"
}
with open(rev_dir / "meta.json", "w", encoding="utf-8") as f:
json.dump(meta, f, indent=2)
return rev_dir


def get_all_skills() -> list[str]:
"""Get all skill names under skills_learning/."""
if not SKILL_LEARN_ROOT.exists():
return []
return sorted(d.name for d in _list_dirs(SKILL_LEARN_ROOT) if d.is_dir())
Loading