feat: MEMORY.md 访问保护、格式简化及预存问题修复
MEMORY.md 保护: 新建 file-guard.ts 共享守卫模块; read_file/write_file/search_files/run_command 禁止访问根目录 MEMORY.md; permissions.ts 增加 deniedPatterns 深度防御。格式简化: 元数据从 # 注释改为 > 引用语法; 校验规则从 6 条简化为 3 条; context-builder extractContent 适配。预存问题: 修复路径遍历前缀碰撞漏洞; 移除 browser_extract 内容截断; SearXNG 配置实时读取; web_search/web_fetch 移除截断; 侧边栏动态获取工具列表; 版本号 0.1.1
This commit is contained in:
@@ -38,34 +38,20 @@ const REQUIRED_FILES = ['SOUL.md', 'AGENTS.md', 'MEMORY.md', 'USERS.md'] as cons
|
||||
const AUTO_CREATED_DIRS = ['logs', 'traces', '.metona'] as const;
|
||||
|
||||
const MEMORY_TEMPLATE = `# MEMORY.md — AI 持久记忆
|
||||
#
|
||||
# 格式版本: 1.0
|
||||
# 创建时间: __CREATED_AT__
|
||||
# 最后更新: __UPDATED_AT__
|
||||
# 工作空间: __WORKSPACE_PATH__
|
||||
#
|
||||
# 此文件由 Metona Agent 自动维护,用户可手动编辑。
|
||||
# 格式规范详见文档,Agent 写入时会自动校验格式。
|
||||
|
||||
> 创建时间: __CREATED_AT__
|
||||
> 最后更新: __UPDATED_AT__
|
||||
> 工作空间: __WORKSPACE_PATH__
|
||||
|
||||
## 用户偏好
|
||||
# 格式: - [类别] 内容描述
|
||||
# 示例: - [沟通风格] 用户喜欢简洁的回答
|
||||
|
||||
## 项目上下文
|
||||
# 格式: - [项目名] 关键信息
|
||||
# 示例: - [MyApp] 技术栈: React + TypeScript
|
||||
|
||||
## 重要决策
|
||||
# 格式: - YYYY-MM-DD: 决策内容
|
||||
# 示例: - 2026-06-25: 选择 sql.js 作为数据库方案
|
||||
|
||||
## 待办事项
|
||||
# 格式: - [状态] 任务描述 (状态: pending/done/cancelled)
|
||||
# 示例: - [pending] 实现用户登录功能
|
||||
|
||||
## 已知问题
|
||||
# 格式: - 问题描述 | 影响范围 | 解决方案
|
||||
# 示例: - 首次加载慢 | 启动 | 预加载优化
|
||||
`;
|
||||
|
||||
// ===== 服务类 =====
|
||||
@@ -161,10 +147,9 @@ export class WorkspaceService {
|
||||
let content = readFileSync(memoryPath, 'utf-8');
|
||||
const now = new Date().toISOString();
|
||||
|
||||
// 替换最后更新时间戳
|
||||
content = content.replace(
|
||||
/# 最后更新: .*/,
|
||||
`# 最后更新: ${now}`,
|
||||
/> 最后更新: .*/,
|
||||
`> 最后更新: ${now}`,
|
||||
);
|
||||
|
||||
writeFileSync(memoryPath, content, 'utf-8');
|
||||
@@ -201,8 +186,8 @@ export class WorkspaceService {
|
||||
|
||||
// 更新时间戳
|
||||
content = content.replace(
|
||||
/# 最后更新: .*/,
|
||||
`# 最后更新: ${new Date().toISOString()}`,
|
||||
/> 最后更新: .*/,
|
||||
`> 最后更新: ${new Date().toISOString()}`,
|
||||
);
|
||||
|
||||
writeFileSync(memoryPath, content, 'utf-8');
|
||||
@@ -211,17 +196,12 @@ export class WorkspaceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 MEMORY.md 格式(6 条规则)
|
||||
* 校验 MEMORY.md 格式
|
||||
*
|
||||
* 规则:
|
||||
* 1. 元数据头 — 必须包含 # 格式版本、# 创建时间、# 最后更新、# 工作空间
|
||||
* 2. 分区结构 — 必须包含 ## 用户偏好、## 项目上下文、## 重要决策
|
||||
* 3. 条目前缀 — 每个条目必须以 - 开头,后跟 [类别/标签]
|
||||
* 4. 日期格式 — 决策条目必须使用 YYYY-MM-DD
|
||||
* 5. 状态标记 — 待办事项必须包含 [pending/done/cancelled]
|
||||
* 6. 时间戳更新 — 每次写入时自动更新 # 最后更新 时间戳
|
||||
*
|
||||
* @see docs/MetonaAI-Desktop 架构与交互设计.html — MEMORY.md 格式校验规则
|
||||
* 简化规则:
|
||||
* 1. 元数据头 — 用 > 引用语法,包含创建时间、最后更新、工作空间
|
||||
* 2. 分区结构 — 包含用户偏好、项目上下文、重要决策(其余可选)
|
||||
* 3. 时间戳自动更新
|
||||
*/
|
||||
validateMemoryFormat(): boolean {
|
||||
const memoryPath = join(this.workspacePath, 'MEMORY.md');
|
||||
@@ -230,33 +210,35 @@ export class WorkspaceService {
|
||||
let content = readFileSync(memoryPath, 'utf-8');
|
||||
let modified = false;
|
||||
|
||||
// 规则 1: 元数据头
|
||||
const requiredMeta = ['# 格式版本', '# 创建时间', '# 最后更新', '# 工作空间'];
|
||||
// 规则 1: 元数据头(> 引用语法)
|
||||
const requiredMeta: Array<{ key: string; line: string }> = [
|
||||
{ key: '> 创建时间', line: `> 创建时间: ${new Date().toISOString()}` },
|
||||
{ key: '> 最后更新', line: `> 最后更新: ${new Date().toISOString()}` },
|
||||
{ key: '> 工作空间', line: `> 工作空间: ${this.workspacePath}` },
|
||||
];
|
||||
for (const meta of requiredMeta) {
|
||||
if (!content.includes(meta)) {
|
||||
// 自动补充缺失的元数据
|
||||
const metaLine = meta === '# 工作空间'
|
||||
? `# 工作空间: ${this.workspacePath}`
|
||||
: `${meta}: ${new Date().toISOString()}`;
|
||||
content = content.replace(/^# MEMORY/m, `# MEMORY\n#\n# ${metaLine.replace('# ', '')}`);
|
||||
if (!content.includes(meta.key)) {
|
||||
// 在标题后插入元数据
|
||||
const titleEnd = content.indexOf('\n', content.indexOf('# MEMORY')) + 1;
|
||||
content = content.slice(0, titleEnd) + meta.line + '\n' + content.slice(titleEnd);
|
||||
modified = true;
|
||||
log.info(`MEMORY.md: auto-added missing metadata "${meta}"`);
|
||||
log.info(`MEMORY.md: auto-added missing metadata "${meta.key}"`);
|
||||
}
|
||||
}
|
||||
|
||||
// 规则 2: 分区结构
|
||||
// 规则 2: 核心分区
|
||||
const requiredSections = ['## 用户偏好', '## 项目上下文', '## 重要决策'];
|
||||
for (const section of requiredSections) {
|
||||
if (!content.includes(section)) {
|
||||
content += `\n${section}\n# 格式: - [类别] 内容描述\n`;
|
||||
content += `\n${section}\n`;
|
||||
modified = true;
|
||||
log.info(`MEMORY.md: auto-added missing section "${section}"`);
|
||||
}
|
||||
}
|
||||
|
||||
// 规则 6: 时间戳更新
|
||||
// 规则 3: 时间戳更新
|
||||
const now = new Date().toISOString();
|
||||
content = content.replace(/# 最后更新: .*/, `# 最后更新: ${now}`);
|
||||
content = content.replace(/> 最后更新: .*/, `> 最后更新: ${now}`);
|
||||
|
||||
if (modified) {
|
||||
writeFileSync(memoryPath, content, 'utf-8');
|
||||
|
||||
Reference in New Issue
Block a user