mirror of
https://github.com/TencentCloud/TencentDB-Agent-Memory
synced 2026-07-10 12:34:27 +00:00
fix(scene): adapt memory prompts to dialogue language (#147)
Signed-off-by: Siren.W <sirenexcelsior@gmail.com>
This commit is contained in:
@@ -32,7 +32,13 @@ export interface PersonaPromptResult {
|
|||||||
|
|
||||||
const PERSONA_SYSTEM_PROMPT = `# 🧬 Persona Architect - Incremental Evolution Protocol
|
const PERSONA_SYSTEM_PROMPT = `# 🧬 Persona Architect - Incremental Evolution Protocol
|
||||||
|
|
||||||
**输出语言**:\`persona.md\` 的所有自然语言内容(Archetype、基本信息、Chapter 1-4 正文等)使用与变化场景内容相同的语言;Markdown 语法、标签格式、文件名 \`persona.md\` 保持英文。模板里 Chapter 标识保留作骨架,非中文输出时请改用目标语言的对照说明。
|
**Output language contract**:
|
||||||
|
- Detect the dominant language from the changed scene content.
|
||||||
|
- \`persona.md\` natural-language content, profile headings, and narrative sections must use that language.
|
||||||
|
- For English scene content, output English persona headings and English body text.
|
||||||
|
- For non-Chinese scene content, do not emit Chinese persona headings.
|
||||||
|
- If the language is ambiguous, default to English.
|
||||||
|
- Keep Markdown syntax, file name \`persona.md\`, tool names, and structural markers in English.
|
||||||
|
|
||||||
请你结合已有的 persona.md 和新增/变化的 block 信息深度分析,然后使用文件工具将结果写入 \`persona.md\` 文件。
|
请你结合已有的 persona.md 和新增/变化的 block 信息深度分析,然后使用文件工具将结果写入 \`persona.md\` 文件。
|
||||||
|
|
||||||
@@ -90,41 +96,41 @@ const PERSONA_SYSTEM_PROMPT = `# 🧬 Persona Architect - Incremental Evolution
|
|||||||
\`\`\`\`markdown
|
\`\`\`\`markdown
|
||||||
# User Narrative Profile
|
# User Narrative Profile
|
||||||
|
|
||||||
> **Archetype (核心原型)**: [一句话定义。例如:一位在现实重力下挣扎,但试图通过技术构建理想国的"务实理想主义者"。]
|
> **Archetype**: [Define the user's core narrative archetype in one sentence.]
|
||||||
|
|
||||||
> **基本信息**
|
> **Basic Information**
|
||||||
(用户的基本信息,如年龄、性别、职业等,更新时若有冲突则覆盖,不冲突尽量叠加)
|
(Basic user facts such as age, gender, occupation, or location. Overwrite only when a conflict is resolved; otherwise merge compatible facts.)
|
||||||
-
|
-
|
||||||
-
|
-
|
||||||
|
|
||||||
> **长期偏好**
|
> **Long-term Preferences**
|
||||||
(你观察到的用户最稳定且可复用的偏好)
|
(The user's most stable and reusable preferences observed from scene evidence.)
|
||||||
-
|
-
|
||||||
-
|
-
|
||||||
|
|
||||||
## 📖 Chapter 1: Context & Current State (全景语境)
|
## 📖 Chapter 1: Context & Current State
|
||||||
*(将基础事实与当前状态融合,写成一段连贯的背景介绍)*
|
*(Merge basic facts and current state into a coherent background.)*
|
||||||
|
|
||||||
**[这里写连贯描述,区别较大的时候可以分点阐述]**
|
**[Write a coherent description. Use short bullets only when the facts are clearly distinct.]**
|
||||||
|
|
||||||
## 🎨 Chapter 2: The Texture of Life (生活的肌理)
|
## 🎨 Chapter 2: The Texture of Life
|
||||||
*(将兴趣、消费、生活习惯串联起来,展示生活品味)*
|
*(Connect interests, consumption patterns, and daily habits to show the user's lived texture.)*
|
||||||
|
|
||||||
**[这里写连贯的描述,重点在于"兴趣/偏好"和"品味"的统一性,区别较大的时候可以分点阐述]**
|
**[Write a coherent description, focusing on the unity of interests, preferences, and taste. Use short bullets only when needed.]**
|
||||||
|
|
||||||
## 🤖 Chapter 3: Interaction & Cognitive Protocol (交互与认知协议)
|
## 🤖 Chapter 3: Interaction & Cognitive Protocol
|
||||||
*(这是 Main Agent 的行动指南。为了实用,这里保持半结构化,但要解释"为什么")*
|
*(This is the Main Agent's action guide. Keep it semi-structured for utility, but explain why each guidance point matters.)*
|
||||||
|
|
||||||
### 3.1 沟通策略 (How to Speak)
|
### 3.1 How to Speak
|
||||||
### 3.2 决策逻辑 (How to Think)
|
### 3.2 How to Think
|
||||||
|
|
||||||
## 🧩 Chapter 4: Deep Insights & Evolution (深层洞察与演变)
|
## 🧩 Chapter 4: Deep Insights & Evolution
|
||||||
*(人类学观察笔记)*
|
*(Anthropological observation notes.)*
|
||||||
|
|
||||||
* **矛盾统一性**: [描述用户身上看似冲突但实则合理的特质]。
|
* **Productive Contradictions**: [Describe traits that seem conflicting but are coherent in context.]
|
||||||
* **演变轨迹**: [可加上时间,分为多点,描述用户最近发生的变化]。
|
* **Evolution Trajectory**: [Optionally include dated points describing recent meaningful changes.]
|
||||||
* **涌现特征**: 提炼 3-7 个最核心的特质标签,每个标签单独一行并附上简短注释(10-15字)
|
* **Emergent Traits**: Extract 3-7 core trait tags, one per line, each with a short note.
|
||||||
- \`TagName\` - 简短注释说明
|
- \`TagName\` - Short note
|
||||||
\`\`\`\`
|
\`\`\`\`
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -170,7 +176,7 @@ export function buildPersonaPrompt(params: PersonaPromptParams): PersonaPromptRe
|
|||||||
`面对变化场景,自主判断处理方式:强化(佐证已有洞察)/ 补充(新维度)/ 修正(矛盾)/ 重构(结构调整)/ 不改(无有用新增内容)。\n`
|
`面对变化场景,自主判断处理方式:强化(佐证已有洞察)/ 补充(新维度)/ 修正(矛盾)/ 重构(结构调整)/ 不改(无有用新增内容)。\n`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const userPrompt = `**输出语言**:\`persona.md\` 使用下方变化场景内容的主导语言。
|
const userPrompt = `**Output language**: \`persona.md\` headings and body text must use the dominant language of the changed scene content below. For English scene content, use English persona headings.
|
||||||
|
|
||||||
**⏰ 更新时间**: ${currentTime}
|
**⏰ 更新时间**: ${currentTime}
|
||||||
**模式**: ${modeLabel}
|
**模式**: ${modeLabel}
|
||||||
|
|||||||
@@ -44,7 +44,13 @@ export interface SceneExtractionPromptResult {
|
|||||||
function buildSceneSystemPrompt(maxScenes: number): string {
|
function buildSceneSystemPrompt(maxScenes: number): string {
|
||||||
return `# Memory Consolidation Architect
|
return `# Memory Consolidation Architect
|
||||||
|
|
||||||
**输出语言**:\`.md\` 场景文件的所有自然语言内容(文件名、章节标题、正文)使用与"New Memories List"中记忆相同的语言;META 字段名(created/updated/summary/heat)和 \`[DELETED]\` 等标记保持英文。模板中给出的中文章节标题(\`## 用户核心特征\` 等)作为结构骨架——非中文输出时请用目标语言的等价表达替换。
|
**Output language contract**:
|
||||||
|
- Detect the dominant language from "New Memories List".
|
||||||
|
- Scene file names, Markdown section headings, and natural-language body text must use that language.
|
||||||
|
- For English memories, output English file names and English section headings.
|
||||||
|
- For non-Chinese memories, do not emit Chinese file names or Chinese section headings.
|
||||||
|
- If the language is ambiguous, default to English.
|
||||||
|
- Keep META field names (\`created\`, \`updated\`, \`summary\`, \`heat\`) and system markers such as \`[DELETED]\` in English.
|
||||||
|
|
||||||
## 角色定义 (Role Definition)
|
## 角色定义 (Role Definition)
|
||||||
你是记忆整合架构师。你的目标是为用户构建一个"数字第二大脑"。你不仅仅是在记录数据,你更像是一位人类学家和心理学家,负责分析原始记忆,从中提取核心特征、捕捉隐性信号,并构建不断演变的叙事。
|
你是记忆整合架构师。你的目标是为用户构建一个"数字第二大脑"。你不仅仅是在记录数据,你更像是一位人类学家和心理学家,负责分析原始记忆,从中提取核心特征、捕捉隐性信号,并构建不断演变的叙事。
|
||||||
@@ -73,7 +79,7 @@ function buildSceneSystemPrompt(maxScenes: number): string {
|
|||||||
**⚠️ 场景文件数量上限:${maxScenes} 个。处理完成后目录中的场景文件数量必须严格小于此上限。**
|
**⚠️ 场景文件数量上限:${maxScenes} 个。处理完成后目录中的场景文件数量必须严格小于此上限。**
|
||||||
|
|
||||||
## ⛔ 文件操作约束(必须严格遵守)
|
## ⛔ 文件操作约束(必须严格遵守)
|
||||||
1. **所有文件操作使用相对文件名**(如 \`技术研究-Rust学习.md\`),当前工作目录已设为场景文件目录
|
1. **所有文件操作使用相对文件名**(如 \`Engineering-Practice.md\`),当前工作目录已设为场景文件目录
|
||||||
2. **read 只能读取用户消息中"已有场景文件清单"列出的文件**,禁止猜测或编造不在清单中的文件名
|
2. **read 只能读取用户消息中"已有场景文件清单"列出的文件**,禁止猜测或编造不在清单中的文件名
|
||||||
3. **创建新场景文件时**,使用 **write** 工具。参数:\`path\`=文件名, \`content\`=完整内容
|
3. **创建新场景文件时**,使用 **write** 工具。参数:\`path\`=文件名, \`content\`=完整内容
|
||||||
4. **局部更新场景文件**:使用 **edit** 工具。参数:\`path\`=文件名, \`edits\`=[{\`oldText\`: 旧内容, \`newText\`: 新内容}]。对于大范围重写或结构性变更,建议使用 **read** + **write** 整体重写。
|
4. **局部更新场景文件**:使用 **edit** 工具。参数:\`path\`=文件名, \`edits\`=[{\`oldText\`: 旧内容, \`newText\`: 新内容}]。对于大范围重写或结构性变更,建议使用 **read** + **write** 整体重写。
|
||||||
@@ -85,17 +91,19 @@ function buildSceneSystemPrompt(maxScenes: number): string {
|
|||||||
|
|
||||||
为保证下游工具(场景导航、健康检查、对象存储同步等)能正确解析路径引用,**新建文件**或 **MERGE 后的目标文件**必须遵守以下命名规则:
|
为保证下游工具(场景导航、健康检查、对象存储同步等)能正确解析路径引用,**新建文件**或 **MERGE 后的目标文件**必须遵守以下命名规则:
|
||||||
|
|
||||||
- **允许字符**:英文字母、数字、CJK 中日韩文字、短横线 \`-\`、下划线 \`_\`、点号 \`.\`
|
- **允许字符**:Unicode letters(包括 Latin/CJK/Cyrillic 等)、数字、短横线 \`-\`、下划线 \`_\`、点号 \`.\`
|
||||||
- **必须以 \`.md\` 结尾**(小写)
|
- **必须以 \`.md\` 结尾**(小写)
|
||||||
- **❌ 禁止包含**:空格、全角空格、引号、括号 \`( ) [ ] { }\`、斜杠 \`/ \\\`、冒号 \`:\`、分号 \`;\`、问号 \`?\`、感叹号 \`!\`、星号 \`*\`、竖线 \`|\`、其他标点
|
- **❌ 禁止包含**:空格、全角空格、引号、括号 \`( ) [ ] { }\`、斜杠 \`/ \\\`、冒号 \`:\`、分号 \`;\`、问号 \`?\`、感叹号 \`!\`、星号 \`*\`、竖线 \`|\`、其他标点
|
||||||
- **多词分隔**:使用 \`-\`(短横线)连接,不要用空格
|
- **多词分隔**:使用 \`-\`(短横线)连接,不要用空格
|
||||||
- **更新现有文件**时,沿用清单中给出的文件名,不要改名
|
- **更新现有文件**时,沿用清单中给出的文件名,不要改名
|
||||||
|
- **英文记忆的新建文件名**必须使用英文标题,并用短横线连接单词
|
||||||
|
|
||||||
✅ 正确示例:
|
✅ 正确示例:
|
||||||
- \`Daily-Rhythm-in-Shanghai.md\`
|
- \`Daily-Rhythm-in-Shanghai.md\`
|
||||||
- \`日常生活-健康管理.md\`
|
- \`日常生活-健康管理.md\`
|
||||||
- \`技术研究-Rust学习.md\`
|
- \`技术研究-Rust学习.md\`
|
||||||
- \`Coffee-Yirgacheffe.md\`
|
- \`Coffee-Yirgacheffe.md\`
|
||||||
|
- \`Work-and-Engineering-Practice.md\`
|
||||||
|
|
||||||
❌ 错误示例(每次都会触发工程兜底重命名):
|
❌ 错误示例(每次都会触发工程兜底重命名):
|
||||||
- \`Daily Rhythm in Shanghai.md\`(含空格)
|
- \`Daily Rhythm in Shanghai.md\`(含空格)
|
||||||
@@ -167,12 +175,12 @@ function buildSceneSystemPrompt(maxScenes: number): string {
|
|||||||
|
|
||||||
### 阶段 3:撰写与合成(核心任务)
|
### 阶段 3:撰写与合成(核心任务)
|
||||||
深度整合: 严禁简单的文本追加。你必须结合上下文(基于摘要或提供的原始内容)重写叙事,将新信息自然地融入其中。
|
深度整合: 严禁简单的文本追加。你必须结合上下文(基于摘要或提供的原始内容)重写叙事,将新信息自然地融入其中。
|
||||||
隐性推断: 寻找用户 没说出口 的信息。更新"隐性信号"部分。
|
隐性推断: 寻找用户 没说出口 的信息。更新 "Implicit Signals" section, or its equivalent in the dialogue language.
|
||||||
冲突检测: 如果新记忆与旧记忆相矛盾,将其记录在"演变轨迹"或"待确认/矛盾点"中。
|
冲突检测: 如果新记忆与旧记忆相矛盾,将其记录在 "Evolution Trajectory" 或 "Pending Confirmation / Contradictions" section, or their equivalents in the dialogue language.
|
||||||
|
|
||||||
### 撰写准则 (严格遵守)
|
### 撰写准则 (严格遵守)
|
||||||
核心部分禁止列表: "用户核心特征"和"核心叙事"必须是连贯的段落,信息要连贯,可以分段。
|
核心部分禁止列表: "User Core Traits" and "Core Narrative" sections, or their equivalents in the dialogue language, must be coherent paragraphs. 信息要连贯,可以分段。
|
||||||
叙事弧线: "核心叙事"必须遵循故事结构(情境 -> 行动 -> 结果)。
|
叙事弧线: "Core Narrative" section, or its equivalent in the dialogue language, must follow a story structure(Trigger -> Action -> Result)。
|
||||||
|
|
||||||
### 热度管理 (Heat Management):
|
### 热度管理 (Heat Management):
|
||||||
新建 Block: heat: 1
|
新建 Block: heat: 1
|
||||||
@@ -185,7 +193,7 @@ function buildSceneSystemPrompt(maxScenes: number): string {
|
|||||||
|
|
||||||
请你参考这个模板输出 .md 文件的内容或基于已有md进行更新,每个md控制在1500字符内。不要把模板本身放在 Markdown 代码块中,只需直接输出要写入文件的原始文本。
|
请你参考这个模板输出 .md 文件的内容或基于已有md进行更新,每个md控制在1500字符内。不要把模板本身放在 Markdown 代码块中,只需直接输出要写入文件的原始文本。
|
||||||
|
|
||||||
> 模板中的中文章节标题(\`## 用户核心特征\` 等)和示例文本仅作为**结构骨架**参考;**实际章节标题与正文必须按上述输出语言书写**(例如英文场景:\`## User Core Traits\`、\`## User Preferences\`、\`## Implicit Signals\`、\`## Core Narrative\` 等)。
|
> The section headings below are English fallback headings. Actual section headings and body text must follow the output language contract above. For English memories, keep English headings such as \`## User Core Traits\`, \`## User Preferences\`, \`## Implicit Signals\`, and \`## Core Narrative\`.
|
||||||
|
|
||||||
\`\`\`markdown
|
\`\`\`markdown
|
||||||
-----META-START-----
|
-----META-START-----
|
||||||
@@ -195,38 +203,38 @@ summary: [30-40 words concise summary for indexing]
|
|||||||
heat: [Integer]
|
heat: [Integer]
|
||||||
-----META-END-----
|
-----META-END-----
|
||||||
|
|
||||||
## 用户基础信息
|
## User Basic Information
|
||||||
[可为空,如果没有可不写这节,可按照需求添加更多点,合并和更新方式尽量叠加,有冲突则覆盖]
|
[Optional. Omit this section if there is no reliable basic information. Merge compatible facts and overwrite only when a conflict is resolved.]
|
||||||
-姓名:
|
- Name:
|
||||||
-职业:
|
- Occupation:
|
||||||
-居住地:
|
- Location:
|
||||||
- ……
|
- ...
|
||||||
|
|
||||||
## 用户核心特征
|
## User Core Traits
|
||||||
[这里不是列表!是一段连贯的描述。你细心推断出来最核心的用户特征,宁缺毋滥,**控制在100字以内**]
|
[Not a list. Write one coherent paragraph about the most important inferred user traits. Be selective and keep it concise, within 100 words.]
|
||||||
[示例: 用户在后端开发方面表现出对 Python 的强烈偏好,特别是异步框架。近期(2026-02)开始关注 Rust 的所有权机制,这表明用户有向系统级编程转型的意图。]
|
[Example: The user shows a strong preference for Python backend development, especially async frameworks. Recently (2026-02), they started focusing on Rust ownership, suggesting an interest in systems-level programming.]
|
||||||
|
|
||||||
## 用户偏好
|
## User Preferences
|
||||||
[这里可以是列表!**如果没有可以为不写这节**,记录用户明确的偏好信息(显性偏好),注意不要重复信息,不要流水账,偏好要可复用,更新时可以动态整合甚至重写]
|
[A list is allowed. Omit this section if there is no reliable preference. Record explicit, reusable preferences without duplication or daily logs. Dynamically merge or rewrite when updating.]
|
||||||
[示例:用户喜欢吃苹果]
|
[Example: The user likes apples.]
|
||||||
|
|
||||||
## 隐性信号
|
## Implicit Signals
|
||||||
[这是给人类学家看的,记录那些"没明说但很重要"的事,和显性偏好不一样,一定是你推断出来的,需要深思熟虑后再生成,可以为空,宁缺毋滥。你可以随时更新/删除/修改这里的信息]
|
[Anthropologist notes: record important signals that were not stated directly. These must be thoughtful inferences, not explicit preferences. This section can be empty; prefer omission over weak speculation. Update, delete, or rewrite as evidence changes.]
|
||||||
|
|
||||||
## 核心叙事
|
## Core Narrative
|
||||||
[这里不是列表!是一段连贯的描述,**控制在400字以内**,注意不要重复信息,不要流水账,可以动态整合甚至重写]
|
[Not a list. Write one coherent narrative within 400 words. Avoid duplication and daily logs. Dynamically merge or rewrite when updating.]
|
||||||
*(这里记录连贯的故事,必须包含 Trigger -> Action -> Result)*
|
*(Record a coherent story that must include Trigger -> Action -> Result.)*
|
||||||
|
|
||||||
[ 示例:本周用户主要集中在后端重构上。初期因为旧代码的耦合度高感到沮丧(**情绪点**),但他拒绝了"打补丁"的建议,坚持进行彻底解耦(**决策点**)。他在此过程中频繁查阅架构设计模式,表现出对"代码洁癖"的执着。]
|
[Example: This week the user focused on backend refactoring. They initially felt frustrated by tight coupling in legacy code, but rejected quick patches and insisted on deeper decoupling. During the process, they repeatedly consulted architecture patterns, showing a strong preference for clean code.]
|
||||||
|
|
||||||
|
|
||||||
## 演变轨迹
|
## Evolution Trajectory
|
||||||
> [注意] 可以为空,仅记录【用户偏好/性格/重大观念】转变,不记录琐碎、日常更新。当发生冲突时,不要直接覆盖,要记录变化轨迹。
|
> [Note] This can be empty. Only record changes in preferences, personality, or major beliefs. Do not record trivial daily updates. When conflicts occur, preserve the change trajectory instead of overwriting directly.
|
||||||
- [2026-01-10]: 从 "反对加班" 转向 "接受弹性工作",原因:创业压力(记忆ID: #987)
|
- [2026-01-10]: Shifted from "opposes overtime" to "accepts flexible work" due to startup pressure (memory ID: #987)
|
||||||
|
|
||||||
|
|
||||||
## 待确认/矛盾点
|
## Pending Confirmation / Contradictions
|
||||||
- [记录当前无法整合的矛盾信息,等待未来记忆澄清]
|
- [Record contradictions that cannot yet be integrated and should wait for future memories to clarify.]
|
||||||
|
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
@@ -272,7 +280,7 @@ export function buildSceneExtractionPrompt(params: SceneExtractionPromptParams):
|
|||||||
? `### 📁 已有场景文件清单(仅以下文件可 read)\n${existingSceneFiles.map((f) => `- \`${f}\``).join("\n")}\n`
|
? `### 📁 已有场景文件清单(仅以下文件可 read)\n${existingSceneFiles.map((f) => `- \`${f}\``).join("\n")}\n`
|
||||||
: `### 📁 已有场景文件清单\n(当前无已有场景文件)\n`;
|
: `### 📁 已有场景文件清单\n(当前无已有场景文件)\n`;
|
||||||
|
|
||||||
const userPrompt = `**输出语言**:场景文件内容使用下方 New Memories List 中记忆的主导语言。
|
const userPrompt = `**Output language**: Scene file names, section headings, and body text must use the dominant language in the New Memories List below. For English memories, use English memory titles and English headings.
|
||||||
${warningSection}
|
${warningSection}
|
||||||
### 1️⃣ New Memories List
|
### 1️⃣ New Memories List
|
||||||
${memoriesJson}
|
${memoriesJson}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import path from "node:path";
|
|||||||
* - Falls back to `"scene"` if the stem becomes empty.
|
* - Falls back to `"scene"` if the stem becomes empty.
|
||||||
*
|
*
|
||||||
* Allowed character set after normalization (informally):
|
* Allowed character set after normalization (informally):
|
||||||
* ASCII alphanumerics, CJK ideographs, hyphen, underscore, dot.
|
* Unicode letters/numbers, hyphen, underscore, dot.
|
||||||
*
|
*
|
||||||
* Examples:
|
* Examples:
|
||||||
* "Daily Rhythm in Shanghai.md" → "Daily-Rhythm-in-Shanghai.md"
|
* "Daily Rhythm in Shanghai.md" → "Daily-Rhythm-in-Shanghai.md"
|
||||||
@@ -55,7 +55,7 @@ export function normalizeSceneFilename(name: string): string {
|
|||||||
// Replace whitespace runs (incl. NBSP, full-width space) with `-`
|
// Replace whitespace runs (incl. NBSP, full-width space) with `-`
|
||||||
.replace(/[\s\u00A0\u3000]+/g, "-")
|
.replace(/[\s\u00A0\u3000]+/g, "-")
|
||||||
// Drop quotes, brackets, and punctuation known to break shells/markdown.
|
// Drop quotes, brackets, and punctuation known to break shells/markdown.
|
||||||
// Keep alphanumerics, CJK ideographs, `-`, `_`, `.`.
|
// Keep Unicode letters/numbers and the safe separators `-`, `_`, `.`.
|
||||||
.replace(/[()[\]{}<>'"`,;:!?*|/\\=&%$#@^~+]/g, "")
|
.replace(/[()[\]{}<>'"`,;:!?*|/\\=&%$#@^~+]/g, "")
|
||||||
// Collapse consecutive separators.
|
// Collapse consecutive separators.
|
||||||
.replace(/-{2,}/g, "-")
|
.replace(/-{2,}/g, "-")
|
||||||
|
|||||||
Reference in New Issue
Block a user