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

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;
}