fix: OpenClaw compliance audit fixes - refactor tool interface to AnyAgentTool, restructure skill directories

This commit is contained in:
root
2026-04-17 09:36:22 +08:00
parent 192179a986
commit 3d58ff7766
11 changed files with 194 additions and 360 deletions

29
.sisyphus/fix-tracker.md Normal file
View File

@ -0,0 +1,29 @@
# 修复追踪文件
**分支**: openclaw
**开始时间**: 2026-04-16
**状态**: 重新执行
---
## 修复计划
### P0 - 阻断性问题
#### 1. 删除自定义 tool_interface.ts
#### 2. 重构 graph_memory_tool.ts
#### 3. 更新 plugin-entry.ts
### P2 - 最佳实践
#### 4. 重组 Skill 目录结构
#### 5. 更新 bundled-skills 目录结构
#### 6. 更新 openclaw.plugin.json
#### 7. 更新 README.md / README_EN.md
---
## 执行记录
### [进行中] P0: 核心修复

View File

@ -35,13 +35,13 @@ npm run build
```bash
# 方式 A:工作空间级(推荐,仅当前项目可用)
cp -r skills/graph-memory <你的项目目录>/skills/graph-memory
cp -r skills/graph-memory/persona <你的项目目录>/skills/graph-memory-persona
cp -r skills/graph-memory/task <你的项目目录>/skills/graph-memory-task
cp -r skills/graph-memory-persona <你的项目目录>/skills/graph-memory-persona
cp -r skills/graph-memory-task <你的项目目录>/skills/graph-memory-task
# 方式 B:用户级(所有项目可用)
cp -r skills/graph-memory ~/.openclaw/skills/graph-memory
cp -r skills/graph-memory/persona ~/.openclaw/skills/graph-memory-persona
cp -r skills/graph-memory/task ~/.openclaw/skills/graph-memory-task
cp -r skills/graph-memory-persona ~/.openclaw/skills/graph-memory-persona
cp -r skills/graph-memory-task ~/.openclaw/skills/graph-memory-task
```
### 步骤 4:安装 Plugin(让 OpenClaw 运行时加载 Tool)
@ -130,8 +130,8 @@ npm run build
```bash
cp -r skills/graph-memory ~/.agents/skills/graph-memory
cp -r skills/graph-memory/persona ~/.agents/skills/graph-memory-persona
cp -r skills/graph-memory/task ~/.agents/skills/graph-memory-task
cp -r skills/graph-memory-persona ~/.agents/skills/graph-memory-persona
cp -r skills/graph-memory-task ~/.agents/skills/graph-memory-task
```
---
@ -153,19 +153,23 @@ ts/
│ │ └── graph_memory_tool.ts # Tool 实现
│ └── tool_limiter.ts # 调用限制器
├── bundled-skills/
│ └── graph-memory/ # 内置 Skill 定义
│ ├── SKILL.md
│ ├── persona/SKILL.md
│ └── task/SKILL.md
│ ├── graph-memory/ # 内置 Skill 定义
│ │ └── SKILL.md
│ ├── graph-memory-persona/
│ │ └── SKILL.md
│ └── graph-memory-task/
│ └── SKILL.md
├── package.json
├── tsconfig.json
└── openclaw.plugin.json # Plugin Manifest
skills/ # 独立 Skill 定义
└── graph-memory/
├── SKILL.md
├── persona/SKILL.md
└── task/SKILL.md
├── graph-memory/
│ └── SKILL.md
├── graph-memory-persona/
│ └── SKILL.md
└── graph-memory-task/
└── SKILL.md
```
---

View File

@ -35,13 +35,13 @@ Copy the 3 Skills from `skills/` to OpenClaw's skill directory. Priority: `<work
```bash
# Option A: Workspace-level (recommended, current project only)
cp -r skills/graph-memory <your-project-dir>/skills/graph-memory
cp -r skills/graph-memory/persona <your-project-dir>/skills/graph-memory-persona
cp -r skills/graph-memory/task <your-project-dir>/skills/graph-memory-task
cp -r skills/graph-memory-persona <your-project-dir>/skills/graph-memory-persona
cp -r skills/graph-memory-task <your-project-dir>/skills/graph-memory-task
# Option B: User-level (available for all projects)
cp -r skills/graph-memory ~/.openclaw/skills/graph-memory
cp -r skills/graph-memory/persona ~/.openclaw/skills/graph-memory-persona
cp -r skills/graph-memory/task ~/.openclaw/skills/graph-memory-task
cp -r skills/graph-memory-persona ~/.openclaw/skills/graph-memory-persona
cp -r skills/graph-memory-task ~/.openclaw/skills/graph-memory-task
```
### Step 4: Install Plugin (enables OpenClaw runtime to load the Tool)
@ -129,8 +129,8 @@ Copy the `skills/` directory to OpenClaw's skill directory:
```bash
cp -r skills/graph-memory ~/.agents/skills/graph-memory
cp -r skills/graph-memory/persona ~/.agents/skills/graph-memory-persona
cp -r skills/graph-memory/task ~/.agents/skills/graph-memory-task
cp -r skills/graph-memory-persona ~/.agents/skills/graph-memory-persona
cp -r skills/graph-memory-task ~/.agents/skills/graph-memory-task
```
---
@ -152,19 +152,23 @@ ts/
│ │ └── graph_memory_tool.ts # Tool implementation
│ └── tool_limiter.ts # Call rate limiter
├── bundled-skills/
│ └── graph-memory/ # Bundled Skill definitions
│ ├── SKILL.md
│ ├── persona/SKILL.md
│ └── task/SKILL.md
│ ├── graph-memory/ # Bundled Skill definitions
│ │ └── SKILL.md
│ ├── graph-memory-persona/
│ │ └── SKILL.md
│ └── graph-memory-task/
│ └── SKILL.md
├── package.json
├── tsconfig.json
└── openclaw.plugin.json # Plugin Manifest
skills/ # Standalone Skill definitions
└── graph-memory/
├── SKILL.md
├── persona/SKILL.md
└── task/SKILL.md
├── graph-memory/
│ └── SKILL.md
├── graph-memory-persona/
│ └── SKILL.md
└── graph-memory-task/
└── SKILL.md
```
---

View File

@ -1,7 +1,13 @@
{
"id": "graph-memory",
"name": "Graph Memory",
"kind": "memory",
"description": "让 AI 拥有真正的长期记忆能力 - 基于图数据库的记忆系统",
"skills": [
"bundled-skills/graph-memory",
"bundled-skills/graph-memory-persona",
"bundled-skills/graph-memory-task"
],
"configSchema": {
"type": "object",
"additionalProperties": false,

View File

@ -1,76 +1,5 @@
/**
* OpenClaw Plugin Entry Point
*
* Registers the GraphMemory tool with OpenClaw's plugin system.
* Uses @sinclair/typebox for parameter schema definition.
*/
import { Type } from '@sinclair/typebox';
import { GraphMemoryTool } from './runtime/core/tools/builtin/graph_memory_tool.js';
import { GraphMemoryToolSchema, createGraphMemoryTool } from './runtime/core/tools/builtin/graph_memory_tool.js';
// OpenClaw Tool Schema using TypeBox
const GraphMemoryToolSchema = Type.Object({
action: Type.String({
description: '记忆操作类型',
enum: [
'recall', 'commit', 'purge', 'introspect',
'persona_update', 'persona_clear',
'task_create', 'task_set_state', 'task_delete', 'task_link_info'
]
}),
params: Type.Object({
queryIntent: Type.Optional(Type.String({ description: '搜索意图' })),
seedEntities: Type.Optional(Type.Array(Type.String({ description: '实体' }), { description: '种子实体' })),
depth: Type.Optional(Type.Number({ description: '检索深度' })),
sessionFilter: Type.Optional(Type.String({ description: '会话ID过滤' })),
triplets: Type.Optional(Type.Array(
Type.Object({
subject: Type.String({ description: '主体' }),
relation: Type.String({ description: '关系' }),
object: Type.String({ description: '客体' }),
confidence: Type.Optional(Type.Number({ description: '置信度' }))
}, { description: '三元组' }),
{ description: '三元组数组' }
)),
sessionId: Type.Optional(Type.String({ description: '会话ID' })),
turnId: Type.Optional(Type.Number({ description: '轮次ID' })),
criteria: Type.Optional(Type.Object({
subject: Type.Optional(Type.String({ description: '主体' })),
target: Type.Optional(Type.String({ description: '客体' })),
relation: Type.Optional(Type.String({ description: '关系' })),
sessionId: Type.Optional(Type.String({ description: '会话ID' }))
}, { description: '删除条件' })),
mode: Type.Optional(Type.String({
enum: ['soft', 'hard', 'supersede'],
description: '删除模式'
})),
newRelation: Type.Optional(Type.Object({
relation: Type.String({ description: '关系' }),
target: Type.String({ description: '客体' })
}, { description: '新关系(supersede模式)' })),
attributes: Type.Optional(Type.Array(
Type.Object({
attribute: Type.String({ description: '属性名' }),
value: Type.String({ description: '属性值' })
}, { description: '属性' }),
{ description: '属性数组' }
)),
confirm: Type.Optional(Type.Boolean({ description: '确认清除' })),
task_id: Type.Optional(Type.String({ description: '任务ID' })),
description: Type.Optional(Type.String({ description: '任务描述' })),
state: Type.Optional(Type.String({ description: '任务状态' })),
info_nodes: Type.Optional(Type.Array(Type.String({ description: '节点' }), { description: '信息节点' })),
info_node: Type.Optional(Type.String({ description: '信息节点' })),
days: Type.Optional(Type.Number({ description: '归档天数' })),
dry_run: Type.Optional(Type.Boolean({ description: '仅预览不删除' }))
}, { description: '操作参数' })
});
/**
* Plugin entry point for OpenClaw.
*
* When loaded as an OpenClaw plugin, this function registers the GraphMemory tool.
* For standalone usage, import GraphMemoryTool directly.
*/
export default function registerGraphMemoryPlugin(api: {
registerTool: (tool: {
name: string;
@ -81,34 +10,6 @@ export default function registerGraphMemoryPlugin(api: {
}>;
}) => void;
}): void {
const tool = new GraphMemoryTool();
api.registerTool({
name: 'graph_memory',
description: '图记忆工具 - 让 AI 拥有真正的长期记忆能力。支持 recall(检索)、commit(写入)、purge(删除)、introspect(状态)、人设管理、任务管理',
parameters: GraphMemoryToolSchema,
async execute(_id: string, params: Record<string, unknown>): Promise<{
content: Array<{ type: 'text'; text: string }>;
}> {
const result = await tool.handler(params, {
toolCallId: _id,
workingDirectory: process.cwd(),
abortController: { signal: new AbortController().signal },
config: {},
logger: {
info: () => {},
warn: () => {},
error: () => {},
debug: () => {}
}
});
return {
content: [{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result)
}]
};
}
});
}
const tool = createGraphMemoryTool();
api.registerTool(tool);
}

View File

@ -1,167 +1,95 @@
import type { Tool, ToolCategory, PermissionLevel, ToolInputSchema, ToolExecutionContext, ToolOutput } from '../tool_interface';
import { GraphDatabase } from '../../graph_memory/graph_database';
import { MemoryService } from '../../graph_memory/memory_service';
import { ToolLimiter } from '../tool_limiter';
import { Type } from '@sinclair/typebox';
import type { Static } from '@sinclair/typebox';
import { GraphDatabase } from '../../graph_memory/graph_database.js';
import { MemoryService } from '../../graph_memory/memory_service.js';
import { ToolLimiter } from '../tool_limiter.js';
const GRAPH_MEMORY_TOOL_ID = 'builtin:graph_memory';
export const GraphMemoryToolSchema = Type.Object({
action: Type.String({
description: '记忆操作类型',
enum: [
'recall', 'commit', 'purge', 'introspect', 'archive', 'cleanup',
'persona_update', 'persona_clear',
'task_create', 'task_set_state', 'task_delete', 'task_link_info'
]
}),
params: Type.Object({
queryIntent: Type.Optional(Type.String({ description: '搜索意图' })),
seedEntities: Type.Optional(Type.Array(Type.String({ description: '实体' }), { description: '种子实体' })),
depth: Type.Optional(Type.Number({ description: '检索深度' })),
sessionFilter: Type.Optional(Type.String({ description: '会话ID过滤' })),
triplets: Type.Optional(Type.Array(
Type.Object({
subject: Type.String({ description: '主体' }),
relation: Type.String({ description: '关系' }),
object: Type.String({ description: '客体' }),
confidence: Type.Optional(Type.Number({ description: '置信度' }))
}, { description: '三元组' }),
{ description: '三元组数组' }
)),
sessionId: Type.Optional(Type.String({ description: '会话ID' })),
turnId: Type.Optional(Type.Number({ description: '轮次ID' })),
criteria: Type.Optional(Type.Object({
subject: Type.Optional(Type.String({ description: '主体' })),
target: Type.Optional(Type.String({ description: '客体' })),
relation: Type.Optional(Type.String({ description: '关系' })),
sessionId: Type.Optional(Type.String({ description: '会话ID' }))
}, { description: '删除条件' })),
mode: Type.Optional(Type.String({
enum: ['soft', 'hard', 'supersede'],
description: '删除模式'
})),
newRelation: Type.Optional(Type.Object({
relation: Type.String({ description: '关系' }),
target: Type.String({ description: '客体' })
}, { description: '新关系(supersede模式)' })),
attributes: Type.Optional(Type.Array(
Type.Object({
attribute: Type.String({ description: '属性名' }),
value: Type.String({ description: '属性值' })
}, { description: '属性' }),
{ description: '属性数组' }
)),
confirm: Type.Optional(Type.Boolean({ description: '确认清除' })),
task_id: Type.Optional(Type.String({ description: '任务ID' })),
description: Type.Optional(Type.String({ description: '任务描述' })),
state: Type.Optional(Type.String({ description: '任务状态' })),
info_nodes: Type.Optional(Type.Array(Type.String({ description: '节点' }), { description: '信息节点' })),
info_node: Type.Optional(Type.String({ description: '信息节点' })),
days: Type.Optional(Type.Number({ description: '归档天数' })),
dry_run: Type.Optional(Type.Boolean({ description: '仅预览不删除' }))
}, { description: '操作参数' })
});
export type GraphMemoryToolParams = Static<typeof GraphMemoryToolSchema>;
const GRAPH_MEMORY_TOOL_DESCRIPTION = `图记忆工具 - 让 AI 拥有真正的长期记忆能力
操作:
- recall: 检索记忆
- commit: 写入记忆
- commit: 写入记忆
- purge: 删除记忆
- introspect: 查看状态
- archive: 归档旧记忆
- cleanup: 清理无效数据
- persona_update/clear: 人设管理
- task_create/set_state/delete: 任务管理`;
- task_create/set_state/delete/link_info: 任务管理`;
export interface OpenClawToolResult {
content: Array<{ type: 'text'; text: string }>;
}
export function createGraphMemoryTool(dbPath?: string, sessionId?: string) {
const db = new GraphDatabase(dbPath, sessionId);
const service = new MemoryService(db);
const limiter = new ToolLimiter();
export class GraphMemoryTool implements Tool {
readonly id = GRAPH_MEMORY_TOOL_ID;
readonly name = 'GraphMemory';
readonly description = GRAPH_MEMORY_TOOL_DESCRIPTION;
readonly category: ToolCategory = 'analysis';
readonly permissionLevel: PermissionLevel = 'safe';
readonly inputSchema: ToolInputSchema = {
type: 'object',
properties: {
action: {
type: 'string',
enum: [
'recall', 'commit', 'purge', 'introspect', 'archive', 'cleanup',
'persona_update', 'persona_clear',
'task_create', 'task_set_state', 'task_delete', 'task_link_info'
],
description: '记忆操作类型'
},
params: {
type: 'object',
description: '操作参数',
properties: {
queryIntent: { type: 'string', description: '搜索意图' },
seedEntities: { type: 'array', items: { type: 'string', description: '实体' }, description: '种子实体' },
depth: { type: 'number', description: '检索深度' },
sessionFilter: { type: 'string', description: '会话ID过滤' },
triplets: {
type: 'array',
items: {
type: 'object',
description: '三元组',
properties: {
subject: { type: 'string', description: '主体' },
relation: { type: 'string', description: '关系' },
object: { type: 'string', description: '客体' },
confidence: { type: 'number', description: '置信度' }
}
},
description: '三元组数组'
},
sessionId: { type: 'string', description: '会话ID' },
turnId: { type: 'number', description: '轮次ID' },
criteria: {
type: 'object',
properties: {
subject: { type: 'string', description: '主体' },
target: { type: 'string', description: '客体' },
relation: { type: 'string', description: '关系' },
sessionId: { type: 'string', description: '会话ID' }
},
description: '删除条件'
},
mode: { type: 'string', enum: ['soft', 'hard', 'supersede'], description: '删除模式' },
newRelation: {
type: 'object',
properties: {
relation: { type: 'string', description: '关系' },
target: { type: 'string', description: '客体' }
},
description: '新关系(supersede模式)'
},
attributes: {
type: 'array',
items: {
type: 'object',
description: '属性',
properties: {
attribute: { type: 'string', description: '属性名' },
value: { type: 'string', description: '属性值' }
}
},
description: '属性数组'
},
confirm: { type: 'boolean', description: '确认清除' },
task_id: { type: 'string', description: '任务ID' },
description: { type: 'string', description: '任务描述' },
state: { type: 'string', description: '任务状态' },
info_nodes: { type: 'array', items: { type: 'string', description: '节点' }, description: '信息节点' },
info_node: { type: 'string', description: '信息节点' },
days: { type: 'number', description: '归档天数' },
dry_run: { type: 'boolean', description: '仅预览不删除' }
}
}
},
required: ['action', 'params']
};
private db: GraphDatabase;
private service: MemoryService;
private limiter: ToolLimiter;
constructor(dbPath?: string, sessionId?: string) {
this.db = new GraphDatabase(dbPath, sessionId);
this.service = new MemoryService(this.db);
this.limiter = new ToolLimiter();
}
async handler(params: Record<string, unknown>, _context: ToolExecutionContext): Promise<ToolOutput> {
const action = params.action as string;
const actionParams = params.params as Record<string, unknown>;
try {
const result = await this.executeAction(action, actionParams);
return JSON.stringify({ success: true, data: result }, null, 2);
} catch (error) {
return JSON.stringify({
success: false,
error: {
type: 'execution_error',
message: error instanceof Error ? error.message : String(error)
}
}, null, 2);
}
}
async execute(_toolCallId: string, params: Record<string, unknown>): Promise<OpenClawToolResult> {
const result = await this.handler(params, {
toolCallId: _toolCallId,
workingDirectory: process.cwd(),
abortController: { signal: new AbortController().signal },
config: {},
logger: { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} }
});
return {
content: [{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result)
}]
};
}
private async executeAction(action: string, params: Record<string, unknown>): Promise<unknown> {
const [allowed, reason] = this.limiter.canCall(action);
async function executeAction(action: string, params: Record<string, unknown>): Promise<unknown> {
const [allowed, reason] = limiter.canCall(action);
if (!allowed) {
throw new Error(reason);
}
this.limiter.recordCall(action);
limiter.recordCall(action);
switch (action) {
case 'recall':
return this.service.recall({
return service.recall({
queryIntent: params.queryIntent as string || '',
seedEntities: params.seedEntities as string[] | undefined,
depth: params.depth as number | undefined,
@ -169,79 +97,100 @@ export class GraphMemoryTool implements Tool {
});
case 'commit':
return this.service.commit({
return service.commit({
triplets: params.triplets as Array<{ subject: string; relation: string; object: string; confidence?: number }>,
sessionId: params.sessionId as string | undefined,
turnId: params.turnId as number | undefined
});
case 'purge':
return this.service.purge({
criteria: params.criteria as { subject?: string | undefined; target?: string | undefined; relation?: string | undefined; sessionId?: string | undefined } | undefined,
return service.purge({
criteria: params.criteria as { subject?: string; target?: string; relation?: string; sessionId?: string } | undefined,
mode: params.mode as 'soft' | 'hard' | 'supersede' | undefined,
newRelation: params.newRelation as { relation: string; target: string } | undefined
});
case 'introspect':
return this.service.introspect();
return service.introspect();
case 'persona_update':
return this.service.updatePersona({
return service.updatePersona({
attributes: params.attributes as Array<{ attribute: string; value: string }>,
mode: params.mode as 'merge' | 'replace'
});
case 'persona_clear':
return this.service.clearPersona({
return service.clearPersona({
confirm: params.confirm as boolean
});
case 'task_create':
return this.service.createTask({
return service.createTask({
task_id: params.task_id as string,
description: params.description as string,
info_nodes: params.info_nodes as string[] | undefined
});
case 'task_set_state':
return this.service.setTaskState({
return service.setTaskState({
task_id: params.task_id as string,
state: params.state as string
});
case 'task_delete':
return this.service.deleteTask({
return service.deleteTask({
task_id: params.task_id as string
});
case 'task_link_info':
return this.service.linkInfoToTask({
return service.linkInfoToTask({
task_id: params.task_id as string,
info_node: params.info_node as string
});
case 'archive':
return this.service.archive(params.days as number | undefined);
return service.archive(params.days as number | undefined);
case 'cleanup':
return this.service.cleanup(params.dry_run as boolean | undefined);
return service.cleanup(params.dry_run as boolean | undefined);
default:
throw new Error(`Unknown action: ${action}`);
}
}
resetLimiter(): void {
this.limiter.reset();
}
return {
name: 'graph_memory',
description: GRAPH_MEMORY_TOOL_DESCRIPTION,
parameters: GraphMemoryToolSchema,
getLimiterSummary(): string {
return this.limiter.getSummary();
}
async execute(_toolCallId: string, params: Record<string, unknown>): Promise<{
content: Array<{ type: 'text'; text: string }>;
}> {
const action = params.action as string;
const actionParams = params.params as Record<string, unknown>;
try {
const result = await executeAction(action, actionParams);
return {
content: [{ type: 'text', text: JSON.stringify({ success: true, data: result }) }]
};
} catch (error) {
return {
content: [{
type: 'text',
text: JSON.stringify({
success: false,
error: {
type: 'execution_error',
message: error instanceof Error ? error.message : String(error)
}
})
}]
};
}
}
};
}
export function createGraphMemoryTool(dbPath?: string, sessionId?: string): GraphMemoryTool {
return new GraphMemoryTool(dbPath, sessionId);
}
export { ToolLimiter } from '../tool_limiter';
export { ToolLimiter } from '../tool_limiter.js';

View File

@ -1,59 +0,0 @@
export type ToolCategory = 'file' | 'code' | 'search' | 'execute' | 'network' | 'analysis' | 'generation' | 'communication' | 'mcp' | 'custom';
export type PermissionLevel = 'safe' | 'moderate' | 'dangerous' | 'restricted';
export type SchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object';
export interface SchemaProperty {
type: SchemaType;
description: string;
enum?: string[];
minimum?: number;
maximum?: number;
minLength?: number;
maxLength?: number;
pattern?: string;
default?: unknown;
examples?: unknown[];
items?: SchemaProperty;
properties?: Record<string, SchemaProperty>;
}
export interface ToolInputSchema {
type: 'object';
properties: Record<string, SchemaProperty>;
required?: string[];
additionalProperties?: boolean;
}
export interface ToolOutputSchema {
type: 'object';
properties: Record<string, SchemaProperty>;
format?: 'json' | 'text' | 'markdown' | 'binary';
maxSize?: number;
maxLines?: number;
}
export type ToolInput = Record<string, unknown>;
export type ToolOutput = string | Record<string, unknown> | void;
export interface ToolExecutionContext {
toolCallId: string;
workingDirectory: string;
abortController: { signal: AbortSignal };
config: { timeout?: number };
logger: { info: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void; debug: (...args: unknown[]) => void };
}
export type ToolHandler = (params: ToolInput, context: ToolExecutionContext) => Promise<ToolOutput>;
export interface Tool {
readonly id: string;
readonly name: string;
readonly description: string;
readonly category: ToolCategory;
readonly inputSchema: ToolInputSchema;
readonly outputSchema?: ToolOutputSchema;
readonly handler: ToolHandler;
readonly permissionLevel: PermissionLevel;
}