Add: 2026-05-29-熔岩之海与云鲸之梦
This commit is contained in:
@@ -1,284 +0,0 @@
|
||||
# Browser Automation Skill
|
||||
|
||||
使用 Playwright 调用本地 Chrome 进行反检测浏览器自动化。
|
||||
|
||||
## 简介
|
||||
|
||||
此 Skill 封装了浏览器自动化功能,专门设计用于规避常见的爬虫检测机制(如 Cloudflare、DataDome 等)。通过使用本地安装的 Chrome 并配置反检测参数,可以模拟真实用户行为。
|
||||
|
||||
## 特点
|
||||
|
||||
- ✅ **反检测模式**:禁用 `navigator.webdriver` 标记,绕过自动化检测
|
||||
- ✅ **本地 Chrome**:使用系统安装的 Google Chrome,而非 Playwright 自带的 Chromium
|
||||
- ✅ **两种模式**:
|
||||
- **launch 模式**:直接启动 Chrome(带反检测参数)
|
||||
- **cdp 模式**:连接已运行的 Chrome 实例
|
||||
- ✅ **简洁 API**:navigate, click, type, snapshot, scroll, screenshot, close
|
||||
|
||||
## 安装依赖
|
||||
|
||||
```bash
|
||||
# 安装 Playwright
|
||||
pip install playwright
|
||||
|
||||
# 安装 Chromium(playwright 需要,虽然我们使用本地 Chrome)
|
||||
playwright install chromium
|
||||
```
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 1. 基础用法(Launch 模式)
|
||||
|
||||
```python
|
||||
from browser_tool import BrowserTool
|
||||
|
||||
# 创建浏览器实例
|
||||
browser = BrowserTool(mode="launch", headless=False)
|
||||
|
||||
# 导航到页面
|
||||
snapshot = browser.navigate("https://example.com")
|
||||
print(snapshot)
|
||||
|
||||
# 点击元素
|
||||
browser.click("@e5")
|
||||
|
||||
# 输入文本
|
||||
browser.type("@e3", "search query")
|
||||
browser.press("Enter")
|
||||
|
||||
# 滚动页面
|
||||
browser.scroll("down")
|
||||
|
||||
# 截图
|
||||
path = browser.screenshot()
|
||||
|
||||
# 关闭
|
||||
browser.close()
|
||||
```
|
||||
|
||||
### 2. 使用上下文管理器
|
||||
|
||||
```python
|
||||
from browser_tool import BrowserTool
|
||||
|
||||
with BrowserTool(mode="launch", headless=False) as browser:
|
||||
snapshot = browser.navigate("https://example.com")
|
||||
print(snapshot)
|
||||
# 自动关闭
|
||||
```
|
||||
|
||||
### 3. CDP 模式(连接已运行的 Chrome)
|
||||
|
||||
首先启动 Chrome 并开启调试端口:
|
||||
|
||||
```bash
|
||||
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
|
||||
--remote-debugging-port=9222 \
|
||||
--user-data-dir=/tmp/chrome_debug
|
||||
```
|
||||
|
||||
然后在代码中连接:
|
||||
|
||||
```python
|
||||
from browser_tool import BrowserTool
|
||||
|
||||
browser = BrowserTool(mode="cdp", cdp_url="http://localhost:9222")
|
||||
snapshot = browser.navigate("https://example.com")
|
||||
```
|
||||
|
||||
### 4. 使用快捷函数
|
||||
|
||||
```python
|
||||
from browser_tool import create_browser
|
||||
|
||||
browser = create_browser(mode="launch", headless=False)
|
||||
```
|
||||
|
||||
### 5. 启动 Chrome 调试实例
|
||||
|
||||
```python
|
||||
from browser_tool import launch_chrome_debug_port
|
||||
|
||||
# 启动 Chrome 并开启 9222 端口
|
||||
proc = launch_chrome_debug_port(port=9222)
|
||||
|
||||
# 之后可以用 CDP 模式连接
|
||||
```
|
||||
|
||||
## API 参考
|
||||
|
||||
### BrowserTool 类
|
||||
|
||||
#### 构造函数
|
||||
|
||||
```python
|
||||
BrowserTool(
|
||||
mode: Literal["launch", "cdp"] = "launch",
|
||||
cdp_url: str = "http://localhost:9222",
|
||||
headless: bool = False,
|
||||
chrome_path: Optional[str] = None,
|
||||
)
|
||||
```
|
||||
|
||||
参数:
|
||||
- `mode`: 启动模式,`"launch"` 直接启动 Chrome,`"cdp"` 连接已运行的 Chrome
|
||||
- `cdp_url`: CDP 连接地址(仅 mode="cdp" 时使用)
|
||||
- `headless`: 是否无头模式(建议 False 规避检测)
|
||||
- `chrome_path`: Chrome 可执行文件路径(默认使用 `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`)
|
||||
|
||||
#### 方法
|
||||
|
||||
##### navigate(url, wait_until="networkidle") -> str
|
||||
|
||||
导航到指定 URL,返回页面快照。
|
||||
|
||||
- `url`: 目标网址
|
||||
- `wait_until`: 等待条件 (`load`, `domcontentloaded`, `networkidle`)
|
||||
|
||||
返回:页面快照文本,包含交互元素列表
|
||||
|
||||
##### snapshot(full=False) -> str
|
||||
|
||||
获取当前页面快照。
|
||||
|
||||
- `full`: 是否返回完整页面内容(默认只返回交互元素)
|
||||
|
||||
##### click(ref, wait_for_navigation=False)
|
||||
|
||||
点击元素。
|
||||
|
||||
- `ref`: 元素引用 ID,如 `@e5`
|
||||
- `wait_for_navigation`: 是否等待页面跳转
|
||||
|
||||
##### type(ref, text, clear=True)
|
||||
|
||||
在输入框填入文本。
|
||||
|
||||
- `ref`: 元素引用 ID
|
||||
- `text`: 要输入的文本
|
||||
- `clear`: 是否先清空输入框
|
||||
|
||||
##### scroll(direction="down", amount=500)
|
||||
|
||||
滚动页面。
|
||||
|
||||
- `direction`: `"up"` 或 `"down"`
|
||||
- `amount`: 滚动像素数
|
||||
|
||||
##### screenshot(path=None) -> str
|
||||
|
||||
截取页面截图。
|
||||
|
||||
- `path`: 截图保存路径(默认自动命名)
|
||||
|
||||
返回:截图文件路径
|
||||
|
||||
##### press(key)
|
||||
|
||||
按下键盘按键。
|
||||
|
||||
- `key`: 按键名称 (`Enter`, `Tab`, `Escape`, `ArrowDown`, 等)
|
||||
|
||||
##### back()
|
||||
|
||||
返回上一页。
|
||||
|
||||
##### close()
|
||||
|
||||
关闭浏览器会话。
|
||||
|
||||
##### check_detection() -> Dict
|
||||
|
||||
检查反检测效果,访问 https://bot.sannysoft.com/ 并返回检测结果。
|
||||
|
||||
返回:包含 `webdriver`, `plugins`, `languages`, `userAgent`, `chrome` 等字段的字典
|
||||
|
||||
## 页面快照格式
|
||||
|
||||
`snapshot()` 和 `navigate()` 返回的快照文本格式如下:
|
||||
|
||||
```
|
||||
URL: https://example.com
|
||||
Title: Example Domain
|
||||
|
||||
=== Interactive Elements ===
|
||||
|
||||
[@e1] <a href="https://example.com/page">Link text</a>
|
||||
[@e2] <button type="submit">Submit</button>
|
||||
[@e3] <input type="text" placeholder="Search...">
|
||||
[@e4] <textarea name="content">
|
||||
```
|
||||
|
||||
使用 `@e1`, `@e2` 等引用 ID 进行点击和输入操作。
|
||||
|
||||
## 反检测原理
|
||||
|
||||
1. **使用本地 Chrome**:而非 Playwright 自带的 headless Chromium,避免被识别为自动化浏览器
|
||||
2. **禁用 webdriver 标记**:通过 `Object.defineProperty` 将 `navigator.webdriver` 设为 `undefined`
|
||||
3. **配置启动参数**:
|
||||
- `--disable-blink-features=AutomationControlled`:禁用自动化控制特征
|
||||
- `--disable-web-security`:禁用 web 安全(可选)
|
||||
- `--window-size`:设置正常 viewport
|
||||
4. **非无头模式**:`headless=False` 让 Chrome 真实显示
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: Chrome 路径不对?
|
||||
|
||||
在创建 BrowserTool 时指定路径:
|
||||
|
||||
```python
|
||||
browser = BrowserTool(chrome_path="/path/to/chrome")
|
||||
```
|
||||
|
||||
### Q: 如何查找 Chrome 路径?
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
ls /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome
|
||||
|
||||
# 或使用 mdfind
|
||||
mdfind -name "Google Chrome.app"
|
||||
```
|
||||
|
||||
### Q: CDP 模式连接失败?
|
||||
|
||||
确保 Chrome 已启动并开启调试端口:
|
||||
|
||||
```bash
|
||||
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
|
||||
--remote-debugging-port=9222
|
||||
```
|
||||
|
||||
检查端口是否监听:
|
||||
|
||||
```bash
|
||||
curl http://localhost:9222/json/version
|
||||
```
|
||||
|
||||
### Q: 还是被检测到了?
|
||||
|
||||
尝试以下方法:
|
||||
1. 确保 `headless=False`
|
||||
2. 使用 CDP 模式连接手动启动的 Chrome
|
||||
3. 添加更多延迟:`browser._page.wait_for_timeout(2000)`
|
||||
4. 使用代理或 VPN 更换 IP
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
browser-automation/
|
||||
├── SKILL.md # 本文档
|
||||
└── browser_tool.py # 核心封装
|
||||
```
|
||||
|
||||
## 依赖
|
||||
|
||||
- Python 3.8+
|
||||
- Playwright
|
||||
- Google Chrome(本地安装)
|
||||
|
||||
## 参考
|
||||
|
||||
- [Playwright Documentation](https://playwright.dev/python/)
|
||||
- [bot.sannysoft.com](https://bot.sannysoft.com/) - 浏览器自动化检测测试
|
||||
Binary file not shown.
@@ -1,568 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Browser Tool - Anti-Detection Browser Automation for OpenClaw
|
||||
|
||||
使用 Playwright 调用本地 Chrome,规避爬虫检测。
|
||||
|
||||
功能:
|
||||
- navigate(url) - 打开网页并返回页面快照
|
||||
- click(ref) - 点击元素
|
||||
- type(ref, text) - 在输入框填入文本
|
||||
- snapshot() - 获取当前页面快照
|
||||
- scroll(direction) - 上下滚动
|
||||
- close() - 关闭浏览器会话
|
||||
|
||||
两种模式:
|
||||
1. 原生启动模式:直接启动本地 Chrome(带反检测参数)
|
||||
2. CDP 连接模式:连接已运行的 Chrome 实例(通过 --remote-debugging-port=9222)
|
||||
|
||||
依赖:
|
||||
pip install playwright
|
||||
playwright install chromium
|
||||
|
||||
作者:Catalyst (封装)
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import subprocess
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Any, Literal
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
# Playwright 导入
|
||||
try:
|
||||
from playwright.sync_api import sync_playwright, Page, Browser, BrowserContext
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Playwright not installed. Run:\n"
|
||||
" pip install playwright\n"
|
||||
" playwright install chromium"
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Configuration
|
||||
# ============================================================================
|
||||
|
||||
CHROME_PATH = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
||||
DEFAULT_VIEWPORT = {"width": 1280, "height": 720}
|
||||
DEFAULT_USER_AGENT = (
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/120.0.0.0 Safari/537.36"
|
||||
)
|
||||
|
||||
# 反检测启动参数
|
||||
STEALTH_ARGS = [
|
||||
"--disable-blink-features=AutomationControlled",
|
||||
"--disable-web-security",
|
||||
"--disable-features=IsolateOrigins,site-per-process",
|
||||
"--disable-site-isolation-trials",
|
||||
"--disable-dev-shm-usage",
|
||||
"--no-sandbox",
|
||||
"--disable-setuid-sandbox",
|
||||
"--disable-accelerated-2d-canvas",
|
||||
"--disable-gpu",
|
||||
"--window-size=1280,720",
|
||||
"--start-maximized",
|
||||
]
|
||||
|
||||
# 禁用 webdriver 标记的脚本
|
||||
HIDE_WEBDRIVER_SCRIPT = """
|
||||
Object.defineProperty(navigator, 'webdriver', {
|
||||
get: () => undefined
|
||||
});
|
||||
Object.defineProperty(navigator, 'plugins', {
|
||||
get: () => [1, 2, 3, 4, 5]
|
||||
});
|
||||
window.chrome = { runtime: {} };
|
||||
Object.defineProperty(navigator, 'languages', {
|
||||
get: () => ['zh-CN', 'zh', 'en']
|
||||
});
|
||||
"""
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Data Classes
|
||||
# ============================================================================
|
||||
|
||||
@dataclass
|
||||
class ElementInfo:
|
||||
"""页面元素信息"""
|
||||
ref: str # @e1, @e2, etc.
|
||||
tag: str # a, button, input, etc.
|
||||
text: str
|
||||
attributes: Dict[str, str] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PageSnapshot:
|
||||
"""页面快照"""
|
||||
url: str
|
||||
title: str
|
||||
elements: list
|
||||
text_content: str
|
||||
screenshot_path: Optional[str] = None
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Browser Tool Class
|
||||
# ============================================================================
|
||||
|
||||
class BrowserTool:
|
||||
"""
|
||||
反检测浏览器自动化工具
|
||||
|
||||
使用本地 Chrome,规避爬虫检测
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mode: Literal["launch", "cdp"] = "launch",
|
||||
cdp_url: str = "http://localhost:9222",
|
||||
headless: bool = False,
|
||||
chrome_path: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
初始化浏览器工具
|
||||
|
||||
Args:
|
||||
mode: "launch" 直接启动 Chrome,"cdp" 连接已运行的 Chrome
|
||||
cdp_url: CDP 连接地址(mode="cdp" 时使用)
|
||||
headless: 是否无头模式(建议 False 规避检测)
|
||||
chrome_path: Chrome 可执行文件路径
|
||||
"""
|
||||
self.mode = mode
|
||||
self.cdp_url = cdp_url
|
||||
self.headless = headless
|
||||
self.chrome_path = chrome_path or CHROME_PATH
|
||||
|
||||
self._playwright = None
|
||||
self._browser: Optional[Browser] = None
|
||||
self._context: Optional[BrowserContext] = None
|
||||
self._page: Optional[Page] = None
|
||||
self._element_map: Dict[str, ElementInfo] = {}
|
||||
self._closed = False
|
||||
|
||||
def _ensure_started(self):
|
||||
"""确保浏览器已启动"""
|
||||
if self._closed:
|
||||
raise RuntimeError("Browser session has been closed")
|
||||
if self._page is None:
|
||||
raise RuntimeError("Browser not started. Call navigate() first.")
|
||||
|
||||
def _start_browser(self):
|
||||
"""启动或连接浏览器"""
|
||||
if self._playwright is None:
|
||||
self._playwright = sync_playwright().start()
|
||||
|
||||
if self._browser is not None:
|
||||
return
|
||||
|
||||
if self.mode == "cdp":
|
||||
# CDP 连接模式
|
||||
self._browser = self._playwright.chromium.connect_over_cdp(self.cdp_url)
|
||||
contexts = self._browser.contexts
|
||||
if contexts:
|
||||
self._context = contexts[0]
|
||||
else:
|
||||
self._context = self._browser.new_context(
|
||||
viewport=DEFAULT_VIEWPORT,
|
||||
user_agent=DEFAULT_USER_AGENT,
|
||||
)
|
||||
else:
|
||||
# 原生启动模式
|
||||
if not os.path.exists(self.chrome_path):
|
||||
raise FileNotFoundError(
|
||||
f"Chrome not found at {self.chrome_path}\n"
|
||||
"Please install Google Chrome or specify correct path."
|
||||
)
|
||||
|
||||
self._browser = self._playwright.chromium.launch(
|
||||
executable_path=self.chrome_path,
|
||||
headless=self.headless,
|
||||
args=STEALTH_ARGS,
|
||||
)
|
||||
self._context = self._browser.new_context(
|
||||
viewport=DEFAULT_VIEWPORT,
|
||||
user_agent=DEFAULT_USER_AGENT,
|
||||
)
|
||||
|
||||
# 注入反检测脚本
|
||||
self._context.add_init_script(HIDE_WEBDRIVER_SCRIPT)
|
||||
|
||||
# 创建新页面
|
||||
self._page = self._context.new_page()
|
||||
|
||||
def _build_element_map(self) -> Dict[str, ElementInfo]:
|
||||
"""构建页面元素映射表"""
|
||||
self._ensure_started()
|
||||
|
||||
# 获取所有可交互元素
|
||||
elements = self._page.query_selector_all(
|
||||
'a, button, input, textarea, select, [role="button"], [onclick]'
|
||||
)
|
||||
|
||||
self._element_map = {}
|
||||
for idx, elem in enumerate(elements, start=1):
|
||||
try:
|
||||
tag = elem.evaluate("el => el.tagName.toLowerCase()")
|
||||
text = elem.inner_text().strip()[:100] # 限制长度
|
||||
|
||||
# 获取关键属性
|
||||
attrs = {}
|
||||
for attr in ["href", "id", "name", "placeholder", "type", "value"]:
|
||||
val = elem.get_attribute(attr)
|
||||
if val:
|
||||
attrs[attr] = val[:100]
|
||||
|
||||
ref = f"@e{idx}"
|
||||
self._element_map[ref] = ElementInfo(
|
||||
ref=ref,
|
||||
tag=tag,
|
||||
text=text,
|
||||
attributes=attrs
|
||||
)
|
||||
except:
|
||||
continue
|
||||
|
||||
return self._element_map
|
||||
|
||||
def _format_snapshot(self) -> str:
|
||||
"""格式化页面快照为文本"""
|
||||
lines = [
|
||||
f"URL: {self._page.url}",
|
||||
f"Title: {self._page.title()}",
|
||||
"",
|
||||
"=== Interactive Elements ===",
|
||||
"",
|
||||
]
|
||||
|
||||
for ref, elem in self._element_map.items():
|
||||
attr_str = " ".join([f'{k}="{v}"' for k, v in elem.attributes.items()])
|
||||
if attr_str:
|
||||
attr_str = f" {attr_str}"
|
||||
|
||||
text = elem.text.replace("\n", " ")[:80]
|
||||
lines.append(f"[{ref}] <{elem.tag}>{attr_str}> {text}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def _get_element_by_ref(self, ref: str):
|
||||
"""通过 ref ID 获取元素"""
|
||||
self._ensure_started()
|
||||
|
||||
if ref not in self._element_map:
|
||||
# 刷新元素映射
|
||||
self._build_element_map()
|
||||
|
||||
if ref not in self._element_map:
|
||||
raise ValueError(f"Element {ref} not found. Call snapshot() to refresh.")
|
||||
|
||||
# 重新获取元素(Playwright 句柄会过期)
|
||||
idx = int(ref.replace("@e", "")) - 1
|
||||
elements = self._page.query_selector_all(
|
||||
'a, button, input, textarea, select, [role="button"], [onclick]'
|
||||
)
|
||||
|
||||
if idx >= len(elements):
|
||||
raise ValueError(f"Element {ref} no longer exists. Call snapshot() to refresh.")
|
||||
|
||||
return elements[idx]
|
||||
|
||||
# ========================================================================
|
||||
# Public API
|
||||
# ========================================================================
|
||||
|
||||
def navigate(self, url: str, wait_until: str = "networkidle") -> str:
|
||||
"""
|
||||
导航到指定 URL
|
||||
|
||||
Args:
|
||||
url: 目标网址
|
||||
wait_until: 等待条件 (load, domcontentloaded, networkidle)
|
||||
|
||||
Returns:
|
||||
页面快照文本
|
||||
"""
|
||||
self._start_browser()
|
||||
|
||||
self._page.goto(url, wait_until=wait_until)
|
||||
|
||||
# 等待页面稳定
|
||||
self._page.wait_for_timeout(1000)
|
||||
|
||||
# 构建元素映射
|
||||
self._build_element_map()
|
||||
|
||||
return self._format_snapshot()
|
||||
|
||||
def snapshot(self, full: bool = False) -> str:
|
||||
"""
|
||||
获取当前页面快照
|
||||
|
||||
Args:
|
||||
full: 是否返回完整页面内容
|
||||
|
||||
Returns:
|
||||
页面快照文本
|
||||
"""
|
||||
self._ensure_started()
|
||||
|
||||
if full:
|
||||
# 返回完整页面文本
|
||||
text = self._page.inner_text("body")
|
||||
return f"URL: {self._page.url}\nTitle: {self._page.title()}\n\n{text[:8000]}"
|
||||
|
||||
# 刷新元素映射
|
||||
self._build_element_map()
|
||||
|
||||
return self._format_snapshot()
|
||||
|
||||
def click(self, ref: str, wait_for_navigation: bool = False):
|
||||
"""
|
||||
点击元素
|
||||
|
||||
Args:
|
||||
ref: 元素引用 ID (如 @e5)
|
||||
wait_for_navigation: 是否等待页面跳转
|
||||
"""
|
||||
self._ensure_started()
|
||||
|
||||
element = self._get_element_by_ref(ref)
|
||||
|
||||
if wait_for_navigation:
|
||||
with self._page.expect_navigation():
|
||||
element.click()
|
||||
else:
|
||||
element.click()
|
||||
|
||||
# 等待一下让页面响应
|
||||
self._page.wait_for_timeout(500)
|
||||
|
||||
def type(self, ref: str, text: str, clear: bool = True):
|
||||
"""
|
||||
在输入框填入文本
|
||||
|
||||
Args:
|
||||
ref: 元素引用 ID (如 @e3)
|
||||
text: 要输入的文本
|
||||
clear: 是否先清空输入框
|
||||
"""
|
||||
self._ensure_started()
|
||||
|
||||
element = self._get_element_by_ref(ref)
|
||||
|
||||
if clear:
|
||||
element.fill(text)
|
||||
else:
|
||||
element.type(text)
|
||||
|
||||
def scroll(self, direction: Literal["up", "down"] = "down", amount: int = 500):
|
||||
"""
|
||||
滚动页面
|
||||
|
||||
Args:
|
||||
direction: up 或 down
|
||||
amount: 滚动像素数
|
||||
"""
|
||||
self._ensure_started()
|
||||
|
||||
delta = -amount if direction == "up" else amount
|
||||
self._page.evaluate(f"window.scrollBy(0, {delta})")
|
||||
|
||||
# 等待滚动完成
|
||||
self._page.wait_for_timeout(300)
|
||||
|
||||
def screenshot(self, path: Optional[str] = None) -> str:
|
||||
"""
|
||||
截取页面截图
|
||||
|
||||
Args:
|
||||
path: 截图保存路径(默认自动命名)
|
||||
|
||||
Returns:
|
||||
截图文件路径
|
||||
"""
|
||||
self._ensure_started()
|
||||
|
||||
if path is None:
|
||||
timestamp = int(time.time())
|
||||
path = f"/tmp/browser_screenshot_{timestamp}.png"
|
||||
|
||||
self._page.screenshot(path=path, full_page=True)
|
||||
return path
|
||||
|
||||
def press(self, key: str):
|
||||
"""
|
||||
按下键盘按键
|
||||
|
||||
Args:
|
||||
key: 按键名称 (Enter, Tab, Escape, ArrowDown, etc.)
|
||||
"""
|
||||
self._ensure_started()
|
||||
self._page.keyboard.press(key)
|
||||
|
||||
def back(self):
|
||||
"""返回上一页"""
|
||||
self._ensure_started()
|
||||
self._page.go_back()
|
||||
|
||||
def close(self):
|
||||
"""关闭浏览器会话"""
|
||||
if self._closed:
|
||||
return
|
||||
|
||||
try:
|
||||
if self._context:
|
||||
self._context.close()
|
||||
if self._browser:
|
||||
self._browser.close()
|
||||
if self._playwright:
|
||||
self._playwright.stop()
|
||||
except:
|
||||
pass
|
||||
|
||||
self._page = None
|
||||
self._context = None
|
||||
self._browser = None
|
||||
self._playwright = None
|
||||
self._closed = True
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
|
||||
# ========================================================================
|
||||
# Utility Methods
|
||||
# ========================================================================
|
||||
|
||||
def check_detection(self) -> Dict[str, Any]:
|
||||
"""
|
||||
检查反检测效果
|
||||
|
||||
Returns:
|
||||
检测结果字典
|
||||
"""
|
||||
if self._page is None:
|
||||
self._start_browser()
|
||||
|
||||
# 访问检测页面
|
||||
self._page.goto("https://bot.sannysoft.com/")
|
||||
self._page.wait_for_timeout(3000)
|
||||
|
||||
# 获取关键检测结果
|
||||
results = self._page.evaluate("""
|
||||
() => ({
|
||||
webdriver: navigator.webdriver,
|
||||
plugins: navigator.plugins.length,
|
||||
languages: navigator.languages,
|
||||
userAgent: navigator.userAgent,
|
||||
chrome: typeof window.chrome !== 'undefined',
|
||||
})
|
||||
""")
|
||||
|
||||
return results
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Helper Functions
|
||||
# ============================================================================
|
||||
|
||||
def launch_chrome_debug_port(port: int = 9222) -> subprocess.Popen:
|
||||
"""
|
||||
启动 Chrome 并开启远程调试端口
|
||||
|
||||
Args:
|
||||
port: 调试端口
|
||||
|
||||
Returns:
|
||||
Chrome 进程对象
|
||||
"""
|
||||
if not os.path.exists(CHROME_PATH):
|
||||
raise FileNotFoundError(f"Chrome not found at {CHROME_PATH}")
|
||||
|
||||
cmd = [
|
||||
CHROME_PATH,
|
||||
f"--remote-debugging-port={port}",
|
||||
"--user-data-dir=/tmp/chrome_debug_profile",
|
||||
"--no-first-run",
|
||||
"--no-default-browser-check",
|
||||
] + STEALTH_ARGS
|
||||
|
||||
proc = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
# 等待 Chrome 启动
|
||||
time.sleep(2)
|
||||
|
||||
return proc
|
||||
|
||||
|
||||
def create_browser(
|
||||
mode: Literal["launch", "cdp"] = "launch",
|
||||
headless: bool = False,
|
||||
) -> BrowserTool:
|
||||
"""
|
||||
创建浏览器工具实例(快捷函数)
|
||||
|
||||
Args:
|
||||
mode: 启动模式
|
||||
headless: 是否无头模式
|
||||
|
||||
Returns:
|
||||
BrowserTool 实例
|
||||
"""
|
||||
return BrowserTool(mode=mode, headless=headless)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# CLI Interface
|
||||
# ============================================================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Browser Automation Tool")
|
||||
parser.add_argument("command", choices=["navigate", "snapshot", "screenshot", "check"])
|
||||
parser.add_argument("--url", help="URL for navigate")
|
||||
parser.add_argument("--mode", default="launch", choices=["launch", "cdp"])
|
||||
parser.add_argument("--headless", action="store_true")
|
||||
parser.add_argument("--output", help="Output file path")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
with BrowserTool(mode=args.mode, headless=args.headless) as browser:
|
||||
if args.command == "navigate":
|
||||
if not args.url:
|
||||
print("Error: --url required for navigate")
|
||||
exit(1)
|
||||
result = browser.navigate(args.url)
|
||||
print(result)
|
||||
|
||||
elif args.command == "snapshot":
|
||||
# 需要先 navigate
|
||||
print("Error: Use navigate command first")
|
||||
exit(1)
|
||||
|
||||
elif args.command == "screenshot":
|
||||
if not args.url:
|
||||
print("Error: --url required")
|
||||
exit(1)
|
||||
browser.navigate(args.url)
|
||||
path = browser.screenshot(args.output)
|
||||
print(f"Screenshot saved: {path}")
|
||||
|
||||
elif args.command == "check":
|
||||
browser.navigate("https://bot.sannysoft.com/")
|
||||
results = browser.check_detection()
|
||||
print(json.dumps(results, indent=2))
|
||||
@@ -1,125 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试脚本:使用代理访问 Google
|
||||
"""
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, '/Users/guchen/.openclaw/workspace/skills/browser-automation')
|
||||
|
||||
from playwright.sync_api import sync_playwright
|
||||
import os
|
||||
|
||||
# 代理配置
|
||||
PROXY = {"server": "http://127.0.0.1:7897"}
|
||||
|
||||
def test_google_with_proxy():
|
||||
"""测试使用代理访问 Google"""
|
||||
|
||||
print("=" * 60)
|
||||
print("开始测试:使用代理访问 Google")
|
||||
print(f"代理配置: {PROXY}")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
with sync_playwright() as p:
|
||||
# 启动浏览器
|
||||
print("\n[1/5] 启动 Chrome 浏览器...")
|
||||
browser = p.chromium.launch(
|
||||
executable_path="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
headless=False, # 设置为 True 可以无头运行
|
||||
args=[
|
||||
"--disable-blink-features=AutomationControlled",
|
||||
"--disable-web-security",
|
||||
"--window-size=1280,720",
|
||||
]
|
||||
)
|
||||
|
||||
# 创建带有代理的上下文
|
||||
print("[2/5] 创建带有代理的浏览器上下文...")
|
||||
context = browser.new_context(
|
||||
viewport={"width": 1280, "height": 720},
|
||||
user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
proxy=PROXY # 关键:传入代理配置
|
||||
)
|
||||
|
||||
# 注入反检测脚本
|
||||
context.add_init_script("""
|
||||
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
|
||||
window.chrome = { runtime: {} };
|
||||
""")
|
||||
|
||||
# 创建新页面
|
||||
page = context.new_page()
|
||||
|
||||
# 访问 Google
|
||||
print("[3/5] 访问 https://www.google.com ...")
|
||||
page.goto("https://www.google.com", wait_until="networkidle")
|
||||
page.wait_for_timeout(2000) # 等待页面完全加载
|
||||
|
||||
# 获取页面信息
|
||||
print("[4/5] 获取页面信息...")
|
||||
title = page.title()
|
||||
url = page.url
|
||||
|
||||
# 获取页面文本内容(前500字符)
|
||||
text_content = page.inner_text("body")
|
||||
text_preview = text_content[:500].replace("\n", " ")
|
||||
|
||||
# 获取可交互元素
|
||||
elements = page.query_selector_all('a, button, input, textarea, select')
|
||||
element_count = len(elements)
|
||||
|
||||
# 截图
|
||||
print("[5/5] 保存页面截图...")
|
||||
screenshot_path = "/tmp/google_test.png"
|
||||
page.screenshot(path=screenshot_path, full_page=True)
|
||||
|
||||
# 获取截图文件大小
|
||||
screenshot_size = os.path.getsize(screenshot_path)
|
||||
|
||||
# 关闭浏览器
|
||||
browser.close()
|
||||
|
||||
# 输出结果
|
||||
print("\n" + "=" * 60)
|
||||
print("测试结果: ✅ 成功")
|
||||
print("=" * 60)
|
||||
print(f"页面标题: {title}")
|
||||
print(f"页面URL: {url}")
|
||||
print(f"交互元素数量: {element_count}")
|
||||
print(f"截图文件: {screenshot_path}")
|
||||
print(f"截图大小: {screenshot_size / 1024:.2f} KB")
|
||||
print("\n页面内容摘要 (前500字符):")
|
||||
print("-" * 60)
|
||||
print(text_preview)
|
||||
print("-" * 60)
|
||||
|
||||
# 验证关键元素
|
||||
has_google = "Google" in text_content or "google" in text_content.lower()
|
||||
has_search = "search" in text_content.lower() or "搜索" in text_content
|
||||
|
||||
print("\n验证结果:")
|
||||
print(f" - 包含 'Google' 文本: {'✅' if has_google else '❌'}")
|
||||
print(f" - 包含搜索相关元素: {'✅' if has_search else '❌'}")
|
||||
|
||||
if has_google and screenshot_size > 1000:
|
||||
print("\n✅ 测试通过:代理配置工作正常!")
|
||||
return True
|
||||
else:
|
||||
print("\n⚠️ 测试部分通过:页面加载但可能不完全")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print("\n" + "=" * 60)
|
||||
print("测试结果: ❌ 失败")
|
||||
print("=" * 60)
|
||||
print(f"错误类型: {type(e).__name__}")
|
||||
print(f"错误信息: {str(e)}")
|
||||
print("\n详细堆栈:")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = test_google_with_proxy()
|
||||
sys.exit(0 if success else 1)
|
||||
@@ -1,5 +0,0 @@
|
||||
# Errors Log
|
||||
|
||||
Command failures, exceptions, and unexpected behaviors.
|
||||
|
||||
---
|
||||
@@ -1,5 +0,0 @@
|
||||
# Feature Requests
|
||||
|
||||
Capabilities requested by user that don't currently exist.
|
||||
|
||||
---
|
||||
@@ -1,5 +0,0 @@
|
||||
# Learnings Log
|
||||
|
||||
Captured learnings, corrections, and discoveries. Review before major tasks.
|
||||
|
||||
---
|
||||
@@ -1,647 +0,0 @@
|
||||
---
|
||||
name: self-improvement
|
||||
description: "Captures learnings, errors, and corrections to enable continuous improvement. Use when: (1) A command or operation fails unexpectedly, (2) User corrects Claude ('No, that's wrong...', 'Actually...'), (3) User requests a capability that doesn't exist, (4) An external API or tool fails, (5) Claude realizes its knowledge is outdated or incorrect, (6) A better approach is discovered for a recurring task. Also review learnings before major tasks."
|
||||
metadata:
|
||||
---
|
||||
|
||||
# Self-Improvement Skill
|
||||
|
||||
Log learnings and errors to markdown files for continuous improvement. Coding agents can later process these into fixes, and important learnings get promoted to project memory.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Situation | Action |
|
||||
|-----------|--------|
|
||||
| Command/operation fails | Log to `.learnings/ERRORS.md` |
|
||||
| User corrects you | Log to `.learnings/LEARNINGS.md` with category `correction` |
|
||||
| User wants missing feature | Log to `.learnings/FEATURE_REQUESTS.md` |
|
||||
| API/external tool fails | Log to `.learnings/ERRORS.md` with integration details |
|
||||
| Knowledge was outdated | Log to `.learnings/LEARNINGS.md` with category `knowledge_gap` |
|
||||
| Found better approach | Log to `.learnings/LEARNINGS.md` with category `best_practice` |
|
||||
| Simplify/Harden recurring patterns | Log/update `.learnings/LEARNINGS.md` with `Source: simplify-and-harden` and a stable `Pattern-Key` |
|
||||
| Similar to existing entry | Link with `**See Also**`, consider priority bump |
|
||||
| Broadly applicable learning | Promote to `CLAUDE.md`, `AGENTS.md`, and/or `.github/copilot-instructions.md` |
|
||||
| Workflow improvements | Promote to `AGENTS.md` (OpenClaw workspace) |
|
||||
| Tool gotchas | Promote to `TOOLS.md` (OpenClaw workspace) |
|
||||
| Behavioral patterns | Promote to `SOUL.md` (OpenClaw workspace) |
|
||||
|
||||
## OpenClaw Setup (Recommended)
|
||||
|
||||
OpenClaw is the primary platform for this skill. It uses workspace-based prompt injection with automatic skill loading.
|
||||
|
||||
### Installation
|
||||
|
||||
**Via ClawdHub (recommended):**
|
||||
```bash
|
||||
clawdhub install self-improving-agent
|
||||
```
|
||||
|
||||
**Manual:**
|
||||
```bash
|
||||
git clone https://github.com/peterskoett/self-improving-agent.git ~/.openclaw/skills/self-improving-agent
|
||||
```
|
||||
|
||||
Remade for openclaw from original repo : https://github.com/pskoett/pskoett-ai-skills - https://github.com/pskoett/pskoett-ai-skills/tree/main/skills/self-improvement
|
||||
|
||||
### Workspace Structure
|
||||
|
||||
OpenClaw injects these files into every session:
|
||||
|
||||
```
|
||||
~/.openclaw/workspace/
|
||||
├── AGENTS.md # Multi-agent workflows, delegation patterns
|
||||
├── SOUL.md # Behavioral guidelines, personality, principles
|
||||
├── TOOLS.md # Tool capabilities, integration gotchas
|
||||
├── MEMORY.md # Long-term memory (main session only)
|
||||
├── memory/ # Daily memory files
|
||||
│ └── YYYY-MM-DD.md
|
||||
└── .learnings/ # This skill's log files
|
||||
├── LEARNINGS.md
|
||||
├── ERRORS.md
|
||||
└── FEATURE_REQUESTS.md
|
||||
```
|
||||
|
||||
### Create Learning Files
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.openclaw/workspace/.learnings
|
||||
```
|
||||
|
||||
Then create the log files (or copy from `assets/`):
|
||||
- `LEARNINGS.md` — corrections, knowledge gaps, best practices
|
||||
- `ERRORS.md` — command failures, exceptions
|
||||
- `FEATURE_REQUESTS.md` — user-requested capabilities
|
||||
|
||||
### Promotion Targets
|
||||
|
||||
When learnings prove broadly applicable, promote them to workspace files:
|
||||
|
||||
| Learning Type | Promote To | Example |
|
||||
|---------------|------------|---------|
|
||||
| Behavioral patterns | `SOUL.md` | "Be concise, avoid disclaimers" |
|
||||
| Workflow improvements | `AGENTS.md` | "Spawn sub-agents for long tasks" |
|
||||
| Tool gotchas | `TOOLS.md` | "Git push needs auth configured first" |
|
||||
|
||||
### Inter-Session Communication
|
||||
|
||||
OpenClaw provides tools to share learnings across sessions:
|
||||
|
||||
- **sessions_list** — View active/recent sessions
|
||||
- **sessions_history** — Read another session's transcript
|
||||
- **sessions_send** — Send a learning to another session
|
||||
- **sessions_spawn** — Spawn a sub-agent for background work
|
||||
|
||||
### Optional: Enable Hook
|
||||
|
||||
For automatic reminders at session start:
|
||||
|
||||
```bash
|
||||
# Copy hook to OpenClaw hooks directory
|
||||
cp -r hooks/openclaw ~/.openclaw/hooks/self-improvement
|
||||
|
||||
# Enable it
|
||||
openclaw hooks enable self-improvement
|
||||
```
|
||||
|
||||
See `references/openclaw-integration.md` for complete details.
|
||||
|
||||
---
|
||||
|
||||
## Generic Setup (Other Agents)
|
||||
|
||||
For Claude Code, Codex, Copilot, or other agents, create `.learnings/` in your project:
|
||||
|
||||
```bash
|
||||
mkdir -p .learnings
|
||||
```
|
||||
|
||||
Copy templates from `assets/` or create files with headers.
|
||||
|
||||
### Add reference to agent files AGENTS.md, CLAUDE.md, or .github/copilot-instructions.md to remind yourself to log learnings. (this is an alternative to hook-based reminders)
|
||||
|
||||
#### Self-Improvement Workflow
|
||||
|
||||
When errors or corrections occur:
|
||||
1. Log to `.learnings/ERRORS.md`, `LEARNINGS.md`, or `FEATURE_REQUESTS.md`
|
||||
2. Review and promote broadly applicable learnings to:
|
||||
- `CLAUDE.md` - project facts and conventions
|
||||
- `AGENTS.md` - workflows and automation
|
||||
- `.github/copilot-instructions.md` - Copilot context
|
||||
|
||||
## Logging Format
|
||||
|
||||
### Learning Entry
|
||||
|
||||
Append to `.learnings/LEARNINGS.md`:
|
||||
|
||||
```markdown
|
||||
## [LRN-YYYYMMDD-XXX] category
|
||||
|
||||
**Logged**: ISO-8601 timestamp
|
||||
**Priority**: low | medium | high | critical
|
||||
**Status**: pending
|
||||
**Area**: frontend | backend | infra | tests | docs | config
|
||||
|
||||
### Summary
|
||||
One-line description of what was learned
|
||||
|
||||
### Details
|
||||
Full context: what happened, what was wrong, what's correct
|
||||
|
||||
### Suggested Action
|
||||
Specific fix or improvement to make
|
||||
|
||||
### Metadata
|
||||
- Source: conversation | error | user_feedback
|
||||
- Related Files: path/to/file.ext
|
||||
- Tags: tag1, tag2
|
||||
- See Also: LRN-20250110-001 (if related to existing entry)
|
||||
- Pattern-Key: simplify.dead_code | harden.input_validation (optional, for recurring-pattern tracking)
|
||||
- Recurrence-Count: 1 (optional)
|
||||
- First-Seen: 2025-01-15 (optional)
|
||||
- Last-Seen: 2025-01-15 (optional)
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
### Error Entry
|
||||
|
||||
Append to `.learnings/ERRORS.md`:
|
||||
|
||||
```markdown
|
||||
## [ERR-YYYYMMDD-XXX] skill_or_command_name
|
||||
|
||||
**Logged**: ISO-8601 timestamp
|
||||
**Priority**: high
|
||||
**Status**: pending
|
||||
**Area**: frontend | backend | infra | tests | docs | config
|
||||
|
||||
### Summary
|
||||
Brief description of what failed
|
||||
|
||||
### Error
|
||||
```
|
||||
Actual error message or output
|
||||
```
|
||||
|
||||
### Context
|
||||
- Command/operation attempted
|
||||
- Input or parameters used
|
||||
- Environment details if relevant
|
||||
|
||||
### Suggested Fix
|
||||
If identifiable, what might resolve this
|
||||
|
||||
### Metadata
|
||||
- Reproducible: yes | no | unknown
|
||||
- Related Files: path/to/file.ext
|
||||
- See Also: ERR-20250110-001 (if recurring)
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
### Feature Request Entry
|
||||
|
||||
Append to `.learnings/FEATURE_REQUESTS.md`:
|
||||
|
||||
```markdown
|
||||
## [FEAT-YYYYMMDD-XXX] capability_name
|
||||
|
||||
**Logged**: ISO-8601 timestamp
|
||||
**Priority**: medium
|
||||
**Status**: pending
|
||||
**Area**: frontend | backend | infra | tests | docs | config
|
||||
|
||||
### Requested Capability
|
||||
What the user wanted to do
|
||||
|
||||
### User Context
|
||||
Why they needed it, what problem they're solving
|
||||
|
||||
### Complexity Estimate
|
||||
simple | medium | complex
|
||||
|
||||
### Suggested Implementation
|
||||
How this could be built, what it might extend
|
||||
|
||||
### Metadata
|
||||
- Frequency: first_time | recurring
|
||||
- Related Features: existing_feature_name
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
## ID Generation
|
||||
|
||||
Format: `TYPE-YYYYMMDD-XXX`
|
||||
- TYPE: `LRN` (learning), `ERR` (error), `FEAT` (feature)
|
||||
- YYYYMMDD: Current date
|
||||
- XXX: Sequential number or random 3 chars (e.g., `001`, `A7B`)
|
||||
|
||||
Examples: `LRN-20250115-001`, `ERR-20250115-A3F`, `FEAT-20250115-002`
|
||||
|
||||
## Resolving Entries
|
||||
|
||||
When an issue is fixed, update the entry:
|
||||
|
||||
1. Change `**Status**: pending` → `**Status**: resolved`
|
||||
2. Add resolution block after Metadata:
|
||||
|
||||
```markdown
|
||||
### Resolution
|
||||
- **Resolved**: 2025-01-16T09:00:00Z
|
||||
- **Commit/PR**: abc123 or #42
|
||||
- **Notes**: Brief description of what was done
|
||||
```
|
||||
|
||||
Other status values:
|
||||
- `in_progress` - Actively being worked on
|
||||
- `wont_fix` - Decided not to address (add reason in Resolution notes)
|
||||
- `promoted` - Elevated to CLAUDE.md, AGENTS.md, or .github/copilot-instructions.md
|
||||
|
||||
## Promoting to Project Memory
|
||||
|
||||
When a learning is broadly applicable (not a one-off fix), promote it to permanent project memory.
|
||||
|
||||
### When to Promote
|
||||
|
||||
- Learning applies across multiple files/features
|
||||
- Knowledge any contributor (human or AI) should know
|
||||
- Prevents recurring mistakes
|
||||
- Documents project-specific conventions
|
||||
|
||||
### Promotion Targets
|
||||
|
||||
| Target | What Belongs There |
|
||||
|--------|-------------------|
|
||||
| `CLAUDE.md` | Project facts, conventions, gotchas for all Claude interactions |
|
||||
| `AGENTS.md` | Agent-specific workflows, tool usage patterns, automation rules |
|
||||
| `.github/copilot-instructions.md` | Project context and conventions for GitHub Copilot |
|
||||
| `SOUL.md` | Behavioral guidelines, communication style, principles (OpenClaw workspace) |
|
||||
| `TOOLS.md` | Tool capabilities, usage patterns, integration gotchas (OpenClaw workspace) |
|
||||
|
||||
### How to Promote
|
||||
|
||||
1. **Distill** the learning into a concise rule or fact
|
||||
2. **Add** to appropriate section in target file (create file if needed)
|
||||
3. **Update** original entry:
|
||||
- Change `**Status**: pending` → `**Status**: promoted`
|
||||
- Add `**Promoted**: CLAUDE.md`, `AGENTS.md`, or `.github/copilot-instructions.md`
|
||||
|
||||
### Promotion Examples
|
||||
|
||||
**Learning** (verbose):
|
||||
> Project uses pnpm workspaces. Attempted `npm install` but failed.
|
||||
> Lock file is `pnpm-lock.yaml`. Must use `pnpm install`.
|
||||
|
||||
**In CLAUDE.md** (concise):
|
||||
```markdown
|
||||
## Build & Dependencies
|
||||
- Package manager: pnpm (not npm) - use `pnpm install`
|
||||
```
|
||||
|
||||
**Learning** (verbose):
|
||||
> When modifying API endpoints, must regenerate TypeScript client.
|
||||
> Forgetting this causes type mismatches at runtime.
|
||||
|
||||
**In AGENTS.md** (actionable):
|
||||
```markdown
|
||||
## After API Changes
|
||||
1. Regenerate client: `pnpm run generate:api`
|
||||
2. Check for type errors: `pnpm tsc --noEmit`
|
||||
```
|
||||
|
||||
## Recurring Pattern Detection
|
||||
|
||||
If logging something similar to an existing entry:
|
||||
|
||||
1. **Search first**: `grep -r "keyword" .learnings/`
|
||||
2. **Link entries**: Add `**See Also**: ERR-20250110-001` in Metadata
|
||||
3. **Bump priority** if issue keeps recurring
|
||||
4. **Consider systemic fix**: Recurring issues often indicate:
|
||||
- Missing documentation (→ promote to CLAUDE.md or .github/copilot-instructions.md)
|
||||
- Missing automation (→ add to AGENTS.md)
|
||||
- Architectural problem (→ create tech debt ticket)
|
||||
|
||||
## Simplify & Harden Feed
|
||||
|
||||
Use this workflow to ingest recurring patterns from the `simplify-and-harden`
|
||||
skill and turn them into durable prompt guidance.
|
||||
|
||||
### Ingestion Workflow
|
||||
|
||||
1. Read `simplify_and_harden.learning_loop.candidates` from the task summary.
|
||||
2. For each candidate, use `pattern_key` as the stable dedupe key.
|
||||
3. Search `.learnings/LEARNINGS.md` for an existing entry with that key:
|
||||
- `grep -n "Pattern-Key: <pattern_key>" .learnings/LEARNINGS.md`
|
||||
4. If found:
|
||||
- Increment `Recurrence-Count`
|
||||
- Update `Last-Seen`
|
||||
- Add `See Also` links to related entries/tasks
|
||||
5. If not found:
|
||||
- Create a new `LRN-...` entry
|
||||
- Set `Source: simplify-and-harden`
|
||||
- Set `Pattern-Key`, `Recurrence-Count: 1`, and `First-Seen`/`Last-Seen`
|
||||
|
||||
### Promotion Rule (System Prompt Feedback)
|
||||
|
||||
Promote recurring patterns into agent context/system prompt files when all are true:
|
||||
|
||||
- `Recurrence-Count >= 3`
|
||||
- Seen across at least 2 distinct tasks
|
||||
- Occurred within a 30-day window
|
||||
|
||||
Promotion targets:
|
||||
- `CLAUDE.md`
|
||||
- `AGENTS.md`
|
||||
- `.github/copilot-instructions.md`
|
||||
- `SOUL.md` / `TOOLS.md` for OpenClaw workspace-level guidance when applicable
|
||||
|
||||
Write promoted rules as short prevention rules (what to do before/while coding),
|
||||
not long incident write-ups.
|
||||
|
||||
## Periodic Review
|
||||
|
||||
Review `.learnings/` at natural breakpoints:
|
||||
|
||||
### When to Review
|
||||
- Before starting a new major task
|
||||
- After completing a feature
|
||||
- When working in an area with past learnings
|
||||
- Weekly during active development
|
||||
|
||||
### Quick Status Check
|
||||
```bash
|
||||
# Count pending items
|
||||
grep -h "Status\*\*: pending" .learnings/*.md | wc -l
|
||||
|
||||
# List pending high-priority items
|
||||
grep -B5 "Priority\*\*: high" .learnings/*.md | grep "^## \["
|
||||
|
||||
# Find learnings for a specific area
|
||||
grep -l "Area\*\*: backend" .learnings/*.md
|
||||
```
|
||||
|
||||
### Review Actions
|
||||
- Resolve fixed items
|
||||
- Promote applicable learnings
|
||||
- Link related entries
|
||||
- Escalate recurring issues
|
||||
|
||||
## Detection Triggers
|
||||
|
||||
Automatically log when you notice:
|
||||
|
||||
**Corrections** (→ learning with `correction` category):
|
||||
- "No, that's not right..."
|
||||
- "Actually, it should be..."
|
||||
- "You're wrong about..."
|
||||
- "That's outdated..."
|
||||
|
||||
**Feature Requests** (→ feature request):
|
||||
- "Can you also..."
|
||||
- "I wish you could..."
|
||||
- "Is there a way to..."
|
||||
- "Why can't you..."
|
||||
|
||||
**Knowledge Gaps** (→ learning with `knowledge_gap` category):
|
||||
- User provides information you didn't know
|
||||
- Documentation you referenced is outdated
|
||||
- API behavior differs from your understanding
|
||||
|
||||
**Errors** (→ error entry):
|
||||
- Command returns non-zero exit code
|
||||
- Exception or stack trace
|
||||
- Unexpected output or behavior
|
||||
- Timeout or connection failure
|
||||
|
||||
## Priority Guidelines
|
||||
|
||||
| Priority | When to Use |
|
||||
|----------|-------------|
|
||||
| `critical` | Blocks core functionality, data loss risk, security issue |
|
||||
| `high` | Significant impact, affects common workflows, recurring issue |
|
||||
| `medium` | Moderate impact, workaround exists |
|
||||
| `low` | Minor inconvenience, edge case, nice-to-have |
|
||||
|
||||
## Area Tags
|
||||
|
||||
Use to filter learnings by codebase region:
|
||||
|
||||
| Area | Scope |
|
||||
|------|-------|
|
||||
| `frontend` | UI, components, client-side code |
|
||||
| `backend` | API, services, server-side code |
|
||||
| `infra` | CI/CD, deployment, Docker, cloud |
|
||||
| `tests` | Test files, testing utilities, coverage |
|
||||
| `docs` | Documentation, comments, READMEs |
|
||||
| `config` | Configuration files, environment, settings |
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Log immediately** - context is freshest right after the issue
|
||||
2. **Be specific** - future agents need to understand quickly
|
||||
3. **Include reproduction steps** - especially for errors
|
||||
4. **Link related files** - makes fixes easier
|
||||
5. **Suggest concrete fixes** - not just "investigate"
|
||||
6. **Use consistent categories** - enables filtering
|
||||
7. **Promote aggressively** - if in doubt, add to CLAUDE.md or .github/copilot-instructions.md
|
||||
8. **Review regularly** - stale learnings lose value
|
||||
|
||||
## Gitignore Options
|
||||
|
||||
**Keep learnings local** (per-developer):
|
||||
```gitignore
|
||||
.learnings/
|
||||
```
|
||||
|
||||
**Track learnings in repo** (team-wide):
|
||||
Don't add to .gitignore - learnings become shared knowledge.
|
||||
|
||||
**Hybrid** (track templates, ignore entries):
|
||||
```gitignore
|
||||
.learnings/*.md
|
||||
!.learnings/.gitkeep
|
||||
```
|
||||
|
||||
## Hook Integration
|
||||
|
||||
Enable automatic reminders through agent hooks. This is **opt-in** - you must explicitly configure hooks.
|
||||
|
||||
### Quick Setup (Claude Code / Codex)
|
||||
|
||||
Create `.claude/settings.json` in your project:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [{
|
||||
"matcher": "",
|
||||
"hooks": [{
|
||||
"type": "command",
|
||||
"command": "./skills/self-improvement/scripts/activator.sh"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This injects a learning evaluation reminder after each prompt (~50-100 tokens overhead).
|
||||
|
||||
### Full Setup (With Error Detection)
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [{
|
||||
"matcher": "",
|
||||
"hooks": [{
|
||||
"type": "command",
|
||||
"command": "./skills/self-improvement/scripts/activator.sh"
|
||||
}]
|
||||
}],
|
||||
"PostToolUse": [{
|
||||
"matcher": "Bash",
|
||||
"hooks": [{
|
||||
"type": "command",
|
||||
"command": "./skills/self-improvement/scripts/error-detector.sh"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Available Hook Scripts
|
||||
|
||||
| Script | Hook Type | Purpose |
|
||||
|--------|-----------|---------|
|
||||
| `scripts/activator.sh` | UserPromptSubmit | Reminds to evaluate learnings after tasks |
|
||||
| `scripts/error-detector.sh` | PostToolUse (Bash) | Triggers on command errors |
|
||||
|
||||
See `references/hooks-setup.md` for detailed configuration and troubleshooting.
|
||||
|
||||
## Automatic Skill Extraction
|
||||
|
||||
When a learning is valuable enough to become a reusable skill, extract it using the provided helper.
|
||||
|
||||
### Skill Extraction Criteria
|
||||
|
||||
A learning qualifies for skill extraction when ANY of these apply:
|
||||
|
||||
| Criterion | Description |
|
||||
|-----------|-------------|
|
||||
| **Recurring** | Has `See Also` links to 2+ similar issues |
|
||||
| **Verified** | Status is `resolved` with working fix |
|
||||
| **Non-obvious** | Required actual debugging/investigation to discover |
|
||||
| **Broadly applicable** | Not project-specific; useful across codebases |
|
||||
| **User-flagged** | User says "save this as a skill" or similar |
|
||||
|
||||
### Extraction Workflow
|
||||
|
||||
1. **Identify candidate**: Learning meets extraction criteria
|
||||
2. **Run helper** (or create manually):
|
||||
```bash
|
||||
./skills/self-improvement/scripts/extract-skill.sh skill-name --dry-run
|
||||
./skills/self-improvement/scripts/extract-skill.sh skill-name
|
||||
```
|
||||
3. **Customize SKILL.md**: Fill in template with learning content
|
||||
4. **Update learning**: Set status to `promoted_to_skill`, add `Skill-Path`
|
||||
5. **Verify**: Read skill in fresh session to ensure it's self-contained
|
||||
|
||||
### Manual Extraction
|
||||
|
||||
If you prefer manual creation:
|
||||
|
||||
1. Create `skills/<skill-name>/SKILL.md`
|
||||
2. Use template from `assets/SKILL-TEMPLATE.md`
|
||||
3. Follow [Agent Skills spec](https://agentskills.io/specification):
|
||||
- YAML frontmatter with `name` and `description`
|
||||
- Name must match folder name
|
||||
- No README.md inside skill folder
|
||||
|
||||
### Extraction Detection Triggers
|
||||
|
||||
Watch for these signals that a learning should become a skill:
|
||||
|
||||
**In conversation:**
|
||||
- "Save this as a skill"
|
||||
- "I keep running into this"
|
||||
- "This would be useful for other projects"
|
||||
- "Remember this pattern"
|
||||
|
||||
**In learning entries:**
|
||||
- Multiple `See Also` links (recurring issue)
|
||||
- High priority + resolved status
|
||||
- Category: `best_practice` with broad applicability
|
||||
- User feedback praising the solution
|
||||
|
||||
### Skill Quality Gates
|
||||
|
||||
Before extraction, verify:
|
||||
|
||||
- [ ] Solution is tested and working
|
||||
- [ ] Description is clear without original context
|
||||
- [ ] Code examples are self-contained
|
||||
- [ ] No project-specific hardcoded values
|
||||
- [ ] Follows skill naming conventions (lowercase, hyphens)
|
||||
|
||||
## Multi-Agent Support
|
||||
|
||||
This skill works across different AI coding agents with agent-specific activation.
|
||||
|
||||
### Claude Code
|
||||
|
||||
**Activation**: Hooks (UserPromptSubmit, PostToolUse)
|
||||
**Setup**: `.claude/settings.json` with hook configuration
|
||||
**Detection**: Automatic via hook scripts
|
||||
|
||||
### Codex CLI
|
||||
|
||||
**Activation**: Hooks (same pattern as Claude Code)
|
||||
**Setup**: `.codex/settings.json` with hook configuration
|
||||
**Detection**: Automatic via hook scripts
|
||||
|
||||
### GitHub Copilot
|
||||
|
||||
**Activation**: Manual (no hook support)
|
||||
**Setup**: Add to `.github/copilot-instructions.md`:
|
||||
|
||||
```markdown
|
||||
## Self-Improvement
|
||||
|
||||
After solving non-obvious issues, consider logging to `.learnings/`:
|
||||
1. Use format from self-improvement skill
|
||||
2. Link related entries with See Also
|
||||
3. Promote high-value learnings to skills
|
||||
|
||||
Ask in chat: "Should I log this as a learning?"
|
||||
```
|
||||
|
||||
**Detection**: Manual review at session end
|
||||
|
||||
### OpenClaw
|
||||
|
||||
**Activation**: Workspace injection + inter-agent messaging
|
||||
**Setup**: See "OpenClaw Setup" section above
|
||||
**Detection**: Via session tools and workspace files
|
||||
|
||||
### Agent-Agnostic Guidance
|
||||
|
||||
Regardless of agent, apply self-improvement when you:
|
||||
|
||||
1. **Discover something non-obvious** - solution wasn't immediate
|
||||
2. **Correct yourself** - initial approach was wrong
|
||||
3. **Learn project conventions** - discovered undocumented patterns
|
||||
4. **Hit unexpected errors** - especially if diagnosis was difficult
|
||||
5. **Find better approaches** - improved on your original solution
|
||||
|
||||
### Copilot Chat Integration
|
||||
|
||||
For Copilot users, add this to your prompts when relevant:
|
||||
|
||||
> After completing this task, evaluate if any learnings should be logged to `.learnings/` using the self-improvement skill format.
|
||||
|
||||
Or use quick prompts:
|
||||
- "Log this to learnings"
|
||||
- "Create a skill from this solution"
|
||||
- "Check .learnings/ for related issues"
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"ownerId": "kn70cjr952qdec1nx70zs6wefn7ynq2t",
|
||||
"slug": "self-improving-agent",
|
||||
"version": "3.0.6",
|
||||
"publishedAt": 1774365304323
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
# Learnings
|
||||
|
||||
Corrections, insights, and knowledge gaps captured during development.
|
||||
|
||||
**Categories**: correction | insight | knowledge_gap | best_practice
|
||||
**Areas**: frontend | backend | infra | tests | docs | config
|
||||
**Statuses**: pending | in_progress | resolved | wont_fix | promoted | promoted_to_skill
|
||||
|
||||
## Status Definitions
|
||||
|
||||
| Status | Meaning |
|
||||
|--------|---------|
|
||||
| `pending` | Not yet addressed |
|
||||
| `in_progress` | Actively being worked on |
|
||||
| `resolved` | Issue fixed or knowledge integrated |
|
||||
| `wont_fix` | Decided not to address (reason in Resolution) |
|
||||
| `promoted` | Elevated to CLAUDE.md, AGENTS.md, or copilot-instructions.md |
|
||||
| `promoted_to_skill` | Extracted as a reusable skill |
|
||||
|
||||
## Skill Extraction Fields
|
||||
|
||||
When a learning is promoted to a skill, add these fields:
|
||||
|
||||
```markdown
|
||||
**Status**: promoted_to_skill
|
||||
**Skill-Path**: skills/skill-name
|
||||
```
|
||||
|
||||
Example:
|
||||
```markdown
|
||||
## [LRN-20250115-001] best_practice
|
||||
|
||||
**Logged**: 2025-01-15T10:00:00Z
|
||||
**Priority**: high
|
||||
**Status**: promoted_to_skill
|
||||
**Skill-Path**: skills/docker-m1-fixes
|
||||
**Area**: infra
|
||||
|
||||
### Summary
|
||||
Docker build fails on Apple Silicon due to platform mismatch
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
# Skill Template
|
||||
|
||||
Template for creating skills extracted from learnings. Copy and customize.
|
||||
|
||||
---
|
||||
|
||||
## SKILL.md Template
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: skill-name-here
|
||||
description: "Concise description of when and why to use this skill. Include trigger conditions."
|
||||
---
|
||||
|
||||
# Skill Name
|
||||
|
||||
Brief introduction explaining the problem this skill solves and its origin.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Situation | Action |
|
||||
|-----------|--------|
|
||||
| [Trigger 1] | [Action 1] |
|
||||
| [Trigger 2] | [Action 2] |
|
||||
|
||||
## Background
|
||||
|
||||
Why this knowledge matters. What problems it prevents. Context from the original learning.
|
||||
|
||||
## Solution
|
||||
|
||||
### Step-by-Step
|
||||
|
||||
1. First step with code or command
|
||||
2. Second step
|
||||
3. Verification step
|
||||
|
||||
### Code Example
|
||||
|
||||
\`\`\`language
|
||||
// Example code demonstrating the solution
|
||||
\`\`\`
|
||||
|
||||
## Common Variations
|
||||
|
||||
- **Variation A**: Description and how to handle
|
||||
- **Variation B**: Description and how to handle
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Warning or common mistake #1
|
||||
- Warning or common mistake #2
|
||||
|
||||
## Related
|
||||
|
||||
- Link to related documentation
|
||||
- Link to related skill
|
||||
|
||||
## Source
|
||||
|
||||
Extracted from learning entry.
|
||||
- **Learning ID**: LRN-YYYYMMDD-XXX
|
||||
- **Original Category**: correction | insight | knowledge_gap | best_practice
|
||||
- **Extraction Date**: YYYY-MM-DD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Minimal Template
|
||||
|
||||
For simple skills that don't need all sections:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: skill-name-here
|
||||
description: "What this skill does and when to use it."
|
||||
---
|
||||
|
||||
# Skill Name
|
||||
|
||||
[Problem statement in one sentence]
|
||||
|
||||
## Solution
|
||||
|
||||
[Direct solution with code/commands]
|
||||
|
||||
## Source
|
||||
|
||||
- Learning ID: LRN-YYYYMMDD-XXX
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Template with Scripts
|
||||
|
||||
For skills that include executable helpers:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: skill-name-here
|
||||
description: "What this skill does and when to use it."
|
||||
---
|
||||
|
||||
# Skill Name
|
||||
|
||||
[Introduction]
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `./scripts/helper.sh` | [What it does] |
|
||||
| `./scripts/validate.sh` | [What it does] |
|
||||
|
||||
## Usage
|
||||
|
||||
### Automated (Recommended)
|
||||
|
||||
\`\`\`bash
|
||||
./skills/skill-name/scripts/helper.sh [args]
|
||||
\`\`\`
|
||||
|
||||
### Manual Steps
|
||||
|
||||
1. Step one
|
||||
2. Step two
|
||||
|
||||
## Scripts
|
||||
|
||||
| Script | Description |
|
||||
|--------|-------------|
|
||||
| `scripts/helper.sh` | Main utility |
|
||||
| `scripts/validate.sh` | Validation checker |
|
||||
|
||||
## Source
|
||||
|
||||
- Learning ID: LRN-YYYYMMDD-XXX
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
- **Skill name**: lowercase, hyphens for spaces
|
||||
- Good: `docker-m1-fixes`, `api-timeout-patterns`
|
||||
- Bad: `Docker_M1_Fixes`, `APITimeoutPatterns`
|
||||
|
||||
- **Description**: Start with action verb, mention trigger
|
||||
- Good: "Handles Docker build failures on Apple Silicon. Use when builds fail with platform mismatch."
|
||||
- Bad: "Docker stuff"
|
||||
|
||||
- **Files**:
|
||||
- `SKILL.md` - Required, main documentation
|
||||
- `scripts/` - Optional, executable code
|
||||
- `references/` - Optional, detailed docs
|
||||
- `assets/` - Optional, templates
|
||||
|
||||
---
|
||||
|
||||
## Extraction Checklist
|
||||
|
||||
Before creating a skill from a learning:
|
||||
|
||||
- [ ] Learning is verified (status: resolved)
|
||||
- [ ] Solution is broadly applicable (not one-off)
|
||||
- [ ] Content is complete (has all needed context)
|
||||
- [ ] Name follows conventions
|
||||
- [ ] Description is concise but informative
|
||||
- [ ] Quick Reference table is actionable
|
||||
- [ ] Code examples are tested
|
||||
- [ ] Source learning ID is recorded
|
||||
|
||||
After creating:
|
||||
|
||||
- [ ] Update original learning with `promoted_to_skill` status
|
||||
- [ ] Add `Skill-Path: skills/skill-name` to learning metadata
|
||||
- [ ] Test skill by reading it in a fresh session
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
name: self-improvement
|
||||
description: "Injects self-improvement reminder during agent bootstrap"
|
||||
metadata: {"openclaw":{"emoji":"🧠","events":["agent:bootstrap"]}}
|
||||
---
|
||||
|
||||
# Self-Improvement Hook
|
||||
|
||||
Injects a reminder to evaluate learnings during agent bootstrap.
|
||||
|
||||
## What It Does
|
||||
|
||||
- Fires on `agent:bootstrap` (before workspace files are injected)
|
||||
- Adds a reminder block to check `.learnings/` for relevant entries
|
||||
- Prompts the agent to log corrections, errors, and discoveries
|
||||
|
||||
## Configuration
|
||||
|
||||
No configuration needed. Enable with:
|
||||
|
||||
```bash
|
||||
openclaw hooks enable self-improvement
|
||||
```
|
||||
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* Self-Improvement Hook for OpenClaw
|
||||
*
|
||||
* Injects a reminder to evaluate learnings during agent bootstrap.
|
||||
* Fires on agent:bootstrap event before workspace files are injected.
|
||||
*/
|
||||
|
||||
const REMINDER_CONTENT = `
|
||||
## Self-Improvement Reminder
|
||||
|
||||
After completing tasks, evaluate if any learnings should be captured:
|
||||
|
||||
**Log when:**
|
||||
- User corrects you → \`.learnings/LEARNINGS.md\`
|
||||
- Command/operation fails → \`.learnings/ERRORS.md\`
|
||||
- User wants missing capability → \`.learnings/FEATURE_REQUESTS.md\`
|
||||
- You discover your knowledge was wrong → \`.learnings/LEARNINGS.md\`
|
||||
- You find a better approach → \`.learnings/LEARNINGS.md\`
|
||||
|
||||
**Promote when pattern is proven:**
|
||||
- Behavioral patterns → \`SOUL.md\`
|
||||
- Workflow improvements → \`AGENTS.md\`
|
||||
- Tool gotchas → \`TOOLS.md\`
|
||||
|
||||
Keep entries simple: date, title, what happened, what to do differently.
|
||||
`.trim();
|
||||
|
||||
const handler = async (event) => {
|
||||
// Safety checks for event structure
|
||||
if (!event || typeof event !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only handle agent:bootstrap events
|
||||
if (event.type !== 'agent' || event.action !== 'bootstrap') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Safety check for context
|
||||
if (!event.context || typeof event.context !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject the reminder as a virtual bootstrap file
|
||||
// Check that bootstrapFiles is an array before pushing
|
||||
if (Array.isArray(event.context.bootstrapFiles)) {
|
||||
event.context.bootstrapFiles.push({
|
||||
path: 'SELF_IMPROVEMENT_REMINDER.md',
|
||||
content: REMINDER_CONTENT,
|
||||
virtual: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = handler;
|
||||
module.exports.default = handler;
|
||||
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
* Self-Improvement Hook for OpenClaw
|
||||
*
|
||||
* Injects a reminder to evaluate learnings during agent bootstrap.
|
||||
* Fires on agent:bootstrap event before workspace files are injected.
|
||||
*/
|
||||
|
||||
import type { HookHandler } from 'openclaw/hooks';
|
||||
|
||||
const REMINDER_CONTENT = `## Self-Improvement Reminder
|
||||
|
||||
After completing tasks, evaluate if any learnings should be captured:
|
||||
|
||||
**Log when:**
|
||||
- User corrects you → \`.learnings/LEARNINGS.md\`
|
||||
- Command/operation fails → \`.learnings/ERRORS.md\`
|
||||
- User wants missing capability → \`.learnings/FEATURE_REQUESTS.md\`
|
||||
- You discover your knowledge was wrong → \`.learnings/LEARNINGS.md\`
|
||||
- You find a better approach → \`.learnings/LEARNINGS.md\`
|
||||
|
||||
**Promote when pattern is proven:**
|
||||
- Behavioral patterns → \`SOUL.md\`
|
||||
- Workflow improvements → \`AGENTS.md\`
|
||||
- Tool gotchas → \`TOOLS.md\`
|
||||
|
||||
Keep entries simple: date, title, what happened, what to do differently.`;
|
||||
|
||||
const handler: HookHandler = async (event) => {
|
||||
// Safety checks for event structure
|
||||
if (!event || typeof event !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only handle agent:bootstrap events
|
||||
if (event.type !== 'agent' || event.action !== 'bootstrap') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Safety check for context
|
||||
if (!event.context || typeof event.context !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip sub-agent sessions to avoid bootstrap issues
|
||||
// Sub-agents have sessionKey patterns like "agent:main:subagent:..."
|
||||
const sessionKey = event.sessionKey || '';
|
||||
if (sessionKey.includes(':subagent:')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject the reminder as a virtual bootstrap file
|
||||
// Check that bootstrapFiles is an array before pushing
|
||||
if (Array.isArray(event.context.bootstrapFiles)) {
|
||||
event.context.bootstrapFiles.push({
|
||||
path: 'SELF_IMPROVEMENT_REMINDER.md',
|
||||
content: REMINDER_CONTENT,
|
||||
virtual: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default handler;
|
||||
@@ -1,374 +0,0 @@
|
||||
# Entry Examples
|
||||
|
||||
Concrete examples of well-formatted entries with all fields.
|
||||
|
||||
## Learning: Correction
|
||||
|
||||
```markdown
|
||||
## [LRN-20250115-001] correction
|
||||
|
||||
**Logged**: 2025-01-15T10:30:00Z
|
||||
**Priority**: high
|
||||
**Status**: pending
|
||||
**Area**: tests
|
||||
|
||||
### Summary
|
||||
Incorrectly assumed pytest fixtures are scoped to function by default
|
||||
|
||||
### Details
|
||||
When writing test fixtures, I assumed all fixtures were function-scoped.
|
||||
User corrected that while function scope is the default, the codebase
|
||||
convention uses module-scoped fixtures for database connections to
|
||||
improve test performance.
|
||||
|
||||
### Suggested Action
|
||||
When creating fixtures that involve expensive setup (DB, network),
|
||||
check existing fixtures for scope patterns before defaulting to function scope.
|
||||
|
||||
### Metadata
|
||||
- Source: user_feedback
|
||||
- Related Files: tests/conftest.py
|
||||
- Tags: pytest, testing, fixtures
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
## Learning: Knowledge Gap (Resolved)
|
||||
|
||||
```markdown
|
||||
## [LRN-20250115-002] knowledge_gap
|
||||
|
||||
**Logged**: 2025-01-15T14:22:00Z
|
||||
**Priority**: medium
|
||||
**Status**: resolved
|
||||
**Area**: config
|
||||
|
||||
### Summary
|
||||
Project uses pnpm not npm for package management
|
||||
|
||||
### Details
|
||||
Attempted to run `npm install` but project uses pnpm workspaces.
|
||||
Lock file is `pnpm-lock.yaml`, not `package-lock.json`.
|
||||
|
||||
### Suggested Action
|
||||
Check for `pnpm-lock.yaml` or `pnpm-workspace.yaml` before assuming npm.
|
||||
Use `pnpm install` for this project.
|
||||
|
||||
### Metadata
|
||||
- Source: error
|
||||
- Related Files: pnpm-lock.yaml, pnpm-workspace.yaml
|
||||
- Tags: package-manager, pnpm, setup
|
||||
|
||||
### Resolution
|
||||
- **Resolved**: 2025-01-15T14:30:00Z
|
||||
- **Commit/PR**: N/A - knowledge update
|
||||
- **Notes**: Added to CLAUDE.md for future reference
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
## Learning: Promoted to CLAUDE.md
|
||||
|
||||
```markdown
|
||||
## [LRN-20250115-003] best_practice
|
||||
|
||||
**Logged**: 2025-01-15T16:00:00Z
|
||||
**Priority**: high
|
||||
**Status**: promoted
|
||||
**Promoted**: CLAUDE.md
|
||||
**Area**: backend
|
||||
|
||||
### Summary
|
||||
API responses must include correlation ID from request headers
|
||||
|
||||
### Details
|
||||
All API responses should echo back the X-Correlation-ID header from
|
||||
the request. This is required for distributed tracing. Responses
|
||||
without this header break the observability pipeline.
|
||||
|
||||
### Suggested Action
|
||||
Always include correlation ID passthrough in API handlers.
|
||||
|
||||
### Metadata
|
||||
- Source: user_feedback
|
||||
- Related Files: src/middleware/correlation.ts
|
||||
- Tags: api, observability, tracing
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
## Learning: Promoted to AGENTS.md
|
||||
|
||||
```markdown
|
||||
## [LRN-20250116-001] best_practice
|
||||
|
||||
**Logged**: 2025-01-16T09:00:00Z
|
||||
**Priority**: high
|
||||
**Status**: promoted
|
||||
**Promoted**: AGENTS.md
|
||||
**Area**: backend
|
||||
|
||||
### Summary
|
||||
Must regenerate API client after OpenAPI spec changes
|
||||
|
||||
### Details
|
||||
When modifying API endpoints, the TypeScript client must be regenerated.
|
||||
Forgetting this causes type mismatches that only appear at runtime.
|
||||
The generate script also runs validation.
|
||||
|
||||
### Suggested Action
|
||||
Add to agent workflow: after any API changes, run `pnpm run generate:api`.
|
||||
|
||||
### Metadata
|
||||
- Source: error
|
||||
- Related Files: openapi.yaml, src/client/api.ts
|
||||
- Tags: api, codegen, typescript
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
## Error Entry
|
||||
|
||||
```markdown
|
||||
## [ERR-20250115-A3F] docker_build
|
||||
|
||||
**Logged**: 2025-01-15T09:15:00Z
|
||||
**Priority**: high
|
||||
**Status**: pending
|
||||
**Area**: infra
|
||||
|
||||
### Summary
|
||||
Docker build fails on M1 Mac due to platform mismatch
|
||||
|
||||
### Error
|
||||
```
|
||||
error: failed to solve: python:3.11-slim: no match for platform linux/arm64
|
||||
```
|
||||
|
||||
### Context
|
||||
- Command: `docker build -t myapp .`
|
||||
- Dockerfile uses `FROM python:3.11-slim`
|
||||
- Running on Apple Silicon (M1/M2)
|
||||
|
||||
### Suggested Fix
|
||||
Add platform flag: `docker build --platform linux/amd64 -t myapp .`
|
||||
Or update Dockerfile: `FROM --platform=linux/amd64 python:3.11-slim`
|
||||
|
||||
### Metadata
|
||||
- Reproducible: yes
|
||||
- Related Files: Dockerfile
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
## Error Entry: Recurring Issue
|
||||
|
||||
```markdown
|
||||
## [ERR-20250120-B2C] api_timeout
|
||||
|
||||
**Logged**: 2025-01-20T11:30:00Z
|
||||
**Priority**: critical
|
||||
**Status**: pending
|
||||
**Area**: backend
|
||||
|
||||
### Summary
|
||||
Third-party payment API timeout during checkout
|
||||
|
||||
### Error
|
||||
```
|
||||
TimeoutError: Request to payments.example.com timed out after 30000ms
|
||||
```
|
||||
|
||||
### Context
|
||||
- Command: POST /api/checkout
|
||||
- Timeout set to 30s
|
||||
- Occurs during peak hours (lunch, evening)
|
||||
|
||||
### Suggested Fix
|
||||
Implement retry with exponential backoff. Consider circuit breaker pattern.
|
||||
|
||||
### Metadata
|
||||
- Reproducible: yes (during peak hours)
|
||||
- Related Files: src/services/payment.ts
|
||||
- See Also: ERR-20250115-X1Y, ERR-20250118-Z3W
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
## Feature Request
|
||||
|
||||
```markdown
|
||||
## [FEAT-20250115-001] export_to_csv
|
||||
|
||||
**Logged**: 2025-01-15T16:45:00Z
|
||||
**Priority**: medium
|
||||
**Status**: pending
|
||||
**Area**: backend
|
||||
|
||||
### Requested Capability
|
||||
Export analysis results to CSV format
|
||||
|
||||
### User Context
|
||||
User runs weekly reports and needs to share results with non-technical
|
||||
stakeholders in Excel. Currently copies output manually.
|
||||
|
||||
### Complexity Estimate
|
||||
simple
|
||||
|
||||
### Suggested Implementation
|
||||
Add `--output csv` flag to the analyze command. Use standard csv module.
|
||||
Could extend existing `--output json` pattern.
|
||||
|
||||
### Metadata
|
||||
- Frequency: recurring
|
||||
- Related Features: analyze command, json output
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
## Feature Request: Resolved
|
||||
|
||||
```markdown
|
||||
## [FEAT-20250110-002] dark_mode
|
||||
|
||||
**Logged**: 2025-01-10T14:00:00Z
|
||||
**Priority**: low
|
||||
**Status**: resolved
|
||||
**Area**: frontend
|
||||
|
||||
### Requested Capability
|
||||
Dark mode support for the dashboard
|
||||
|
||||
### User Context
|
||||
User works late hours and finds the bright interface straining.
|
||||
Several other users have mentioned this informally.
|
||||
|
||||
### Complexity Estimate
|
||||
medium
|
||||
|
||||
### Suggested Implementation
|
||||
Use CSS variables for colors. Add toggle in user settings.
|
||||
Consider system preference detection.
|
||||
|
||||
### Metadata
|
||||
- Frequency: recurring
|
||||
- Related Features: user settings, theme system
|
||||
|
||||
### Resolution
|
||||
- **Resolved**: 2025-01-18T16:00:00Z
|
||||
- **Commit/PR**: #142
|
||||
- **Notes**: Implemented with system preference detection and manual toggle
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
## Learning: Promoted to Skill
|
||||
|
||||
```markdown
|
||||
## [LRN-20250118-001] best_practice
|
||||
|
||||
**Logged**: 2025-01-18T11:00:00Z
|
||||
**Priority**: high
|
||||
**Status**: promoted_to_skill
|
||||
**Skill-Path**: skills/docker-m1-fixes
|
||||
**Area**: infra
|
||||
|
||||
### Summary
|
||||
Docker build fails on Apple Silicon due to platform mismatch
|
||||
|
||||
### Details
|
||||
When building Docker images on M1/M2 Macs, the build fails because
|
||||
the base image doesn't have an ARM64 variant. This is a common issue
|
||||
that affects many developers.
|
||||
|
||||
### Suggested Action
|
||||
Add `--platform linux/amd64` to docker build command, or use
|
||||
`FROM --platform=linux/amd64` in Dockerfile.
|
||||
|
||||
### Metadata
|
||||
- Source: error
|
||||
- Related Files: Dockerfile
|
||||
- Tags: docker, arm64, m1, apple-silicon
|
||||
- See Also: ERR-20250115-A3F, ERR-20250117-B2D
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
## Extracted Skill Example
|
||||
|
||||
When the above learning is extracted as a skill, it becomes:
|
||||
|
||||
**File**: `skills/docker-m1-fixes/SKILL.md`
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: docker-m1-fixes
|
||||
description: "Fixes Docker build failures on Apple Silicon (M1/M2). Use when docker build fails with platform mismatch errors."
|
||||
---
|
||||
|
||||
# Docker M1 Fixes
|
||||
|
||||
Solutions for Docker build issues on Apple Silicon Macs.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Error | Fix |
|
||||
|-------|-----|
|
||||
| `no match for platform linux/arm64` | Add `--platform linux/amd64` to build |
|
||||
| Image runs but crashes | Use emulation or find ARM-compatible base |
|
||||
|
||||
## The Problem
|
||||
|
||||
Many Docker base images don't have ARM64 variants. When building on
|
||||
Apple Silicon (M1/M2/M3), Docker attempts to pull ARM64 images by
|
||||
default, causing platform mismatch errors.
|
||||
|
||||
## Solutions
|
||||
|
||||
### Option 1: Build Flag (Recommended)
|
||||
|
||||
Add platform flag to your build command:
|
||||
|
||||
\`\`\`bash
|
||||
docker build --platform linux/amd64 -t myapp .
|
||||
\`\`\`
|
||||
|
||||
### Option 2: Dockerfile Modification
|
||||
|
||||
Specify platform in the FROM instruction:
|
||||
|
||||
\`\`\`dockerfile
|
||||
FROM --platform=linux/amd64 python:3.11-slim
|
||||
\`\`\`
|
||||
|
||||
### Option 3: Docker Compose
|
||||
|
||||
Add platform to your service:
|
||||
|
||||
\`\`\`yaml
|
||||
services:
|
||||
app:
|
||||
platform: linux/amd64
|
||||
build: .
|
||||
\`\`\`
|
||||
|
||||
## Trade-offs
|
||||
|
||||
| Approach | Pros | Cons |
|
||||
|----------|------|------|
|
||||
| Build flag | No file changes | Must remember flag |
|
||||
| Dockerfile | Explicit, versioned | Affects all builds |
|
||||
| Compose | Convenient for dev | Requires compose |
|
||||
|
||||
## Performance Note
|
||||
|
||||
Running AMD64 images on ARM64 uses Rosetta 2 emulation. This works
|
||||
for development but may be slower. For production, find ARM-native
|
||||
alternatives when possible.
|
||||
|
||||
## Source
|
||||
|
||||
- Learning ID: LRN-20250118-001
|
||||
- Category: best_practice
|
||||
- Extraction Date: 2025-01-18
|
||||
```
|
||||
@@ -1,223 +0,0 @@
|
||||
# Hook Setup Guide
|
||||
|
||||
Configure automatic self-improvement triggers for AI coding agents.
|
||||
|
||||
## Overview
|
||||
|
||||
Hooks enable proactive learning capture by injecting reminders at key moments:
|
||||
- **UserPromptSubmit**: Reminder after each prompt to evaluate learnings
|
||||
- **PostToolUse (Bash)**: Error detection when commands fail
|
||||
|
||||
## Claude Code Setup
|
||||
|
||||
### Option 1: Project-Level Configuration
|
||||
|
||||
Create `.claude/settings.json` in your project root:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "./skills/self-improvement/scripts/activator.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "./skills/self-improvement/scripts/error-detector.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Option 2: User-Level Configuration
|
||||
|
||||
Add to `~/.claude/settings.json` for global activation:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "~/.claude/skills/self-improvement/scripts/activator.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Minimal Setup (Activator Only)
|
||||
|
||||
For lower overhead, use only the UserPromptSubmit hook:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "./skills/self-improvement/scripts/activator.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Codex CLI Setup
|
||||
|
||||
Codex uses the same hook system as Claude Code. Create `.codex/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "./skills/self-improvement/scripts/activator.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## GitHub Copilot Setup
|
||||
|
||||
Copilot doesn't support hooks directly. Instead, add guidance to `.github/copilot-instructions.md`:
|
||||
|
||||
```markdown
|
||||
## Self-Improvement
|
||||
|
||||
After completing tasks that involved:
|
||||
- Debugging non-obvious issues
|
||||
- Discovering workarounds
|
||||
- Learning project-specific patterns
|
||||
- Resolving unexpected errors
|
||||
|
||||
Consider logging the learning to `.learnings/` using the format from the self-improvement skill.
|
||||
|
||||
For high-value learnings that would benefit other sessions, consider skill extraction.
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
### Test Activator Hook
|
||||
|
||||
1. Enable the hook configuration
|
||||
2. Start a new Claude Code session
|
||||
3. Send any prompt
|
||||
4. Verify you see `<self-improvement-reminder>` in the context
|
||||
|
||||
### Test Error Detector Hook
|
||||
|
||||
1. Enable PostToolUse hook for Bash
|
||||
2. Run a command that fails: `ls /nonexistent/path`
|
||||
3. Verify you see `<error-detected>` reminder
|
||||
|
||||
### Dry Run Extract Script
|
||||
|
||||
```bash
|
||||
./skills/self-improvement/scripts/extract-skill.sh test-skill --dry-run
|
||||
```
|
||||
|
||||
Expected output shows the skill scaffold that would be created.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Hook Not Triggering
|
||||
|
||||
1. **Check script permissions**: `chmod +x scripts/*.sh`
|
||||
2. **Verify path**: Use absolute paths or paths relative to project root
|
||||
3. **Check settings location**: Project vs user-level settings
|
||||
4. **Restart session**: Hooks are loaded at session start
|
||||
|
||||
### Permission Denied
|
||||
|
||||
```bash
|
||||
chmod +x ./skills/self-improvement/scripts/activator.sh
|
||||
chmod +x ./skills/self-improvement/scripts/error-detector.sh
|
||||
chmod +x ./skills/self-improvement/scripts/extract-skill.sh
|
||||
```
|
||||
|
||||
### Script Not Found
|
||||
|
||||
If using relative paths, ensure you're in the correct directory or use absolute paths:
|
||||
|
||||
```json
|
||||
{
|
||||
"command": "/absolute/path/to/skills/self-improvement/scripts/activator.sh"
|
||||
}
|
||||
```
|
||||
|
||||
### Too Much Overhead
|
||||
|
||||
If the activator feels intrusive:
|
||||
|
||||
1. **Use minimal setup**: Only UserPromptSubmit, skip PostToolUse
|
||||
2. **Add matcher filter**: Only trigger for certain prompts:
|
||||
|
||||
```json
|
||||
{
|
||||
"matcher": "fix|debug|error|issue",
|
||||
"hooks": [...]
|
||||
}
|
||||
```
|
||||
|
||||
## Hook Output Budget
|
||||
|
||||
The activator is designed to be lightweight:
|
||||
- **Target**: ~50-100 tokens per activation
|
||||
- **Content**: Structured reminder, not verbose instructions
|
||||
- **Format**: XML tags for easy parsing
|
||||
|
||||
If you need to reduce overhead further, you can edit `activator.sh` to output less text.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Hook scripts run with the same permissions as Claude Code
|
||||
- Scripts only output text; they don't modify files or run commands
|
||||
- Error detector reads `CLAUDE_TOOL_OUTPUT` environment variable
|
||||
- All scripts are opt-in (you must configure them explicitly)
|
||||
|
||||
## Disabling Hooks
|
||||
|
||||
To temporarily disable without removing configuration:
|
||||
|
||||
1. **Comment out in settings**:
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
// "UserPromptSubmit": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Or delete the settings file**: Hooks won't run without configuration
|
||||
@@ -1,248 +0,0 @@
|
||||
# OpenClaw Integration
|
||||
|
||||
Complete setup and usage guide for integrating the self-improvement skill with OpenClaw.
|
||||
|
||||
## Overview
|
||||
|
||||
OpenClaw uses workspace-based prompt injection combined with event-driven hooks. Context is injected from workspace files at session start, and hooks can trigger on lifecycle events.
|
||||
|
||||
## Workspace Structure
|
||||
|
||||
```
|
||||
~/.openclaw/
|
||||
├── workspace/ # Working directory
|
||||
│ ├── AGENTS.md # Multi-agent coordination patterns
|
||||
│ ├── SOUL.md # Behavioral guidelines and personality
|
||||
│ ├── TOOLS.md # Tool capabilities and gotchas
|
||||
│ ├── MEMORY.md # Long-term memory (main session only)
|
||||
│ └── memory/ # Daily memory files
|
||||
│ └── YYYY-MM-DD.md
|
||||
├── skills/ # Installed skills
|
||||
│ └── <skill-name>/
|
||||
│ └── SKILL.md
|
||||
└── hooks/ # Custom hooks
|
||||
└── <hook-name>/
|
||||
├── HOOK.md
|
||||
└── handler.ts
|
||||
```
|
||||
|
||||
## Quick Setup
|
||||
|
||||
### 1. Install the Skill
|
||||
|
||||
```bash
|
||||
clawdhub install self-improving-agent
|
||||
```
|
||||
|
||||
Or copy manually:
|
||||
|
||||
```bash
|
||||
cp -r self-improving-agent ~/.openclaw/skills/
|
||||
```
|
||||
|
||||
### 2. Install the Hook (Optional)
|
||||
|
||||
Copy the hook to OpenClaw's hooks directory:
|
||||
|
||||
```bash
|
||||
cp -r hooks/openclaw ~/.openclaw/hooks/self-improvement
|
||||
```
|
||||
|
||||
Enable the hook:
|
||||
|
||||
```bash
|
||||
openclaw hooks enable self-improvement
|
||||
```
|
||||
|
||||
### 3. Create Learning Files
|
||||
|
||||
Create the `.learnings/` directory in your workspace:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.openclaw/workspace/.learnings
|
||||
```
|
||||
|
||||
Or in the skill directory:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.openclaw/skills/self-improving-agent/.learnings
|
||||
```
|
||||
|
||||
## Injected Prompt Files
|
||||
|
||||
### AGENTS.md
|
||||
|
||||
Purpose: Multi-agent workflows and delegation patterns.
|
||||
|
||||
```markdown
|
||||
# Agent Coordination
|
||||
|
||||
## Delegation Rules
|
||||
- Use explore agent for open-ended codebase questions
|
||||
- Spawn sub-agents for long-running tasks
|
||||
- Use sessions_send for cross-session communication
|
||||
|
||||
## Session Handoff
|
||||
When delegating to another session:
|
||||
1. Provide full context in the handoff message
|
||||
2. Include relevant file paths
|
||||
3. Specify expected output format
|
||||
```
|
||||
|
||||
### SOUL.md
|
||||
|
||||
Purpose: Behavioral guidelines and communication style.
|
||||
|
||||
```markdown
|
||||
# Behavioral Guidelines
|
||||
|
||||
## Communication Style
|
||||
- Be direct and concise
|
||||
- Avoid unnecessary caveats and disclaimers
|
||||
- Use technical language appropriate to context
|
||||
|
||||
## Error Handling
|
||||
- Admit mistakes promptly
|
||||
- Provide corrected information immediately
|
||||
- Log significant errors to learnings
|
||||
```
|
||||
|
||||
### TOOLS.md
|
||||
|
||||
Purpose: Tool capabilities, integration gotchas, local configuration.
|
||||
|
||||
```markdown
|
||||
# Tool Knowledge
|
||||
|
||||
## Self-Improvement Skill
|
||||
Log learnings to `.learnings/` for continuous improvement.
|
||||
|
||||
## Local Tools
|
||||
- Document tool-specific gotchas here
|
||||
- Note authentication requirements
|
||||
- Track integration quirks
|
||||
```
|
||||
|
||||
## Learning Workflow
|
||||
|
||||
### Capturing Learnings
|
||||
|
||||
1. **In-session**: Log to `.learnings/` as usual
|
||||
2. **Cross-session**: Promote to workspace files
|
||||
|
||||
### Promotion Decision Tree
|
||||
|
||||
```
|
||||
Is the learning project-specific?
|
||||
├── Yes → Keep in .learnings/
|
||||
└── No → Is it behavioral/style-related?
|
||||
├── Yes → Promote to SOUL.md
|
||||
└── No → Is it tool-related?
|
||||
├── Yes → Promote to TOOLS.md
|
||||
└── No → Promote to AGENTS.md (workflow)
|
||||
```
|
||||
|
||||
### Promotion Format Examples
|
||||
|
||||
**From learning:**
|
||||
> Git push to GitHub fails without auth configured - triggers desktop prompt
|
||||
|
||||
**To TOOLS.md:**
|
||||
```markdown
|
||||
## Git
|
||||
- Don't push without confirming auth is configured
|
||||
- Use `gh auth status` to check GitHub CLI auth
|
||||
```
|
||||
|
||||
## Inter-Agent Communication
|
||||
|
||||
OpenClaw provides tools for cross-session communication:
|
||||
|
||||
### sessions_list
|
||||
|
||||
View active and recent sessions:
|
||||
```
|
||||
sessions_list(activeMinutes=30, messageLimit=3)
|
||||
```
|
||||
|
||||
### sessions_history
|
||||
|
||||
Read transcript from another session:
|
||||
```
|
||||
sessions_history(sessionKey="session-id", limit=50)
|
||||
```
|
||||
|
||||
### sessions_send
|
||||
|
||||
Send message to another session:
|
||||
```
|
||||
sessions_send(sessionKey="session-id", message="Learning: API requires X-Custom-Header")
|
||||
```
|
||||
|
||||
### sessions_spawn
|
||||
|
||||
Spawn a background sub-agent:
|
||||
```
|
||||
sessions_spawn(task="Research X and report back", label="research")
|
||||
```
|
||||
|
||||
## Available Hook Events
|
||||
|
||||
| Event | When It Fires |
|
||||
|-------|---------------|
|
||||
| `agent:bootstrap` | Before workspace files inject |
|
||||
| `command:new` | When `/new` command issued |
|
||||
| `command:reset` | When `/reset` command issued |
|
||||
| `command:stop` | When `/stop` command issued |
|
||||
| `gateway:startup` | When gateway starts |
|
||||
|
||||
## Detection Triggers
|
||||
|
||||
### Standard Triggers
|
||||
- User corrections ("No, that's wrong...")
|
||||
- Command failures (non-zero exit codes)
|
||||
- API errors
|
||||
- Knowledge gaps
|
||||
|
||||
### OpenClaw-Specific Triggers
|
||||
|
||||
| Trigger | Action |
|
||||
|---------|--------|
|
||||
| Tool call error | Log to TOOLS.md with tool name |
|
||||
| Session handoff confusion | Log to AGENTS.md with delegation pattern |
|
||||
| Model behavior surprise | Log to SOUL.md with expected vs actual |
|
||||
| Skill issue | Log to .learnings/ or report upstream |
|
||||
|
||||
## Verification
|
||||
|
||||
Check hook is registered:
|
||||
|
||||
```bash
|
||||
openclaw hooks list
|
||||
```
|
||||
|
||||
Check skill is loaded:
|
||||
|
||||
```bash
|
||||
openclaw status
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Hook not firing
|
||||
|
||||
1. Ensure hooks enabled in config
|
||||
2. Restart gateway after config changes
|
||||
3. Check gateway logs for errors
|
||||
|
||||
### Learnings not persisting
|
||||
|
||||
1. Verify `.learnings/` directory exists
|
||||
2. Check file permissions
|
||||
3. Ensure workspace path is configured correctly
|
||||
|
||||
### Skill not loading
|
||||
|
||||
1. Check skill is in skills directory
|
||||
2. Verify SKILL.md has correct frontmatter
|
||||
3. Run `openclaw status` to see loaded skills
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Self-Improvement Activator Hook
|
||||
# Triggers on UserPromptSubmit to remind Claude about learning capture
|
||||
# Keep output minimal (~50-100 tokens) to minimize overhead
|
||||
|
||||
set -e
|
||||
|
||||
# Output reminder as system context
|
||||
cat << 'EOF'
|
||||
<self-improvement-reminder>
|
||||
After completing this task, evaluate if extractable knowledge emerged:
|
||||
- Non-obvious solution discovered through investigation?
|
||||
- Workaround for unexpected behavior?
|
||||
- Project-specific pattern learned?
|
||||
- Error required debugging to resolve?
|
||||
|
||||
If yes: Log to .learnings/ using the self-improvement skill format.
|
||||
If high-value (recurring, broadly applicable): Consider skill extraction.
|
||||
</self-improvement-reminder>
|
||||
EOF
|
||||
@@ -1,55 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Self-Improvement Error Detector Hook
|
||||
# Triggers on PostToolUse for Bash to detect command failures
|
||||
# Reads CLAUDE_TOOL_OUTPUT environment variable
|
||||
|
||||
set -e
|
||||
|
||||
# Check if tool output indicates an error
|
||||
# CLAUDE_TOOL_OUTPUT contains the result of the tool execution
|
||||
OUTPUT="${CLAUDE_TOOL_OUTPUT:-}"
|
||||
|
||||
# Patterns indicating errors (case-insensitive matching)
|
||||
ERROR_PATTERNS=(
|
||||
"error:"
|
||||
"Error:"
|
||||
"ERROR:"
|
||||
"failed"
|
||||
"FAILED"
|
||||
"command not found"
|
||||
"No such file"
|
||||
"Permission denied"
|
||||
"fatal:"
|
||||
"Exception"
|
||||
"Traceback"
|
||||
"npm ERR!"
|
||||
"ModuleNotFoundError"
|
||||
"SyntaxError"
|
||||
"TypeError"
|
||||
"exit code"
|
||||
"non-zero"
|
||||
)
|
||||
|
||||
# Check if output contains any error pattern
|
||||
contains_error=false
|
||||
for pattern in "${ERROR_PATTERNS[@]}"; do
|
||||
if [[ "$OUTPUT" == *"$pattern"* ]]; then
|
||||
contains_error=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Only output reminder if error detected
|
||||
if [ "$contains_error" = true ]; then
|
||||
cat << 'EOF'
|
||||
<error-detected>
|
||||
A command error was detected. Consider logging this to .learnings/ERRORS.md if:
|
||||
- The error was unexpected or non-obvious
|
||||
- It required investigation to resolve
|
||||
- It might recur in similar contexts
|
||||
- The solution could benefit future sessions
|
||||
|
||||
Use the self-improvement skill format: [ERR-YYYYMMDD-XXX]
|
||||
</error-detected>
|
||||
EOF
|
||||
fi
|
||||
@@ -1,221 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Skill Extraction Helper
|
||||
# Creates a new skill from a learning entry
|
||||
# Usage: ./extract-skill.sh <skill-name> [--dry-run]
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
SKILLS_DIR="./skills"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
usage() {
|
||||
cat << EOF
|
||||
Usage: $(basename "$0") <skill-name> [options]
|
||||
|
||||
Create a new skill from a learning entry.
|
||||
|
||||
Arguments:
|
||||
skill-name Name of the skill (lowercase, hyphens for spaces)
|
||||
|
||||
Options:
|
||||
--dry-run Show what would be created without creating files
|
||||
--output-dir Relative output directory under current path (default: ./skills)
|
||||
-h, --help Show this help message
|
||||
|
||||
Examples:
|
||||
$(basename "$0") docker-m1-fixes
|
||||
$(basename "$0") api-timeout-patterns --dry-run
|
||||
$(basename "$0") pnpm-setup --output-dir ./skills/custom
|
||||
|
||||
The skill will be created in: \$SKILLS_DIR/<skill-name>/
|
||||
EOF
|
||||
}
|
||||
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1" >&2
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
SKILL_NAME=""
|
||||
DRY_RUN=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
--output-dir)
|
||||
if [ -z "${2:-}" ] || [[ "${2:-}" == -* ]]; then
|
||||
log_error "--output-dir requires a relative path argument"
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
SKILLS_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
-*)
|
||||
log_error "Unknown option: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [ -z "$SKILL_NAME" ]; then
|
||||
SKILL_NAME="$1"
|
||||
else
|
||||
log_error "Unexpected argument: $1"
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate skill name
|
||||
if [ -z "$SKILL_NAME" ]; then
|
||||
log_error "Skill name is required"
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate skill name format (lowercase, hyphens, no spaces)
|
||||
if ! [[ "$SKILL_NAME" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then
|
||||
log_error "Invalid skill name format. Use lowercase letters, numbers, and hyphens only."
|
||||
log_error "Examples: 'docker-fixes', 'api-patterns', 'pnpm-setup'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate output path to avoid writes outside current workspace.
|
||||
if [[ "$SKILLS_DIR" = /* ]]; then
|
||||
log_error "Output directory must be a relative path under the current directory."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$SKILLS_DIR" =~ (^|/)\.\.(/|$) ]]; then
|
||||
log_error "Output directory cannot include '..' path segments."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SKILLS_DIR="${SKILLS_DIR#./}"
|
||||
SKILLS_DIR="./$SKILLS_DIR"
|
||||
|
||||
SKILL_PATH="$SKILLS_DIR/$SKILL_NAME"
|
||||
|
||||
# Check if skill already exists
|
||||
if [ -d "$SKILL_PATH" ] && [ "$DRY_RUN" = false ]; then
|
||||
log_error "Skill already exists: $SKILL_PATH"
|
||||
log_error "Use a different name or remove the existing skill first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Dry run output
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log_info "Dry run - would create:"
|
||||
echo " $SKILL_PATH/"
|
||||
echo " $SKILL_PATH/SKILL.md"
|
||||
echo ""
|
||||
echo "Template content would be:"
|
||||
echo "---"
|
||||
cat << TEMPLATE
|
||||
name: $SKILL_NAME
|
||||
description: "[TODO: Add a concise description of what this skill does and when to use it]"
|
||||
---
|
||||
|
||||
# $(echo "$SKILL_NAME" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1')
|
||||
|
||||
[TODO: Brief introduction explaining the skill's purpose]
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Situation | Action |
|
||||
|-----------|--------|
|
||||
| [Trigger condition] | [What to do] |
|
||||
|
||||
## Usage
|
||||
|
||||
[TODO: Detailed usage instructions]
|
||||
|
||||
## Examples
|
||||
|
||||
[TODO: Add concrete examples]
|
||||
|
||||
## Source Learning
|
||||
|
||||
This skill was extracted from a learning entry.
|
||||
- Learning ID: [TODO: Add original learning ID]
|
||||
- Original File: .learnings/LEARNINGS.md
|
||||
TEMPLATE
|
||||
echo "---"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create skill directory structure
|
||||
log_info "Creating skill: $SKILL_NAME"
|
||||
|
||||
mkdir -p "$SKILL_PATH"
|
||||
|
||||
# Create SKILL.md from template
|
||||
cat > "$SKILL_PATH/SKILL.md" << TEMPLATE
|
||||
---
|
||||
name: $SKILL_NAME
|
||||
description: "[TODO: Add a concise description of what this skill does and when to use it]"
|
||||
---
|
||||
|
||||
# $(echo "$SKILL_NAME" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1')
|
||||
|
||||
[TODO: Brief introduction explaining the skill's purpose]
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Situation | Action |
|
||||
|-----------|--------|
|
||||
| [Trigger condition] | [What to do] |
|
||||
|
||||
## Usage
|
||||
|
||||
[TODO: Detailed usage instructions]
|
||||
|
||||
## Examples
|
||||
|
||||
[TODO: Add concrete examples]
|
||||
|
||||
## Source Learning
|
||||
|
||||
This skill was extracted from a learning entry.
|
||||
- Learning ID: [TODO: Add original learning ID]
|
||||
- Original File: .learnings/LEARNINGS.md
|
||||
TEMPLATE
|
||||
|
||||
log_info "Created: $SKILL_PATH/SKILL.md"
|
||||
|
||||
# Suggest next steps
|
||||
echo ""
|
||||
log_info "Skill scaffold created successfully!"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Edit $SKILL_PATH/SKILL.md"
|
||||
echo " 2. Fill in the TODO sections with content from your learning"
|
||||
echo " 3. Add references/ folder if you have detailed documentation"
|
||||
echo " 4. Add scripts/ folder if you have executable code"
|
||||
echo " 5. Update the original learning entry with:"
|
||||
echo " **Status**: promoted_to_skill"
|
||||
echo " **Skill-Path**: skills/$SKILL_NAME"
|
||||
Reference in New Issue
Block a user