feat: 更新 knowledge-base-manager skill,添加完整 ID 映射和严格目录限制

This commit is contained in:
oliver 2026-05-16 22:27:57 +08:00
parent acfb2cf73b
commit 807679aaaa
21 changed files with 753 additions and 1580 deletions

View file

@ -574,7 +574,7 @@
<script defer src="/admin/assets/admin-theme.js"></script>
<script
defer
src="/admin/assets/chat.js?v=20260511-2"
src="/admin/assets/chat.js?v=20260515-2"
onerror="(function(){var s=document.getElementById('chat-boot-splash');if(s){s.querySelector('.chat-boot-splash__title span:last-child').textContent='无法加载 chat.js';s.querySelector('.chat-boot-splash__muted').textContent='请确认网关已启动且 /admin/assets/chat.js 可访问。';}})()"
></script>
</head>

View file

@ -77,6 +77,7 @@ const I18N = {
"theme.catppuccin": "Catppuccin Mocha",
"theme.light": "浅色",
"lang.switch": "English",
"chat.langSwitchedWhileStreaming": "语言已切换。当前会话仍在输出,刷新页面可更新全部界面文案。",
"chat.imageViewerClose": "关闭",
"chat.imageViewerHint": "点击查看大图,空白处或 Esc 关闭",
"chat.imageViewerDownload": "下载图片",
@ -256,6 +257,7 @@ const I18N = {
"theme.catppuccin": "Catppuccin Mocha",
"theme.light": "Light",
"lang.switch": "中文",
"chat.langSwitchedWhileStreaming": "Language updated. Session is still streaming; refresh the page to reload all labels.",
"chat.imageViewerClose": "Close",
"chat.imageViewerHint": "Click image to enlarge; click outside or Esc to close",
"chat.imageViewerDownload": "Download image",
@ -873,8 +875,8 @@ async function openDispatchLabelsEditor(statusEl) {
? JSON.stringify(cfg.overrides, null, 2)
: "";
const backdrop = el("div", {
style:
"position:fixed;inset:0;background:rgba(0,0,0,0.35);z-index:9999;display:flex;align-items:center;justify-content:center;padding:16px;",
class: "chat-confirm-backdrop",
style: "z-index:9999;",
});
const card = el("div", {
class: "card",
@ -1180,6 +1182,57 @@ function closeChatImageLightbox() {
document.body.style.overflow = _chatLightboxPrevOverflow || "";
}
function dismissChatMenus() {
document.querySelectorAll(".chat-sess-menu-pop, .chat-menu-scrim").forEach((n) => {
try {
n.remove();
} catch (_) {}
});
}
function isChatStreaming() {
return !!document.querySelector(".chat-composer-shell--busy");
}
/** Remove full-screen layers on ``document.body`` that survive ``#app`` remounts (boot/lang/popstate). */
function clearChatPageBlockers() {
closeChatImageLightbox();
dismissChatMenus();
document.body.style.overflow = "";
document.querySelectorAll(".chat-confirm-backdrop").forEach((n) => {
try {
n.remove();
} catch (_) {}
});
}
function attachChatMenuDismiss(menu) {
const scrim = el("div", {
class: "chat-menu-scrim",
style: "position:fixed;inset:0;z-index:250;background:transparent;",
});
const closeAll = () => {
dismissChatMenus();
document.removeEventListener("click", onDocClick, true);
document.removeEventListener("keydown", onKey, true);
};
const onDocClick = (e) => {
if (menu.contains(e.target)) return;
closeAll();
};
const onKey = (e) => {
if (e.key === "Escape") closeAll();
};
scrim.addEventListener("click", closeAll);
document.body.appendChild(scrim);
document.body.appendChild(menu);
setTimeout(() => {
document.addEventListener("click", onDocClick, true);
document.addEventListener("keydown", onKey, true);
}, 0);
return closeAll;
}
function openChatImageLightbox(src, alt) {
if (!src || !String(src).trim()) return;
closeChatImageLightbox();
@ -2504,6 +2557,7 @@ async function appendMessageRow(messagesEl, m, options = {}) {
}
function mount(node) {
clearChatPageBlockers();
const c = document.getElementById("app");
c.innerHTML = "";
c.appendChild(node);
@ -2566,7 +2620,7 @@ function syncAuthUserLabel() {
title: t("chat.sessionMenu"),
onclick: (ev) => {
ev.stopPropagation();
document.querySelectorAll(".chat-sess-menu-pop").forEach((n) => n.remove());
clearChatPageBlockers();
const items = [
el("button", {
type: "button",
@ -2695,10 +2749,12 @@ function syncAuthUserLabel() {
text: t("auth.logout"),
}),
);
const menu = el("div", { class: "chat-sess-menu-pop", style: "position:fixed;min-width:220px;" }, items);
const menu = el("div", {
class: "chat-sess-menu-pop",
style: "position:fixed;min-width:220px;z-index:300;",
}, items);
const rect = moreBtn.getBoundingClientRect();
menu.style.position = "fixed";
document.body.appendChild(menu);
attachChatMenuDismiss(menu);
const mrect = menu.getBoundingClientRect();
const pad = 8;
let left = rect.left;
@ -2710,13 +2766,6 @@ function syncAuthUserLabel() {
top = Math.max(pad, Math.min(top, window.innerHeight - pad - mrect.height));
menu.style.left = `${left}px`;
menu.style.top = `${top}px`;
const close = (e) => {
if (!menu.contains(e.target)) {
menu.remove();
document.removeEventListener("click", close);
}
};
setTimeout(() => document.addEventListener("click", close), 0);
},
});
const row = el("div", { class: "chat-user-row" }, [nameBtn, moreBtn]);
@ -3684,12 +3733,12 @@ async function renderChatUi() {
title: t("chat.sessionMenu"),
onclick: (ev) => {
ev.stopPropagation();
document.querySelectorAll(".chat-sess-menu-pop").forEach((n) => n.remove());
clearChatPageBlockers();
const menu = openSessionMenu(sid, title);
const rect = more.getBoundingClientRect();
menu.style.position = "fixed";
document.body.appendChild(menu);
// Clamp into viewport; flip above if near bottom.
menu.style.zIndex = "300";
const rect = more.getBoundingClientRect();
attachChatMenuDismiss(menu);
const mrect = menu.getBoundingClientRect();
const pad = 8;
let left = rect.left;
@ -3701,13 +3750,6 @@ async function renderChatUi() {
top = Math.max(pad, Math.min(top, window.innerHeight - pad - mrect.height));
menu.style.left = `${left}px`;
menu.style.top = `${top}px`;
const close = (e) => {
if (!menu.contains(e.target)) {
menu.remove();
document.removeEventListener("click", close);
}
};
setTimeout(() => document.addEventListener("click", close), 0);
},
});
row.appendChild(btn);
@ -5374,6 +5416,7 @@ async function syncLangFromServer() {
}
async function boot() {
clearChatPageBlockers();
applyI18nStatic();
if (forceReloginRequested()) {
clearAuthAndReloginFlagFromUrl();
@ -5385,10 +5428,6 @@ async function boot() {
}
const tok = String(localStorage.getItem(AUTH_TOKEN_KEY) || "").trim();
if (!authSession || !tok) {
// Ensure stale overlays from previous chat UI never block login inputs.
closeChatImageLightbox();
document.querySelectorAll(".chat-sess-menu-pop").forEach((n) => n.remove());
document.body.style.overflow = "";
localStorage.removeItem(AUTH_TOKEN_KEY);
localStorage.removeItem(AUTH_SESSION_KEY);
authSession = null;
@ -5446,7 +5485,14 @@ document.body.addEventListener("click", async (e) => {
try {
await apiPost("/admin/api/chat/settings/ui-lang", { lang: currentLang });
} catch (_) {}
await boot();
clearChatPageBlockers();
if (isChatStreaming()) {
applyI18nStatic();
syncAuthUserLabel();
showToast(t("chat.langSwitchedWhileStreaming"), { kind: "info", ttlMs: 6500 });
} else {
await boot();
}
return;
}
}
@ -5457,6 +5503,14 @@ window.addEventListener("popstate", () => {
if (authSession) boot().catch(() => {});
});
document.addEventListener("keydown", (ev) => {
if (ev.key !== "Escape" || ev.defaultPrevented) return;
if (!document.querySelector(".chat-confirm-backdrop, .chat-sess-menu-pop, .chat-img-lightbox, .chat-menu-scrim")) {
return;
}
clearChatPageBlockers();
});
boot().catch((err) => {
mount(
el("div", { class: "chat-app--login" }, [

View file

@ -85,11 +85,24 @@ def _mcp_local_env_paths_in_load_order() -> list[Path]:
]
def _apply_trilium_env_aliases(vals: dict[str, str]) -> None:
"""Map legacy TRILIUM_URL / TRILIUM_TOKEN to names expected by triliumnext-mcp."""
if not str(vals.get("TRILIUM_API_URL") or "").strip():
legacy = str(vals.get("TRILIUM_URL") or "").strip()
if legacy:
vals["TRILIUM_API_URL"] = legacy
if not str(vals.get("TRILIUM_API_TOKEN") or "").strip():
legacy = str(vals.get("TRILIUM_TOKEN") or "").strip()
if legacy:
vals["TRILIUM_API_TOKEN"] = legacy
def mcp_local_env_merged() -> dict[str, str]:
out: dict[str, str] = {}
for p in _mcp_local_env_paths_in_load_order():
if p.is_file():
out.update(_parse_env_file(p))
_apply_trilium_env_aliases(out)
return out
@ -102,7 +115,8 @@ def gateway_mcp_env_extras() -> dict[str, str]:
has_aia = str(os.getenv("AIA_MCP_ENV_ALLOWLIST") or "").strip()
if not has_aia:
extra["AIA_MCP_ENV_ALLOWLIST"] = _DEFAULT_ALLOWLIST
file_vals = mcp_local_env_merged()
file_vals = dict(mcp_local_env_merged())
_apply_trilium_env_aliases(file_vals)
for k, v in file_vals.items():
if not v.strip():
continue

View file

@ -0,0 +1,450 @@
---
name: knowledge-base-manager
description: 智能知识库管理助手,自动将新信息归类到正确的 PARA 分类(Projects/Areas/Resources/Archives),确保笔记结构规范化。带严格目录限制和完整 ID 映射。
user-invocable: true
disable-model-invocation: false
metadata: {"oclaw": {}}
---
# knowledge-base-manager
**Description**: 智能知识库管理助手,自动将新信息归类到正确的 PARA 分类(Projects/Areas/Resources/Archives),确保笔记结构规范化。
**Version**: 1.1 (带目录限制)
**Author**: Oliver
**Created**: 2026-04-11
**Last Updated**: 2026-04-11
---
## ⚠️ 核心约束(必须遵守)
### 🚫 禁止操作
1. **严禁移动现有笔记** - 除非用户明确要求"移动 XXX 到 YYY"
2. **严禁删除任何笔记** - 除非用户明确要求"删除 XXX"
3. **严禁修改非授权目录** - 只能在以下允许的子目录下创建笔记
4. **严禁在根目录或其他区域创建笔记** - 必须遵循目录结构
### ✅ 允许操作的目录
**只能**在以下父节点的**子目录**下创建新笔记:
| 允许创建的父节点 | Note ID | 用途 |
|-----------------|---------|------|
| `00-Inbox` | `h9skuCU9czep` | 临时收集箱(默认) |
| `01-Projects` | `8C1UOi0hfib5` | 项目相关笔记 |
| `02-Areas/基础设施` | `pS6clLY4g5sd` | 基础设施领域 |
| `02-Areas/开发环境` | `b7lkhRneQk2a` | 开发环境领域 |
| `03-Resources` | `1AbShK4WDw38` | 参考资源 |
| `04-Archives` | `6rQ5acaYa7fZ` | 归档内容 |
| `99-Templates` | `RVjlkQmiHxaw` | 模板笔记 |
**重要**:
- 创建新笔记时,`parentNoteId` **必须**是上述 ID 之一或其子节点
- 如果用户要求的位置不在上述列表中,**必须拒绝**并提示用户
- 不确定时,默认使用 `h9skuCU9czep` (Inbox)
---
## 📋 完整笔记 ID 映射表
### 🌟 知识库总览
```
🌟 知识库总览 → XTNCDr5hq9bG
📖 知识库使用指南 → hgILpr4yRkcS
📝 笔记记录规范 → [见使用指南子节点]
✅ 迁移完成报告 → [见使用指南子节点]
🚀 快速记录指南 → U4FnimoXwG1j
📋 ID 速查表 → Aa2aRAGLWuu4
```
### 📥 Inbox (临时收集箱)
```
00-Inbox → h9skuCU9czep ← 默认存放位置
```
### 🚀 Projects (进行中项目)
```
01-Projects → 8C1UOi0hfib5
├── Oclaw 项目 → [待确认]
├── NETX 项目 → [待确认]
└── 计划 → THJbELUQ2Ecm
```
### 🏗️ Areas (持续责任)
```
02-Areas → HFsgsHfH4nKN
├── 基础设施 → pS6clLY4g5sd
│ ├── QNAP-NAS → 6CC2lab7vPDA
│ └── QNAP → 9TRXB88ODggT
└── 开发环境 → b7lkhRneQk2a
├── API 密钥管理 → [待确认]
├── PG 数据库 → zdscNfnBnoqq
├── 启动命令 → 1tCnm2jbiJPW
└── skill → JRYSKzNWQaCp
```
### 📚 Resources (参考资源)
```
03-Resources → 1AbShK4WDw38
├── Python → M5wP7D0FZ819
├── PostgreSQL → NZS3aOMlNJcp
├── Skills 开发指南 → yPold3qhllN1
│ └── Note-Taking Skill → TNyFX4lADEk5
├── Trilium Next 使用指南 → 2s26WHNqv0Q8
└── 学习 → l6OfBhJ804qL
```
### 🗄️ Archives (归档)
```
04-Archives → 6rQ5acaYa7fZ
├── API (空) → MLXD4mGL5mzh
└── 生活 → Gvn9zydADEYl
```
### 📝 Templates (模板)
```
99-Templates → RVjlkQmiHxaw
```
---
## 🎯 功能说明
本 Skill 帮助用户在记录信息时自动判断最佳存放位置,遵循 PARA 知识管理方法,并**严格遵守目录限制**。
### 核心能力
1. **智能分类** - 根据内容自动推荐 Projects/Areas/Resources/Archives
2. **快速创建** - 一键在正确位置创建结构化笔记
3. **目录验证** - 强制验证父节点是否在允许列表中
4. **Inbox 管理** - 临时存储 + 定期整理提醒
5. **标签建议** - 自动添加合适的状态/优先级/类型标签
6. **关联推荐** - 推荐相关笔记建立链接
---
## 📋 触发条件
当用户提到以下关键词时触发:
- "记一下"、"记录"、"保存"、"添加笔记"
- "新建笔记"、"创建文档"、"写个笔记"
- "放在哪里"、"归到哪类"、"怎么分类"
- "inbox"、"整理笔记"、"归档"
- "项目笔记"、"技术文档"、"参考资料"
---
## 🔄 工作流程
### 1. 接收信息
用户提供要记录的内容或想法
### 2. 分析分类
根据内容特征判断所属类别:
| 特征 | 分类 | 示例 |
|------|------|------|
| 有明确目标/截止日期 | **Projects** | "Oclaw 新功能开发计划" |
| 持续责任/维护领域 | **Areas** | "NAS 备份策略"、"API 密钥更新" |
| 知识点/参考资料 | **Resources** | "Python 异步编程笔记"、"PostgreSQL 优化技巧" |
| 已完成/过时内容 | **Archives** | "2023 年项目总结" |
| 不确定/临时信息 | **Inbox** | "稍后整理的会议记录" |
### 3. 目录验证(关键步骤)
```javascript
// 验证父节点是否在允许列表中
const allowedParents = [
'h9skuCU9czep', // Inbox
'8C1UOi0hfib5', // Projects
'pS6clLY4g5sd', // Areas/基础设施
'b7lkhRneQk2a', // Areas/开发环境
'1AbShK4WDw38', // Resources
'6rQ5acaYa7fZ', // Archives
'RVjlkQmiHxaw' // Templates
];
if (!allowedParents.includes(parentNoteId)) {
throw new Error("❌ 无法在指定位置创建笔记");
}
```
### 4. 创建笔记
- 在确定的父节点下创建新笔记
- 使用描述性标题
- 应用标准模板(如适用)
- 添加基础标签
### 5. 确认反馈
向用户展示:
- ✅ 已创建笔记的位置
- 📝 笔记标题和预览
- 🏷️ 应用的标签
- 🔗 推荐的相关笔记
- ⚠️ 如有目录限制问题,说明原因
---
## 📂 分类决策树
```
用户提供信息
↓
是否有明确的项目目标?
├─ 是 → Projects/[项目名称] (8C1UOi0hfib5)
│ └─ 如果项目不存在 → 先询问是否创建新项目
└─ 否 → 是否是持续责任领域?
├─ 是 → Areas/[领域名称]
│ ├─ 基础设施 → pS6clLY4g5sd
│ └─ 开发环境 → b7lkhRneQk2a
└─ 否 → 是否是参考知识?
├─ 是 → Resources/[主题] (1AbShK4WDw38)
│ └─ 技术栈、教程、文档、最佳实践
└─ 否 → 是否已过时?
├─ 是 → Archives/ (6rQ5acaYa7fZ)
└─ 否 → Inbox/ (h9skuCU9czep) ← 默认
```
---
## 🏷️ 标签系统
### 自动应用的标签
#### 状态标签
- `#status/active` - 进行中项目/任务
- `#status/pending` - 待处理事项
- `#status/completed` - 已完成
- `#status/archived` - 已归档
#### 优先级标签
- `#priority/high` - 高优先级(包含"紧急"、"重要"、"尽快"等词)
- `#priority/medium` - 中优先级(默认)
- `#priority/low` - 低优先级(包含"有空时"、"参考"等词)
#### 类型标签
- `#type/note` - 普通笔记
- `#type/task` - 任务清单(包含"待办"、"要做"等词)
- `#type/reference` - 参考资料
- `#type/meeting` - 会议记录
- `#type/idea` - 想法/灵感
#### 领域标签
- `#domain/tech` - 技术相关(代码、工具、框架等)
- `#domain/business` - 业务相关
- `#domain/personal` - 个人生活
- `#domain/learning` - 学习内容
---
## 💬 使用示例
### 示例 1: 记录项目任务
**用户**: "记一下,明天要完成 Oclaw 的搜索功能优化"
**助手**:
```
✅ 已创建笔记
📍 位置:Projects/Oclaw 项目/搜索功能优化
🏷️ 标签:#status/active #priority/high #type/task #domain/tech
📅 截止:明天
是否需要设置提醒或添加到待办清单?
```
### 示例 2: 保存技术知识
**用户**: "记录一个 PostgreSQL 的性能优化技巧:使用 EXPLAIN ANALYZE 分析慢查询"
**助手**:
```
✅ 已创建笔记
📍 位置:Resources/PostgreSQL/性能优化技巧
🏷️ 标签:#type/reference #domain/tech #database
🔗 相关:PostgreSQL 使用指南
需要补充更多细节或示例吗?
```
### 示例 3: 临时想法
**用户**: "先记下来,周末再整理:关于技能市场化的想法"
**助手**:
```
✅ 已放入 Inbox
📍 位置:00-Inbox/技能市场化想法
🏷️ 标签:#status/pending #type/idea #domain/business
⏰ 提醒:周末整理
需要在周末提醒你整理这条笔记吗?
```
### 示例 4: 尝试在非授权目录创建(应拒绝)
**用户**: "把这个笔记放到根目录下"
**助手**:
```
❌ 抱歉,我无法在指定位置创建笔记。
⚠️ 目录限制规则:
为确保知识库结构稳定,我只能在规定目录下创建新笔记。
✅ 允许的目录:
- Inbox (h9skuCU9czep) - 临时收集
- Projects (8C1UOi0hfib5) - 项目相关
- Areas/基础设施 (pS6clLY4g5sd)
- Areas/开发环境 (b7lkhRneQk2a)
- Resources (1AbShK4WDw38) - 参考资料
- Archives (6rQ5acaYa7fZ) - 归档内容
- Templates (RVjlkQmiHxaw) - 模板
💡 建议:先放入 Inbox,之后可以手动移动。
是否要放入 Inbox?[是] [取消]
```
### 示例 5: 询问分类
**用户**: "这个 NAS 维护文档应该放在哪里?"
**助手**:
```
📂 建议分类:Areas/基础设施/QNAP-NAS
理由:
- NAS 维护是持续责任领域(Area)
- 属于基础设施范畴
- 已有 QNAP-NAS 子分类
- 父节点 ID: pS6clLY4g5sd
是否立即在此位置创建笔记?
```
---
## 🛠️ 工具调用
### Trilium Next API
- `create_note` - 创建新笔记(必须先验证父节点)
- `move_note` - 移动笔记到正确位置(仅当用户明确要求)
- `search_notes` - 查找相关笔记建立关联
- `read_attributes` - 读取现有标签
- `manage_attributes` - 添加/更新标签
### 可选集成
- Todo 系统 - 同步任务类笔记
- 日历系统 - 设置时间相关提醒
- Git - 备份重要笔记
---
## 📊 统计与报告
### 每周自动生成
- 新增笔记数量按分类统计
- Inbox 整理情况
- 最活跃的项目/领域
- 标签使用分布
### 每月回顾
- 完成项目归档
- 过时资源清理
- 分类体系优化建议
---
## ⚙️ 配置选项
### 用户偏好
```yaml
default_inbox_review: weekly # Inbox 整理频率
auto_tag_priority: true # 自动识别优先级
suggest_related: true # 推荐相关笔记
backup_enabled: true # 启用 Git 备份
```
### 目录限制(强制)
```yaml
directory_restrictions:
enabled: true # ⚠️ 必须为 true
allowed_parents:
- h9skuCU9czep # Inbox
- 8C1UOi0hfib5 # Projects
- pS6clLY4g5sd # Areas/基础设施
- b7lkhRneQk2a # Areas/开发环境
- 1AbShK4WDw38 # Resources
- 6rQ5acaYa7fZ # Archives
- RVjlkQmiHxaw # Templates
default_fallback: h9skuCU9czep # 不确定时默认放入 Inbox
strict_mode: true # 严格模式,禁止任何越界操作
```
### 自定义分类
用户可以扩展默认分类:
- 新增 Projects
- 自定义 Areas
- 扩展 Resources 子分类
---
## 🚨 错误处理
### 常见问题
**Q: 找不到合适的分类?**
A: 默认放入 Inbox (`h9skuCU9czep`),并提示用户手动选择
**Q: 笔记标题重复?**
A: 自动添加时间戳或序号区分
**Q: 父节点不存在?**
A: 询问用户是否创建新分类或直接放入 Inbox
**Q: 尝试在非授权目录创建?**
A: **拒绝**并显示允许目录列表,建议使用 Inbox
---
## 📈 最佳实践
### 给用户的建议
1. **及时记录** - 想法出现时立即记下
2. **信任系统** - 让 Skill 自动分类,事后可以调整
3. **定期整理** - 每周花 10 分钟整理 Inbox
4. **善用标签** - 不要过度标签化(3-5 个为宜)
5. **建立关联** - 主动链接相关笔记
### Skill 行为准则
1. **优先确认** - 不确定时询问用户而非猜测
2. **保持一致** - 同类内容使用相同分类逻辑
3. **最小干扰** - 快速创建,减少打断
4. **主动提醒** - 定期提示整理 Inbox 和归档
5. **严格遵守** - 绝不违反目录限制规则
---
## 🔮 未来增强
- [ ] AI 辅助摘要生成
- [ ] 自动提取关键词作为标签
- [ ] 智能关联推荐(基于内容相似度)
- [ ] 语音输入支持
- [ ] 多语言笔记支持
- [ ] 团队协作功能
- [ ] 自动审计目录合规性
---
## 📚 参考资源
- [PARA 方法](https://fortelabs.com/blog/para/)
- [Trilium Next 文档](https://triliumnext.org/)
- [知识管理最佳实践](Resources/Knowledge Management)
- [笔记记录规范](hgILpr4yRkcS)
- [知识库 ID 速查表](Aa2aRAGLWuu4)
---
**最后更新**: 2026-04-11
**版本**: 1.1 (带目录限制)
**⚠️ 重要**: 严格遵守目录限制,禁止在非授权位置创建笔记!

View file

@ -0,0 +1,128 @@
---
name: playwright-cli
description: 官方Microsoft Playwright CLI网页自动化工具,支持所有主流浏览器的无头/有头自动化操作,包括页面导航、元素交互、截图、录制、测试等功能。当用户提到网页自动化、浏览器操作、爬虫、截图、录制用户操作、E2E测试时触发。
---
# Playwright CLI 技能
Playwright CLI是微软官方的浏览器自动化工具,支持Chromium、Firefox、WebKit三大主流浏览器,提供强大的网页自动化能力。
## 安装步骤
### 1. 安装Playwright CLI
```bash
npm install -g @playwright/test
# 或者
pip install playwright
```
### 2. 安装浏览器
```bash
playwright install
# 仅安装特定浏览器
playwright install chromium firefox
# 安装系统依赖(Linux环境)
playwright install-deps
```
## 核心常用命令
### 页面操作
```bash
# 打开指定网页
playwright open https://example.com
# 无头模式打开网页并截图
playwright screenshot https://example.com example.png
# 全屏截图
playwright screenshot https://example.com --full-page full.png
# 指定视口大小
playwright screenshot https://example.com --viewport-size=1920,1080 desktop.png
# 生成PDF
playwright pdf https://example.com example.pdf
# 自定义PDF格式
playwright pdf https://example.com --format=A4 --landscape report.pdf
```
### 录制用户操作
```bash
# 录制用户操作并生成代码
playwright codegen https://example.com
# 保存录制结果到文件
playwright codegen https://example.com --output script.py
# 生成指定语言的代码(python, javascript, java, csharp)
playwright codegen https://example.com --target python
```
### 测试相关
```bash
# 运行测试
playwright test
# 运行特定测试文件
playwright test tests/example.spec.js
# 有头模式运行测试(显示浏览器界面)
playwright test --headed
# 调试模式运行
playwright test --debug
# 生成测试报告
playwright show-report
```
### 浏览器管理
```bash
# 列出已安装的浏览器
playwright list-browsers
# 更新浏览器到最新版本
playwright install --force
# 卸载浏览器
playwright uninstall chromium
```
## 使用示例
### 示例1:批量截图多个网页
```bash
# 对多个网页进行全屏截图
for url in "https://google.com" "https://github.com" "https://stackoverflow.com"; do
name=$(echo $url | sed 's/https\?:\/\///' | sed 's/\//_/g')
playwright screenshot $url --full-page "${name}.png"
done
```
### 示例2:自动填写表单
```python
# 录制生成的自动登录脚本示例
from playwright.sync_api import Playwright, sync_playwright, expect
def run(playwright: Playwright) -> None:
browser = playwright.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
page.goto("https://example.com/login")
page.get_by_label("用户名").fill("your_username")
page.get_by_label("密码").fill("your_password")
page.get_by_role("button", name="登录").click()
# 截图登录后的页面
page.screenshot(path="logged_in.png")
context.close()
browser.close()
with sync_playwright() as playwright:
run(playwright)
```
### 示例3:模拟移动端访问
```bash
# 模拟iPhone 14访问网页并截图
playwright screenshot https://example.com --device="iPhone 14" mobile.png
# 模拟安卓设备
playwright screenshot https://example.com --device="Galaxy S23" android.png
```
## 最佳实践
1. 优先使用无头模式执行自动化任务,性能更高
2. 复杂操作优先使用`codegen`录制生成代码,再手动调整
3. 执行长时间任务时添加`--slowmo=1000`参数减慢操作速度,避免被反爬
4. 保存登录状态使用`--save-storage=auth.json`,下次可以直接`--load-storage=auth.json`跳过登录
5. 网络不稳定时添加`--timeout=60000`延长超时时间

View file

@ -0,0 +1,6 @@
{
"ownerId": "kn7ez7a31k0sqt7ad9wk2hq31n82qea1",
"slug": "playwright-cli-openclaw",
"version": "1.0.0",
"publishedAt": 1773287492717
}

View file

@ -1,14 +1,5 @@
# self-improvement
Self-improvement skill for Oclaw. It captures learnings, errors, and feature requests to support continuous improvement across sessions.
Self-improvement skill for Oclaw. Captures learnings, errors, and feature requests to support continuous improvement across sessions.
## Attribution
Remade for Oclaw from the original repo:
- https://github.com/pskoett/pskoett-ai-skills
- https://github.com/pskoett/pskoett-ai-skills/tree/main/skills/self-improvement
## Main File
- `SKILL.md`
Based on the original [pskoett/pskoett-ai-skills](https://github.com/pskoett/pskoett-ai-skills) — adapted for Oclaw with `memory_wiki_*` tools.

View file

@ -1,83 +1,91 @@
---
name: self-improvement
description: 将纠错、错误与能力缺口写入 Wiki 以持续改进。适用于操作失败、用户反馈纠正、问题复发、或需要沉淀并推动的新能力需求场景。
metadata:
# self-improvement — 自我改进 (Self-Improvement)
> 将纠错、错误与能力缺口写入 Wiki 以持续改进,形成"犯错→记录→不再犯"的闭环。
> 本技能完全基于 `memory_wiki_*` 工具,不依赖本地文件或外部脚本。
---
# 自我改进(仅 Wiki)
## 触发条件 (Triggers)
本技能仅使用 Wiki,不使用本地 `.learnings/` 文件。
出现以下情况时启用:
## 存储路径
1. 命令或操作出现非预期失败
2. 用户对回答进行纠正
3. 用户提出缺失能力需求
4. 同类问题再次复发
5. 发现更优且可复用的方法
6. 每轮对话结束时(强制三问反思)
- `improvement/learnings.md`
- `improvement/errors.md`
- `improvement/feature-requests.md`
## 必要流程 (Required Workflow)
## 触发条件
```
1. memory_wiki_search → 检索 improvement/* 看是否已有记录
2. memory_wiki_get → 读取目标文件上下文
3. memory_wiki_apply → 追加结构化条目 (action=append)
4. memory_wiki_lint → 质量检查
5. lint 报错 → 立即修复
```
出现以下情况时启用本技能:
## 条目路由 (Entry Routing)
1. 命令或操作出现非预期失败。
2. 用户对错误回答进行纠正。
3. 用户提出缺失能力需求。
4. 同类问题再次复发。
5. 发现更优且可复用的方法。
| 内容类型 | 目标文件 | 示例 |
|---------|---------|------|
| 纠错/洞见/最佳实践 | `improvement/learnings.md` | 用户纠正了某个认知错误 |
| 运行时/工具/API 失败 | `improvement/errors.md` | 某个工具调用报错 |
| 能力缺口/功能请求 | `improvement/feature-requests.md` | 用户问"你能做 X 吗?" |
| 长篇经验沉淀 (>300 字) | `improvement/learnings/<topic>.md` | 一次完整的技能安装流程 |
## 必要流程
每次触发都执行以下流程:
1. 用 `memory_wiki_search` 检索历史相关记录。
2. 用 `memory_wiki_get` 读取目标文件上下文。
3. 用 `memory_wiki_apply`(`action=append`)追加结构化条目。
4. 对目标文件执行 `memory_wiki_lint`。
5. 若 lint 报错,立即用 `memory_wiki_apply` 修复。
## 条目路由
- 纠错 / 洞见 / 最佳实践 -> `improvement/learnings.md`
- 运行时 / 工具 / API 失败 -> `improvement/errors.md`
- 能力请求 / 缺失功能 -> `improvement/feature-requests.md`
## 条目模板
## 条目模板 (Entry Template)
```markdown
## [ID] <标题>
**Logged**: ISO-8601 时间戳
## [ID] 标题 (Title)
**Logged**: ISO-8601 时间戳 (Timestamp)
**Priority**: low | medium | high | critical
**Status**: pending
**Status**: pending | resolved | promoted_to_skill
**Area**: frontend | backend | infra | tests | docs | config
### Summary
### 摘要 (Summary)
一句话摘要。
### Details
### 详情 (Details)
发生了什么、为什么重要、可执行改进建议。
### Metadata
- Source: conversation | error | user_feedback
### 元数据 (Metadata)
- Source: conversation | error | user_feedback | reflection
- Related Files: path/to/file.ext
- See Also: <optional-id>
```
ID 格式:
### ID 格式 (ID Format)
- Learning: `LRN-YYYYMMDD-XXX`
- Error: `ERR-YYYYMMDD-XXX`
- Feature request: `FEAT-YYYYMMDD-XXX`
## 提升目标
XXX = 当天序号,从 001 开始。
当条目已具备广泛复用价值时,将精炼规则提升到:
## 提升路径 (Promotion Path)
- `AGENTS.md`(工作流模式)
- `SOUL.md`(行为模式)
- `TOOLS.md`(工具易错点)
- `.github/copilot-instructions.md`(共享编码约定)
当某个条目被验证有广泛复用价值时:
## 安全规则
| 到达条件 | 提升目标 |
|---------|---------|
| 可用作行为约定 | `core/principles.md` |
| 可用作维护规则 | `core/maintenance.md` |
| 可提炼为独立技能 | 使用 `skill-creator` 创建新技能 |
| 某个工具易错点 | 标注在 SKILL.md 或 REFERENCES.md |
- 不记录密钥、令牌、凭据或原始敏感信息。
- 敏感输出使用脱敏摘要,不保留完整原文。
- 未验证事实不得当作已确认规则持久化。
## 会话结束三问 (End-of-Session Reflection)
每轮对话结束前执行:
1. **学到了什么?** → `improvement/learnings.md`
2. **有什么可优化?** → `improvement/feature-requests.md` 或 `core/principles.md`
3. **用户画像有更新吗?** → `users/current.md`
## 安全规则 (Security Rules)
- 不记录密钥、令牌、凭据或原始敏感信息
- 敏感输出使用脱敏摘要,不保留完整原文
- 未验证事实不得当作已确认规则持久化
- 涉及用户偏好写入时需有明确的对话证据,不推测

View file

@ -1,5 +0,0 @@
# Errors Log
Command failures, exceptions, and unexpected behaviors.
---

View file

@ -1,5 +0,0 @@
# Feature Requests
Capabilities requested by user that don't currently exist.
---

View file

@ -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
...
```
---

View file

@ -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

View file

@ -1,23 +0,0 @@
---
name: self-improvement
description: "在智能体启动阶段注入自我改进提醒"
metadata: {"oclaw":{"emoji":"🧠","events":["agent:bootstrap"]}}
---
# 自我改进 Hook
在 `agent:bootstrap` 阶段注入“学习沉淀提醒”。
## 功能说明
- 在 `agent:bootstrap` 触发(工作区文件注入前)
- 注入提醒块,引导将学习写入 Wiki 路径
- 提示智能体记录纠错、错误与新发现
## 配置方式
无需额外配置,启用命令:
```bash
oclaw hooks enable self-improvement
```

View file

@ -1,85 +0,0 @@
from __future__ import annotations
from typing import Any
REMINDER_NAME = "SELF_IMPROVEMENT_REMINDER.md"
REMINDER_PATH = REMINDER_NAME
REMINDER_CONTENT = """## 自我改进提醒
任务完成后,请评估是否产生可沉淀学习。
仅在当前仓库/工作区启用 self-improvement 技能时记录。
记录前:
- 使用 memory_wiki_* 工具写入 `improvement/` 下的 Wiki 笔记
- 不记录密钥、令牌、私钥、环境变量或原始对话全文
- 优先使用简短摘要或脱敏片段,避免完整命令输出
**以下情况应记录:**
- 用户纠正你 → `improvement/learnings.md`
- 命令/操作失败 → `improvement/errors.md`
- 用户提出缺失能力 → `improvement/feature-requests.md`
- 发现认知错误 → `improvement/learnings.md`
- 发现更优做法 → `improvement/learnings.md`
**当模式被验证后进行提升:**
- 行为模式 → `SOUL.md`
- 工作流改进 → `AGENTS.md`
- 工具易错点 → `TOOLS.md`
条目保持简洁:时间、标题、发生了什么、后续应如何做。"""
def _is_record(value: object) -> bool:
return isinstance(value, dict)
def _is_injected_reminder_file(value: object) -> bool:
if not _is_record(value) or str(value.get("path")) != REMINDER_PATH: # type: ignore[union-attr]
return False
v = value # type: ignore[assignment]
return v.get("virtual") is True or v.get("content") == REMINDER_CONTENT
def handle(event: object) -> None:
if getattr(event, "type", None) != "agent" or getattr(event, "action", None) != "bootstrap":
return
ctx = getattr(event, "context", None)
if not isinstance(ctx, dict):
return
session_key = str(getattr(event, "sessionKey", "") or "")
if ":subagent:" in session_key:
return
if not isinstance(ctx.get("bootstrapFiles"), list):
return
files: list[object] = list(ctx.get("bootstrapFiles") or [])
occupied = any(
_is_record(f) and str(f.get("path")) == REMINDER_PATH and not _is_injected_reminder_file(f) # type: ignore[union-attr]
for f in files
)
if occupied:
return
cleaned: list[object] = [
f
for i, f in enumerate(files)
if (not _is_injected_reminder_file(f))
or (next((j for j, c in enumerate(files) if _is_injected_reminder_file(c)), -1) == i)
]
reminder_file: dict[str, Any] = {
"name": REMINDER_NAME,
"path": REMINDER_PATH,
"content": REMINDER_CONTENT,
"missing": False,
"virtual": True,
}
existing_idx = next((i for i, f in enumerate(cleaned) if _is_injected_reminder_file(f)), -1)
if existing_idx == -1:
cleaned.append(reminder_file)
else:
cleaned[existing_idx] = reminder_file
ctx["bootstrapFiles"] = cleaned

View file

@ -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: oclaw/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, oclaw/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 API timeout during request processing
### Error
```
TimeoutError: Request to api.example.com timed out after 30000ms
```
### Context
- Command: POST /api/process
- 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: oclaw/services/api-client.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
```

View file

@ -1,225 +0,0 @@
# Hook Setup Guide
Configure automatic self-improvement triggers for AI coding agents.
## 概览
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
## 自我改进
After completing tasks that involved:
- Debugging non-obvious issues
- Discovering workarounds
- Learning project-specific patterns
- Resolving unexpected errors
Consider logging the learning to Wiki (`improvement/learnings.md`, `improvement/errors.md`, `improvement/feature-requests.md`) 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
- Treat `CLAUDE_TOOL_OUTPUT` as potentially sensitive; do not log or forward it verbatim unless the user explicitly wants that detail
- All scripts are opt-in (you must configure them explicitly)
- Recommended default: enable `UserPromptSubmit` only, and add `PostToolUse` only when you want error-pattern reminders from command output
## 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

View file

@ -1,248 +0,0 @@
# Oclaw Integration
Complete setup and usage guide for integrating the self-improvement skill with Oclaw.
## 概览
Oclaw 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
```
~/.oclaw/
├── 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.py
```
## 快速配置
### 1. Install the Skill
```bash
clawdhub install self-improving-agent
```
Or copy manually:
```bash
cp -r self-improving-agent ~/.oclaw/skills/
```
### 2. Install the Hook (Optional)
Copy the hook to Oclaw's hooks directory:
```bash
cp -r hooks/oclaw ~/.oclaw/hooks/self-improvement
```
Enable the hook:
```bash
oclaw hooks enable self-improvement
```
### 3. Ensure Wiki Improvement Notes Exist
Create or initialize these Wiki notes under your configured wiki root:
- `improvement/learnings.md`
- `improvement/errors.md`
- `improvement/feature-requests.md`
## 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
## 自我改进技能
Log learnings to Wiki `improvement/*.md` notes 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 Wiki improvement notes (`improvement/*.md`)
2. **Cross-session**: Promote to workspace files
### Promotion Decision Tree
```
Is the learning project-specific?
├── Yes → Keep in improvement/learnings.md
└── 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
Oclaw provides tools for cross-session communication:
Use these only when cross-session sharing is explicitly needed and the environment is trusted. Prefer short sanitized summaries over raw transcripts, command output, or secret-bearing content.
### 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)
```
Only read another session's transcript when the user explicitly wants shared context or continuation across sessions.
### sessions_send
Send message to another session:
```
sessions_send(sessionKey="session-id", message="Learning: API requires X-Custom-Header")
```
Prefer sending a concise learning summary plus relevant paths rather than forwarding raw transcript content.
### 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
### Oclaw-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 `improvement/*.md` or report upstream |
## Verification
Check hook is registered:
```bash
oclaw hooks list
```
Check skill is loaded:
```bash
oclaw 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 wiki `improvement/*.md` notes exist
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 `oclaw status` to see loaded skills

View file

@ -1,23 +0,0 @@
#!/bin/bash
# 自我改进激活 Hook
# 在 UserPromptSubmit 触发,用于提醒记录学习沉淀
# 输出保持精简(约 50-100 tokens)以降低上下文负担
set -e
# 以系统上下文形式输出提醒
cat << 'EOF'
<self-improvement-reminder>
本任务完成后,请判断是否产出可沉淀知识:
- 是否通过排查得到非显而易见的解法?
- 是否形成了异常行为的可复用绕过方案?
- 是否识别出项目特有模式?
- 是否有需要调试才能解决的错误?
若是,请写入 Wiki:
- improvement/learnings.md
- improvement/errors.md
- improvement/feature-requests.md
若价值较高(复发、可广泛复用),请考虑提炼为独立技能。
</self-improvement-reminder>
EOF

View file

@ -1,55 +0,0 @@
#!/bin/bash
# 自我改进错误检测 Hook
# 在 Bash 的 PostToolUse 触发,用于检测命令失败
# 读取 CLAUDE_TOOL_OUTPUT 环境变量
set -e
# 检查工具输出是否包含错误信号
# CLAUDE_TOOL_OUTPUT 为工具执行结果
OUTPUT="${CLAUDE_TOOL_OUTPUT:-}"
# 错误模式(大小写不敏感匹配)
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"
)
# 检查输出是否匹配任一错误模式
contains_error=false
for pattern in "${ERROR_PATTERNS[@]}"; do
if [[ "$OUTPUT" == *"$pattern"* ]]; then
contains_error=true
break
fi
done
# 仅在检测到错误时输出提醒
if [ "$contains_error" = true ]; then
cat << 'EOF'
<error-detected>
检测到命令错误。若满足以下任一条件,请记录到 improvement/errors.md:
- 错误出乎预期或并不直观
- 需要排查才能解决
- 可能在相似场景复发
- 解决方案对后续会话有复用价值
记录时请使用 self-improvement 技能格式:[ERR-YYYYMMDD-XXX]
</error-detected>
EOF
fi

View file

@ -1,221 +0,0 @@
#!/bin/bash
# Skill Extraction Helper
# Creates a new skill from a learning entry
# 用法: ./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
用法: $(basename "$0") <skill-name> [options]
根据学习条目创建新技能。
参数:
skill-name 技能名称(小写,空格使用连字符)
选项:
--dry-run 仅预览将创建的内容,不落盘
--output-dir 当前路径下的相对输出目录(默认: ./skills)
-h, --help 显示帮助信息
示例:
$(basename "$0") docker-m1-fixes
$(basename "$0") api-timeout-patterns --dry-run
$(basename "$0") pnpm-setup --output-dir ./skills/custom
技能将创建在: \$SKILLS_DIR/<skill-name>/
EOF
}
log_info() {
echo -e "${GREEN}[信息]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[警告]${NC} $1"
}
log_error() {
echo -e "${RED}[错误]${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 需要提供相对路径参数"
usage
exit 1
fi
SKILLS_DIR="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
-*)
log_error "未知选项: $1"
usage
exit 1
;;
*)
if [ -z "$SKILL_NAME" ]; then
SKILL_NAME="$1"
else
log_error "意外参数: $1"
usage
exit 1
fi
shift
;;
esac
done
# Validate skill name
if [ -z "$SKILL_NAME" ]; then
log_error "必须提供技能名称"
usage
exit 1
fi
# Validate skill name format (lowercase, hyphens, no spaces)
if ! [[ "$SKILL_NAME" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then
log_error "技能名称格式无效。仅允许小写字母、数字和连字符。"
log_error "示例: 'docker-fixes', 'api-patterns', 'pnpm-setup'"
exit 1
fi
# Validate output path to avoid writes outside current workspace.
if [[ "$SKILLS_DIR" = /* ]]; then
log_error "输出目录必须是当前目录下的相对路径。"
exit 1
fi
if [[ "$SKILLS_DIR" =~ (^|/)\.\.(/|$) ]]; then
log_error "输出目录不能包含 '..' 路径段。"
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_PATH"
log_error "请更换名称或先删除已有技能目录。"
exit 1
fi
# Dry run output
if [ "$DRY_RUN" = true ]; then
log_info "预览模式 - 将会创建:"
echo " $SKILL_PATH/"
echo " $SKILL_PATH/SKILL.md"
echo ""
echo "模板内容预览:"
echo "---"
cat << TEMPLATE
name: $SKILL_NAME
description: "[TODO: 用一句话说明技能作用与触发场景]"
---
# $(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: 简要说明技能目的]
## Quick Reference
| Situation | Action |
|-----------|--------|
| [触发条件] | [执行动作] |
## Usage
[TODO: 详细使用说明]
## Examples
[TODO: 补充具体示例]
## Source Learning
本技能由学习条目提炼生成。
- Learning ID: [TODO: 填写原始学习条目 ID]
- Original File: improvement/learnings.md
TEMPLATE
echo "---"
exit 0
fi
# Create skill directory structure
log_info "正在创建技能: $SKILL_NAME"
mkdir -p "$SKILL_PATH"
# Create SKILL.md from template
cat > "$SKILL_PATH/SKILL.md" << TEMPLATE
---
name: $SKILL_NAME
description: "[TODO: 用一句话说明技能作用与触发场景]"
---
# $(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: 简要说明技能目的]
## Quick Reference
| Situation | Action |
|-----------|--------|
| [触发条件] | [执行动作] |
## Usage
[TODO: 详细使用说明]
## Examples
[TODO: 补充具体示例]
## Source Learning
本技能由学习条目提炼生成。
- Learning ID: [TODO: 填写原始学习条目 ID]
- Original File: improvement/learnings.md
TEMPLATE
log_info "已创建: $SKILL_PATH/SKILL.md"
# Suggest next steps
echo ""
log_info "技能脚手架创建成功!"
echo ""
echo "下一步建议:"
echo " 1. 编辑 $SKILL_PATH/SKILL.md"
echo " 2. 用你的学习内容填写 TODO 区块"
echo " 3. 若有详细文档,新增 references/ 目录"
echo " 4. 若有可执行脚本,新增 scripts/ 目录"
echo " 5. 在原学习条目中更新:"
echo " **Status**: promoted_to_skill"
echo " **Skill-Path**: skills/$SKILL_NAME"

View file

@ -155,6 +155,14 @@ class McpAdapterTests(unittest.TestCase):
self.assertIn("oclaw", parts)
self.assertIn("_local", parts)
def test_trilium_env_aliases_from_legacy_names(self) -> None:
from runtime.operations.mcp_env import _apply_trilium_env_aliases
vals = {"TRILIUM_URL": "http://127.0.0.1:37840/etapi", "TRILIUM_TOKEN": "secret"}
_apply_trilium_env_aliases(vals)
self.assertEqual(vals["TRILIUM_API_URL"], "http://127.0.0.1:37840/etapi")
self.assertEqual(vals["TRILIUM_API_TOKEN"], "secret")
def test_mcp_env_allowlist_keys_default_when_unset(self) -> None:
from runtime.operations import mcp_env