GPU_GUARD_MONOREPO/docs/superpowers/plans/2026-04-27-p2-standard-compat.md
2026-05-20 21:39:12 +08:00

423 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# P2: Agent Skills 标准兼容 + References 加载 实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 兼容 Agent Skills 社区标准名称验证、hidden skill、字段映射改造 prompt 索引区分三种 skill 类型,改造 read_skill 返回格式强制 Agent 读取 required references。
**Architecture:** 新增 `validateSkillName()` 验证函数。`buildSkillsPrompt()` 输出增加 type 属性和 compute-entry 的 interface 摘要。`read_skill` 工具返回区分 `<skill_required_references>``<skill_optional_references>`。基础设施文件从 collectFiles 中过滤。
**Tech Stack:** TypeBox, XML 模板, Midway.js
**Spec:** `docs/superpowers/specs/2026-04-27-skill-system-evolution-design.md` Section 5 + 10.5-10.7 + 10.10
**Depends on:** P1 (SkillConfigService, SkillClassification must be integrated into SkillLoaderService before P2 Task 2 can work. Execute P1 Tasks 1-2 first.)
---
### Task 1: 名称验证函数(无依赖,可独立执行)
**Files:**
- Modify: `packages/backend/src/modules/netaclaw/service/skill_loader.ts`
- [ ] **Step 1: 在 skill_loader.ts 顶部新增 validateSkillName 函数**
在 class 定义之前添加:
```typescript
const MAX_NAME_LENGTH = 64;
export function validateSkillName(name: string, parentDirName?: string): string[] {
const errors: string[] = [];
if (!name) { errors.push('name is required'); return errors; }
if (name.length > MAX_NAME_LENGTH) errors.push(`name exceeds ${MAX_NAME_LENGTH} characters`);
if (!/^[a-z0-9-]+$/.test(name)) errors.push('name must be lowercase a-z, 0-9, hyphens only');
if (name.startsWith('-') || name.endsWith('-')) errors.push('name must not start or end with hyphen');
if (name.includes('--')) errors.push('name must not contain consecutive hyphens');
if (parentDirName && name !== parentDirName) errors.push(`name "${name}" does not match directory "${parentDirName}"`);
return errors;
}
```
- [ ] **Step 2: 在 parseSkillMd 中调用验证warning 级别,不阻塞加载)**
`parseSkillMd` 方法中,解析出 name 后添加:
```typescript
const nameErrors = validateSkillName(name);
if (nameErrors.length > 0) {
this.logger.warn('[SkillLoader] Skill "%s" name validation: %s', name, nameErrors.join(', '));
}
```
- [ ] **Step 3: 在 skill_manage.ts 和 skill_installer.ts 的创建/安装流程中调用验证error 级别,阻塞)**
`skill_manage.ts``execute` 方法中,`create` 分支的 `parseSkillMd` 之后添加:
```typescript
const nameErrors = validateSkillName(parsed.name, params.name);
if (nameErrors.length > 0) {
return textResult(`名称不合规: ${nameErrors.join('; ')}`);
}
```
`skill_installer.ts``installFromGitHub``installFromZip` 中,`parseSkillMd` 之后添加类似校验。
- [ ] **Step 4: Commit**
```bash
git add packages/backend/src/modules/netaclaw/service/skill_loader.ts \
packages/backend/src/modules/netaclaw/tools/builtin/skill_manage.ts \
packages/backend/src/modules/netaclaw/service/skill_installer.ts
git commit -m "feat(skill): add Agent Skills standard name validation"
```
---
### Task 2: buildSkillsPrompt 输出改造
**Files:**
- Modify: `packages/backend/src/modules/netaclaw/service/skill_loader.ts`
- [ ] **Step 1: 改造 buildSkillsPrompt 方法**
替换现有 `buildSkillsPrompt` 方法:
```typescript
buildSkillsPrompt(skillNames: string[], availableTools: string[]): string {
const filtered = this.filterByConditions(skillNames, availableTools);
if (filtered.length === 0) return '';
const lines: string[] = [];
let totalChars = 0;
for (const s of filtered) {
// 跳过 hidden skill
const fm = s.metadata as any;
if (fm?.['disable-model-invocation'] === true) continue;
const classification = this.skillConfig.classify(s.name);
const category = fm?.category || '通用';
const tags = Array.isArray(fm?.tags) ? fm.tags.join(',') : '';
let body = ` ${s.description}`;
// compute-entry: 附带 interface.input 摘要
if (classification === 'compute-entry') {
const config = this.skillConfig.getConfig(s.name);
if (config?.interface?.input) {
const inputDesc = Object.entries(config.interface.input)
.map(([k, v]) => `${k}(${v.type}${v.required ? ',必填' : ''}${v.default ? ',默认' + v.default : ''})`)
.join(', ');
body += `\n 输入: ${inputDesc}`;
}
}
const line = ` <skill name="${s.name}" type="${classification}" category="${category}" tags="${tags}">\n${body}\n </skill>`;
if (totalChars + line.length > SkillLoaderService.MAX_SKILLS_PROMPT_CHARS) break;
lines.push(line);
totalChars += line.length;
}
if (lines.length === 0) return '';
return `\n\n## 技能(必须扫描)\n回复前扫描以下技能列表。prompt 类型用 read_skill 加载指令compute-entry 类型用 execute_skill 直接调用compute-toolkit 类型用 read_skill 加载指令后通过 bash 执行。\n读取 skill 后,如果返回中包含 <skill_required_references>,你必须在执行任何操作前先用 read_skill_file 逐一读取列出的所有文档。这不是建议,是强制要求。\n\n<available_skills>\n${lines.join('\n')}\n</available_skills>`;
}
```
- [ ] **Step 2: 验证编译通过**
Run: `cd packages/backend && npx tsc --noEmit`
- [ ] **Step 3: Commit**
```bash
git add packages/backend/src/modules/netaclaw/service/skill_loader.ts
git commit -m "feat(skill): enhance buildSkillsPrompt with type classification and required references guidance"
```
---
### Task 3: read_skill 返回格式改造
**Files:**
- Modify: `packages/backend/src/modules/netaclaw/tools/builtin/read_skill.ts`
- [ ] **Step 1: 改造 read_skill 的 execute 方法**
替换现有 `execute` 方法体:
```typescript
async execute(_id, params) {
const skill = skillLoader.getSkill(params.name);
if (!skill) {
return textResult(`未找到名为 "${params.name}" 的 skill`);
}
let result = skill.content;
// 获取 references 配置
const config = skillLoader.getSkillConfig(params.name);
const configRefs = config?.references;
const fmRefs = (skill.metadata as any)?.references;
const refs = configRefs || fmRefs || null;
const requiredRefs: string[] = refs?.required || [];
const allFiles = skill.files || [];
// 基础设施文件过滤
const INFRA_FILES = new Set([
'skill.config.yaml', 'requirements.txt', 'package.json',
'package-lock.json', 'tsconfig.json', '.env',
]);
const referenceFiles = allFiles.filter(f => !INFRA_FILES.has(f) && !requiredRefs.includes(f));
if (requiredRefs.length > 0) {
result += `\n\n<skill_required_references>`;
result += `\n⚠ 执行此 skill 的任务前,你必须先用 read_skill_file 读取以下文档:`;
for (const ref of requiredRefs) {
result += `\n- ${ref}`;
}
result += `\n未读取这些文档就执行操作会导致错误。`;
result += `\n</skill_required_references>`;
}
if (referenceFiles.length > 0) {
result += `\n\n<skill_optional_references>`;
result += `\n以下文档可按需读取用 read_skill_file 工具):`;
for (const ref of referenceFiles) {
result += `\n- ${ref}`;
}
result += `\n</skill_optional_references>`;
}
return textResult(result);
},
```
- [ ] **Step 2: 验证编译通过**
Run: `cd packages/backend && npx tsc --noEmit`
- [ ] **Step 3: Commit**
```bash
git add packages/backend/src/modules/netaclaw/tools/builtin/read_skill.ts
git commit -m "feat(skill): enhance read_skill with required/optional references separation"
```
---
### Task 4: collectFiles 基础设施文件过滤
**Files:**
- Modify: `packages/backend/src/modules/netaclaw/service/skill_loader.ts`
- [ ] **Step 1: 在 collectFiles 方法中过滤基础设施文件**
`collectFiles` 方法的 `else if (entry.name !== 'SKILL.md')` 条件中,增加过滤:
```typescript
const INFRA_FILES = new Set([
'skill.config.yaml', 'requirements.txt', 'package.json',
'package-lock.json', 'tsconfig.json', '.env',
]);
// 在 else if 分支中
} else if (entry.name !== 'SKILL.md' && !INFRA_FILES.has(entry.name)) {
const rel = path.relative(baseDir, fullPath).replace(/\\/g, '/');
results.push(rel);
}
```
- [ ] **Step 2: Commit**
```bash
git add packages/backend/src/modules/netaclaw/service/skill_loader.ts
git commit -m "feat(skill): filter infrastructure files from skill file listing"
```
---
### Task 5: read_skill task 参数与 routes 匹配Spec 10.10
**Files:**
- Modify: `packages/backend/src/modules/netaclaw/tools/builtin/read_skill.ts`
- [ ] **Step 1: 给 ReadSkillParams 新增可选 task 参数**
```typescript
const ReadSkillParams = Type.Object({
name: Type.String({ description: '要读取的 skill 名称' }),
task: Type.Optional(Type.String({ description: '当前任务描述,用于自动匹配需要读取的文档' })),
});
```
- [ ] **Step 2: 在 execute 方法中实现 routes 匹配**
在获取 `requiredRefs` 之后、构建返回结果之前,添加 routes 匹配逻辑:
```typescript
// routes 匹配:如果传入了 task 且 config 有 routes尝试关键词匹配
let routeMatchedRefs: string[] = [];
if (params.task && refs?.routes) {
const taskLower = params.task.toLowerCase();
for (const route of refs.routes) {
if (route.match.some(keyword => taskLower.includes(keyword.toLowerCase()))) {
routeMatchedRefs.push(...route.required_refs);
}
}
routeMatchedRefs = [...new Set(routeMatchedRefs)]; // 去重
}
// 合并 required + route matched
const allRequired = [...new Set([...requiredRefs, ...routeMatchedRefs])];
```
如果 `routeMatchedRefs` 命中了文档,尝试直接读取并拼接到返回结果中(减少 Agent 二次调用):
```typescript
if (routeMatchedRefs.length > 0) {
result += `\n\n<skill_route_matched_references>`;
for (const ref of routeMatchedRefs) {
const filePath = skillLoader.getSkillFilePath(params.name, ref);
if (filePath) {
try {
const content = await fs.readFile(filePath, 'utf-8');
result += `\n\n--- ${ref} ---\n${content}`;
} catch { /* skip unreadable */ }
}
}
result += `\n</skill_route_matched_references>`;
}
```
- [ ] **Step 3: Commit**
```bash
git add packages/backend/src/modules/netaclaw/tools/builtin/read_skill.ts
git commit -m "feat(skill): add task parameter and routes matching to read_skill tool"
```
---
### Task 6: Agent 分配 skill 时的类型校验Spec 10.6
**Files:**
- Modify: `packages/frontend/src/modules/agent/views/agent-edit.vue`
- Modify: `packages/backend/src/modules/netaclaw/controller/agent.ts`
- [ ] **Step 1: metas 端点返回 classification 字段**
`SkillLoaderService.getSkillMetas()` 中,返回对象添加(如果 P3 Task 5 尚未添加):
```typescript
classification: this.skillConfig.classify(fs.name),
```
- [ ] **Step 2: 前端 skill 选择器显示分类标签**
`agent-edit.vue` 的可选 Skill 列表项中(约 line 38在现有 `skillType` 标签旁新增分类标签:
```vue
<el-tag v-if="sk.classification" size="small"
:type="sk.classification === 'compute-entry' ? 'success' : sk.classification === 'compute-toolkit' ? '' : 'info'">
{{ sk.classification }}
</el-tag>
```
已选择列表中同样添加:
```vue
<span class="item-main">{{ getSkillLabel(name) }}</span>
<el-tag size="small" :type="getSkillClassification(name) === 'compute-entry' ? 'success' : 'info'">
{{ getSkillClassification(name) }}
</el-tag>
```
新增辅助函数:
```typescript
function getSkillClassification(name: string): string {
const meta = skillMetas.value.find((s) => s.name === name);
return meta?.classification || 'prompt';
}
```
- [ ] **Step 3: 前端选择 compute skill 时显示工具权限 warning**
`addSkill` 函数中添加校验:
```typescript
function addSkill(name: string) {
if (!form.value.skills.includes(name)) {
form.value.skills.push(name);
// 校验工具权限
const classification = getSkillClassification(name);
if (classification === 'compute-entry') {
ElMessage.warning(`"${name}" 是 compute-entry 类型,请确保该 Agent 已启用 execute_skill 工具`);
} else if (classification === 'compute-toolkit') {
ElMessage.warning(`"${name}" 是 compute-toolkit 类型,请确保该 Agent 已启用 bash 工具`);
}
}
}
```
- [ ] **Step 4: 后端保存时返回 warnings**
`controller/agent.ts``update` 方法中,注入 `SkillLoaderService`,保存前校验:
```typescript
@Inject()
skillLoader: SkillLoaderService;
@Post('/update')
async update(@Body() body: any) {
const warnings: string[] = [];
if (body.skills?.length) {
for (const skillName of body.skills) {
const classification = this.skillLoader.getSkillClassification(skillName);
if (classification === 'compute-entry') {
warnings.push(`Skill "${skillName}" 需要 execute_skill 工具`);
} else if (classification === 'compute-toolkit') {
warnings.push(`Skill "${skillName}" 需要 bash 工具`);
}
}
}
await this.agentService.update(body);
return { code: 1000, message: 'success', warnings };
}
```
- [ ] **Step 5: Commit**
```bash
git add packages/frontend/src/modules/agent/views/agent-edit.vue \
packages/backend/src/modules/netaclaw/controller/agent.ts
git commit -m "feat(skill): add skill classification display and tool permission warnings in agent editor"
```
---
### Task 7: skill_context.ts 废弃标记Spec 10.12
**Files:**
- Modify: `packages/backend/src/modules/netaclaw/service/skill_context.ts`
- [ ] **Step 1: 添加 @deprecated 注释**
`buildSkillContext` 函数上方添加:
```typescript
/**
* @deprecated 使用 tool_resolver.ts 中的 skill 工具注入逻辑替代。
* 此函数不再被主链路调用,保留仅为兼容可能的外部引用。
*/
```
- [ ] **Step 2: Commit**
```bash
git add packages/backend/src/modules/netaclaw/service/skill_context.ts
git commit -m "chore(skill): mark buildSkillContext as deprecated"
```